Commit c8ebc476 authored by Volker Krause's avatar Volker Krause
Browse files

Add a generic BER/DER/X.690 decoder

This will replace various incomplete adhoc solutions for this in the VDV
ticket parsing. It will also be helpful for decoding at least parts of SBB
and Thalys ticket codes.

This currently only does the bare minimum, proper type decoding and
recursing into nested structures would also be useful, as well as support
for built-in primitive type content.
parent cf6ab98d
......@@ -10,6 +10,7 @@ ecm_add_test(knowledgedbtest.cpp LINK_LIBRARIES Qt5::Test KPim::Itinerary)
ecm_add_test(airportdbtest.cpp LINK_LIBRARIES Qt5::Test KPim::Itinerary)
ecm_add_test(extractorinputtest.cpp LINK_LIBRARIES Qt5::Test KPim::Itinerary)
ecm_add_test(extractorrepositorytest.cpp LINK_LIBRARIES Qt5::Test KPim::Itinerary)
ecm_add_test(berdecodertest.cpp LINK_LIBRARIES Qt5::Test KPim::Itinerary)
ecm_add_test(bcbpparsertest.cpp LINK_LIBRARIES Qt5::Test KPim::Itinerary)
ecm_add_test(uic9183parsertest.cpp LINK_LIBRARIES Qt5::Test KPim::Itinerary)
ecm_add_test(vdvtickettest.cpp LINK_LIBRARIES Qt5::Test KPim::Itinerary)
......
/*
Copyright (C) 2020 Volker Krause <vkrause@kde.org>
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <tlv/berelement.cpp>
#include <QDebug>
#include <QObject>
#include <QTest>
using namespace KItinerary;
class BerDecoderTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testBerElementInvalid_data()
{
QTest::addColumn<QByteArray>("input");
QTest::newRow("empty") << QByteArray();
QTest::newRow("only type") << QByteArray::fromHex("02");
QTest::newRow("truncated extended type") << QByteArray::fromHex("1F");
QTest::newRow("no content") << QByteArray::fromHex("0201");
QTest::newRow("too large") << QByteArray::fromHex("020242");
QTest::newRow("variable length no terminator") << QByteArray::fromHex("028042");
}
void testBerElementInvalid()
{
QFETCH(QByteArray, input);
BER::Element e(input);
QVERIFY(!e.isValid());
}
void testBerElementType_data()
{
QTest::addColumn<QByteArray>("input");
QTest::addColumn<uint32_t>("type");
QTest::newRow("primitive type") << QByteArray::fromHex("020142") << 0x02u;
QTest::newRow("extended type") << QByteArray::fromHex("1F420142") << 0x1F42u;
}
void testBerElementType()
{
QFETCH(QByteArray, input);
QFETCH(uint32_t, type);
BER::Element e(input);
QVERIFY(e.isValid());
QCOMPARE(e.type(), type);
QCOMPARE(e.size(), input.size());
QVERIFY(e.contentData());
}
void testBerElementContentSize_data()
{
QTest::addColumn<QByteArray>("input");
QTest::addColumn<int>("size");
QTest::newRow("basic size") << QByteArray::fromHex("020142") << 1;
QTest::newRow("1 byte extended size") << QByteArray::fromHex("02810142") << 1;
QByteArray b(256, 42);
b.prepend(QByteArray::fromHex("02820100"));
QTest::newRow("2 byte extended size") << b << 256;
QTest::newRow("variable length") << QByteArray::fromHex("0280420000") << 1;
}
void testBerElementContentSize()
{
QFETCH(QByteArray, input);
QFETCH(int, size);
BER::Element e(input);
QVERIFY(e.isValid());
QCOMPARE(e.contentSize(), size);
QCOMPARE(e.size(), input.size());
QVERIFY(e.contentData());
}
};
QTEST_APPLESS_MAIN(BerDecoderTest)
#include "berdecodertest.moc"
......@@ -50,6 +50,8 @@ set(kitinerary_lib_srcs
pdf/popplerglobalparams.cpp
pdf/popplerutils.cpp
tlv/berelement.cpp
uic9183/rct2ticket.cpp
uic9183/uic9183block.cpp
uic9183/uic9183parser.cpp
......
/*
Copyright (C) 2020 Volker Krause <vkrause@kde.org>
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "berelement_p.h"
#include <QDebug>
using namespace KItinerary;
enum {
BerLongTypeMask = 0x1F,
BerExtendedTypeMask = 0x80,
BerExtendedLenghtMask = 0x80,
BerVariableLengthMarker = 0x80,
};
BER::Element::Element(const QByteArray &data, int offset)
: m_data(data)
, m_offset(offset)
{
if (!isValid()) {
m_data.clear();
m_offset = -1;
}
}
BER::Element::~Element() = default;
bool BER::Element::isValid() const
{
if (m_offset < 0 || m_offset + 2 >= m_data.size()) {
return false;
}
// check type size
const auto ts = typeSize();
if (ts < 0 || ts >= 4 || m_offset + ts + 1 >= m_data.size()) {
return false;
}
// check size of length field
const auto ls = lengthSize();
if (ls <= 0 || ls >= 4 || m_offset + ts + ls >= m_data.size()) {
return false;
}
// check size of the content
const auto cs = contentSize();
return cs > 0 && m_offset + ts + ls + cs <= m_data.size();
}
int BER::Element::typeSize() const
{
assert(m_offset >= 0);
assert(m_offset + 1 < m_data.size());
auto it = m_data.begin() + m_offset;
if (((*it) & BerLongTypeMask) != BerLongTypeMask) {
return 1;
}
while (it != m_data.end()) {
++it;
if (((*it) & BerExtendedTypeMask) == 0) {
return std::distance(m_data.begin(), it) - m_offset + 1;
}
}
return 0;
}
uint32_t BER::Element::type() const
{
const auto ts = typeSize();
// this is actually complete wrong compared to BER encoding rules, but since pretty much all docs
// we works with use the encoded type ids rather than the decoded ones this is actually much more practical
// might be worth to eventually have two methods for this, one with proper decoding and one with the raw data
uint32_t result = 0;
for (int i = 0; i < ts; ++i) {
result <<= 8;
result += (uint8_t)*(m_data.constData() + m_offset + i);
}
return result;
}
int BER::Element::lengthSize() const
{
const auto ts = typeSize();
const uint8_t firstLengthByte = *(m_data.constData() + m_offset + ts);
if (firstLengthByte == BerVariableLengthMarker) {
return 1;
}
if (firstLengthByte & BerExtendedLenghtMask) {
return (firstLengthByte & ~BerExtendedLenghtMask) + 1;
}
return 1;
}
int BER::Element::size() const
{
const auto ts = typeSize();
const auto s = ts + lengthSize() + contentSize();
const uint8_t firstLengthByte = *(m_data.constData() + m_offset + ts);
if (firstLengthByte == BerVariableLengthMarker) {
return s + 2;
}
return s;
}
int BER::Element::contentSize() const
{
const auto ts = typeSize();
const uint8_t firstLengthByte = *(m_data.constData() + m_offset + ts);
if (firstLengthByte == BerVariableLengthMarker) {
return m_data.indexOf(QByteArray("\0\0", 2), m_offset + ts + 1) - m_offset - ts - 1;
}
if (firstLengthByte & BerExtendedLenghtMask) {
const auto ls = firstLengthByte & ~BerExtendedLenghtMask;
int result = 0;
for (int i = 0; i < ls; ++i) {
result <<= 8;
result += (uint8_t)*(m_data.constData() + m_offset + ts + 1 + i);
}
return result;
}
return firstLengthByte;
}
const uint8_t* BER::Element::contentData() const
{
return reinterpret_cast<const uint8_t*>(m_data.constData() + typeSize() + lengthSize());
}
/*
Copyright (C) 2020 Volker Krause <vkrause@kde.org>
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef KITINERARY_BER_ELEMENT_P_H
#define KITINERARY_BER_ELEMENT_P_H
#include <QByteArray>
#include <cstdint>
namespace KItinerary {
/** BER/DER/X.690 encoding classes and functions. */
namespace BER {
/**
* An element in BER/DER/X.690 encoding.
*/
class Element
{
public:
explicit Element(const QByteArray &data, int offset = 0);
~Element();
/** Returns @c true if this element has a valid structure and can be read from. */
bool isValid() const;
/** Type, "right-aligned" in the returned 32bit value. */
uint32_t type() const;
/** Size of the entire element (type, size and content). */
int size() const;
/** Size of the value part of this element.
* This is excluding a possible variable length end marker.
*/
int contentSize() const;
/** Raw content data. */
const uint8_t* contentData() const;
private:
int typeSize() const;
int lengthSize() const;
QByteArray m_data;
int m_offset = -1;
};
}
}
#endif // KITINERARY_BER_ELEMENT_P_H
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment