Commit 2c9bb76c authored by Volker Krause's avatar Volker Krause
Browse files

Implement zero-copy recursion for nested BER/DER elements

Needed by the VDV ticket parser.
parent c8ebc476
......@@ -90,6 +90,50 @@ private Q_SLOTS:
QCOMPARE(e.size(), input.size());
QVERIFY(e.contentData());
}
void testBerRecursionInvalid_data()
{
QTest::addColumn<QByteArray>("input");
QTest::newRow("incomplete content") << QByteArray::fromHex("1E020201");
QTest::newRow("wrong child size") << QByteArray::fromHex("1E03020242");
QTest::newRow("wrong child size with trailing data") << QByteArray::fromHex("1E03020242020142");
QTest::newRow("variable length child") << QByteArray::fromHex("1E0402804242420000");
}
void testBerRecursionInvalid()
{
QFETCH(QByteArray, input);
BER::Element e(input);
QVERIFY(e.isValid());
QVERIFY(!e.first().isValid());
}
void testBerChildCount_data()
{
QTest::addColumn<QByteArray>("input");
QTest::addColumn<int>("childCount");
QTest::newRow("one child") << QByteArray::fromHex("1E03020142") << 1;
QTest::newRow("two children") << QByteArray::fromHex("1E06020142020123") << 2;
QTest::newRow("one child, trailing element") << QByteArray::fromHex("1E03020142020123") << 1;
}
void testBerChildCount()
{
QFETCH(QByteArray, input);
QFETCH(int, childCount);
BER::Element e(input);
QVERIFY(e.isValid());
BER::Element c = e.first();
QVERIFY(c.isValid());
int i = 0;
while (c.isValid()) {
c = e.next(c);
++i;
}
QCOMPARE(i, childCount);
}
};
QTEST_APPLESS_MAIN(BerDecoderTest)
......
......@@ -28,13 +28,18 @@ enum {
BerVariableLengthMarker = 0x80,
};
BER::Element::Element(const QByteArray &data, int offset)
BER::Element::Element() = default;
BER::Element::Element(const QByteArray &data, int offset, int size)
: m_data(data)
, m_offset(offset)
, m_dataSize(size < 0 ? data.size() : std::min(offset + size, data.size()))
{
assert(m_dataSize <= m_data.size());
if (!isValid()) {
m_data.clear();
m_offset = -1;
m_dataSize = -1;
}
}
......@@ -42,37 +47,37 @@ BER::Element::~Element() = default;
bool BER::Element::isValid() const
{
if (m_offset < 0 || m_offset + 2 >= m_data.size()) {
if (m_offset < 0 || m_dataSize <= 0 || m_offset + 2 >= m_dataSize) {
return false;
}
// check type size
const auto ts = typeSize();
if (ts < 0 || ts >= 4 || m_offset + ts + 1 >= m_data.size()) {
if (ts < 0 || ts >= 4 || m_offset + ts + 1 >= m_dataSize) {
return false;
}
// check size of length field
const auto ls = lengthSize();
if (ls <= 0 || ls >= 4 || m_offset + ts + ls >= m_data.size()) {
if (ls <= 0 || ls >= 4 || m_offset + ts + ls >= m_dataSize) {
return false;
}
// check size of the content
const auto cs = contentSize();
return cs > 0 && m_offset + ts + ls + cs <= m_data.size();
return cs > 0 && m_offset + ts + ls + cs <= m_dataSize;
}
int BER::Element::typeSize() const
{
assert(m_offset >= 0);
assert(m_offset + 1 < m_data.size());
assert(m_offset + 1 < m_dataSize);
auto it = m_data.begin() + m_offset;
if (((*it) & BerLongTypeMask) != BerLongTypeMask) {
return 1;
}
while (it != m_data.end()) {
while (it != m_data.end() && std::distance(m_data.begin(), it) < m_dataSize) {
++it;
if (((*it) & BerExtendedTypeMask) == 0) {
return std::distance(m_data.begin(), it) - m_offset + 1;
......@@ -126,7 +131,11 @@ 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;
const auto idx = m_data.indexOf(QByteArray("\0\0", 2), m_offset + ts + 1);
if (idx + 1 > m_dataSize) {
return - 1;
}
return idx - m_offset - ts - 1;
}
if (firstLengthByte & BerExtendedLenghtMask) {
const auto ls = firstLengthByte & ~BerExtendedLenghtMask;
......@@ -140,7 +149,23 @@ int BER::Element::contentSize() const
return firstLengthByte;
}
int BER::Element::contentOffset() const
{
return m_offset + typeSize() + lengthSize();
}
const uint8_t* BER::Element::contentData() const
{
return reinterpret_cast<const uint8_t*>(m_data.constData() + typeSize() + lengthSize());
return reinterpret_cast<const uint8_t*>(m_data.constData() + contentOffset());
}
BER::Element BER::Element::first() const
{
return BER::Element(m_data, contentOffset(), contentSize());
}
BER::Element BER::Element::next(const Element &prev) const
{
const auto off = prev.m_offset + prev.size();
return BER::Element(m_data, off, contentSize() - (off - contentOffset()));
}
......@@ -29,11 +29,14 @@ namespace BER {
/**
* An element in BER/DER/X.690 encoding.
* Implicitly this is also kinda implementing a QByteArrayRef, as this works without copying
* the underlying data.
*/
class Element
{
public:
explicit Element(const QByteArray &data, int offset = 0);
Element();
explicit Element(const QByteArray &data, int offset = 0, int size = -1);
~Element();
/** Returns @c true if this element has a valid structure and can be read from. */
......@@ -52,12 +55,35 @@ public:
/** Raw content data. */
const uint8_t* contentData() const;
/** Convenience method to access typed content. */
template <typename T>
inline const T* contentAt(int offset) const
{
return reinterpret_cast<const T*>(contentData() + offset);
}
/** First child element, for nested types. */
Element first() const;
/** Next child element, for nested types. */
Element next(const Element &prev) const;
private:
int typeSize() const;
int lengthSize() const;
int contentOffset() const;
QByteArray m_data;
int m_offset = -1;
int m_dataSize = -1;
};
template <uint32_t TagValue>
struct TypedElement : public Element
{
inline bool isValid() const
{
return Element::isValid() && type() == TagValue;
}
};
}
......
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