diff options
Diffstat (limited to 'tests/auto/corelib/serialization')
3 files changed, 947 insertions, 0 deletions
diff --git a/tests/auto/corelib/serialization/qcborstreamreader/qcborstreamreader.pro b/tests/auto/corelib/serialization/qcborstreamreader/qcborstreamreader.pro new file mode 100644 index 0000000000..5df331314a --- /dev/null +++ b/tests/auto/corelib/serialization/qcborstreamreader/qcborstreamreader.pro @@ -0,0 +1,11 @@ +QT = core testlib +TARGET = tst_qcborstreamreader +CONFIG += testcase +SOURCES += \ + tst_qcborstreamreader.cpp + +INCLUDEPATH += \ + ../../../../../src/3rdparty/tinycbor/src \ + ../../../../../src/3rdparty/tinycbor/tests/parser + +DEFINES += SRCDIR=\\\"$$PWD/\\\" diff --git a/tests/auto/corelib/serialization/qcborstreamreader/tst_qcborstreamreader.cpp b/tests/auto/corelib/serialization/qcborstreamreader/tst_qcborstreamreader.cpp new file mode 100644 index 0000000000..06df9f2a68 --- /dev/null +++ b/tests/auto/corelib/serialization/qcborstreamreader/tst_qcborstreamreader.cpp @@ -0,0 +1,935 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Intel Corporation. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/qcborstream.h> +#include <QtTest> + +class tst_QCborStreamReader : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void basics(); + void integers_data(); + void integers(); + void fixed_data(); + void fixed(); + void strings_data(); + void strings(); + void tags_data(); + void tags() { fixed(); } + void emptyContainers_data(); + void emptyContainers(); + + void arrays_data(); + void arrays(); + void maps_data() { arrays_data(); } + void maps(); + void undefLengthArrays_data() { arrays_data(); } + void undefLengthArrays(); + void undefLengthMaps_data() { arrays_data(); } + void undefLengthMaps(); + + void next_data() { arrays_data(); } + void next(); + void validation_data(); + void validation(); + void recursionLimit_data(); + void recursionLimit(); + + void addData_singleElement_data(); + void addData_singleElement(); + void addData_complex_data() { arrays_data(); } + void addData_complex(); +}; + +#define FOR_CBOR_TYPE(F) \ + F(QCborStreamReader::UnsignedInteger) \ + F(QCborStreamReader::NegativeInteger) \ + F(QCborStreamReader::ByteArray) \ + F(QCborStreamReader::String) \ + F(QCborStreamReader::Array) \ + F(QCborStreamReader::Map) \ + F(QCborStreamReader::Tag) \ + F(QCborStreamReader::SimpleType) \ + F(QCborStreamReader::Float16) \ + F(QCborStreamReader::Float) \ + F(QCborStreamReader::Double) \ + F(QCborStreamReader::Invalid) + +QT_BEGIN_NAMESPACE +namespace QTest { +template<> char *toString<QCborStreamReader::Type>(const QCborStreamReader::Type &t) +{ + return qstrdup([=]() { + switch (t) { +#define TYPE(t) \ + case t: return QT_STRINGIFY(t); + FOR_CBOR_TYPE(TYPE) +#undef TYPE + } + return "<huh?>"; + }()); +} +} // namespace QTest +QT_END_NAMESPACE + +// Get the data from TinyCBOR (see src/3rdparty/tinycbor/tests/parser/data.cpp) +#include "data.cpp" + +void tst_QCborStreamReader::basics() +{ + QCborStreamReader reader; + + QCOMPARE(reader.device(), nullptr); + QCOMPARE(reader.currentOffset(), 0); + QCOMPARE(reader.lastError(), QCborError::EndOfFile); + + QCOMPARE(reader.type(), QCborStreamReader::Invalid); + QVERIFY(!reader.isUnsignedInteger()); + QVERIFY(!reader.isNegativeInteger()); + QVERIFY(!reader.isByteArray()); + QVERIFY(!reader.isString()); + QVERIFY(!reader.isArray()); + QVERIFY(!reader.isContainer()); + QVERIFY(!reader.isMap()); + QVERIFY(!reader.isTag()); + QVERIFY(!reader.isSimpleType()); + QVERIFY(!reader.isBool()); + QVERIFY(!reader.isNull()); + QVERIFY(!reader.isUndefined()); + QVERIFY(!reader.isFloat16()); + QVERIFY(!reader.isFloat()); + QVERIFY(!reader.isDouble()); + QVERIFY(!reader.isValid()); + QVERIFY(reader.isInvalid()); + + QCOMPARE(reader.containerDepth(), 0); + QCOMPARE(reader.parentContainerType(), QCborStreamReader::Invalid); + QVERIFY(!reader.hasNext()); + QVERIFY(!reader.next()); + QCOMPARE(reader.lastError(), QCborError::EndOfFile); + + QVERIFY(reader.isLengthKnown()); // well, it's not unknown + QCOMPARE(reader.length(), ~0ULL); + + reader.addData(QByteArray()); + reader.reparse(); + + // nothing changes, we added nothing + QCOMPARE(reader.device(), nullptr); + QCOMPARE(reader.currentOffset(), 0); + QCOMPARE(reader.lastError(), QCborError::EndOfFile); + + QCOMPARE(reader.type(), QCborStreamReader::Invalid); + QVERIFY(!reader.isUnsignedInteger()); + QVERIFY(!reader.isNegativeInteger()); + QVERIFY(!reader.isByteArray()); + QVERIFY(!reader.isString()); + QVERIFY(!reader.isArray()); + QVERIFY(!reader.isContainer()); + QVERIFY(!reader.isMap()); + QVERIFY(!reader.isTag()); + QVERIFY(!reader.isSimpleType()); + QVERIFY(!reader.isBool()); + QVERIFY(!reader.isNull()); + QVERIFY(!reader.isUndefined()); + QVERIFY(!reader.isFloat16()); + QVERIFY(!reader.isFloat()); + QVERIFY(!reader.isDouble()); + QVERIFY(!reader.isValid()); + QVERIFY(reader.isInvalid()); + + QVERIFY(reader.isLengthKnown()); // well, it's not unknown + QCOMPARE(reader.length(), ~0ULL); + + QCOMPARE(reader.containerDepth(), 0); + QCOMPARE(reader.parentContainerType(), QCborStreamReader::Invalid); + QVERIFY(!reader.hasNext()); + QVERIFY(!reader.next()); +} + +void tst_QCborStreamReader::integers_data() +{ + addIntegers(); +} + +void tst_QCborStreamReader::integers() +{ + QFETCH(QByteArray, data); + QFETCH(bool, isNegative); + QFETCH(quint64, expectedRaw); + QFETCH(qint64, expectedValue); + QFETCH(bool, inInt64Range); + quint64 absolute = (isNegative ? expectedRaw + 1 : expectedRaw); + + QCborStreamReader reader(data); + QVERIFY(reader.isValid()); + QCOMPARE(reader.lastError(), QCborError::NoError); + QVERIFY(reader.isInteger()); + + if (inInt64Range) + QCOMPARE(reader.toInteger(), expectedValue); + if (isNegative) + QCOMPARE(quint64(reader.toNegativeInteger()), absolute); + else + QCOMPARE(reader.toUnsignedInteger(), absolute); +} + +void escapedAppendTo(QString &result, const QByteArray &data) +{ + result += "h'" + QString::fromLatin1(data.toHex()) + '\''; +} + +void escapedAppendTo(QString &result, const QString &data) +{ + result += '"'; + for (int i = 0; i <= data.size(); i += 245) { + // hopefully we won't have a surrogate pair split here + QScopedArrayPointer<char> escaped(QTest::toPrettyUnicode(data.mid(i, 245))); + QLatin1String s(escaped.data() + 1); // skip opening " + s.chop(1); // drop the closing " + result += s; + } + result += '"'; +} + +template <typename S, QCborStreamReader::StringResult<S> (QCborStreamReader:: *Decoder)()> +static QString parseOneString_helper(QCborStreamReader &reader) +{ + QString result; + bool parens = !reader.isLengthKnown(); + if (parens) + result += '('; + + auto r = (reader.*Decoder)(); + const char *comma = ""; + while (r.status == QCborStreamReader::Ok) { + result += comma; + escapedAppendTo(result, r.data); + + r = (reader.*Decoder)(); + comma = ", "; + } + + if (r.status == QCborStreamReader::Error) + return QString(); + + if (parens) + result += ')'; + return result; +} + +static QString parseOneByteArray(QCborStreamReader &reader) +{ + return parseOneString_helper<QByteArray, &QCborStreamReader::readByteArray>(reader); +} + +static QString parseOneString(QCborStreamReader &reader) +{ + return parseOneString_helper<QString, &QCborStreamReader::readString>(reader); +} + +static QString makeNegativeString(QCborNegativeInteger n) +{ + return n == QCborNegativeInteger(0) ? + QString("-18446744073709551616") : + QString("-%1").arg(quint64(n)); +} + +template <typename T> static inline bool canConvertTo(double v) +{ + // The [conv.fpint] (7.10 Floating-integral conversions) section of the + // standard says only exact conversions are guaranteed. Converting + // integrals to floating-point with loss of precision has implementation- + // defined behavior whether the next higher or next lower is returned; + // converting FP to integral is UB if it can't be represented.; + Q_STATIC_ASSERT(std::numeric_limits<T>::is_integer); + + double supremum = ldexp(1, std::numeric_limits<T>::digits); + if (v >= supremum) + return false; + + if (v < std::numeric_limits<T>::min()) // either zero or a power of two, so it's exact + return false; + + // we're in range + return v == floor(v); +} + +static QString makeFpString(double v) +{ + if (canConvertTo<qint64>(v)) + return QString::number(qint64(v)) + '.'; + if (canConvertTo<quint64>(v)) + return QString::number(quint64(v)) + '.'; + + QString s = QString::number(v, 'g', std::numeric_limits<double>::digits10 + 2); + if (!s.contains('.') && !s.contains('e') && !qIsInf(v) && !qIsNaN(v)) + s += '.'; + return s; +} + +static QString makeFpString(float v) +{ + if (qIsInf(v)) + return v > 0 ? "inf" : "-inf"; + if (qIsNaN(v)) + return "nan"; + return makeFpString(double(v)) + 'f'; +} + +static QString makeFpString(qfloat16 v) +{ + if (qIsInf(v)) + return v > 0 ? "inf" : "-inf"; + if (qIsNaN(v)) + return "nan"; + return makeFpString(double(v)) + "f16"; +} + +static QString parseOne(QCborStreamReader &reader) +{ + QString result; + + switch (reader.type()) { + case QCborStreamReader::UnsignedInteger: + result = QString::number(reader.toUnsignedInteger()); + break; + case QCborStreamReader::NegativeInteger: + result = makeNegativeString(reader.toNegativeInteger()); + break; + case QCborStreamReader::ByteArray: + return parseOneByteArray(reader); + case QCborStreamReader::String: + return parseOneString(reader); + case QCborStreamReader::Array: + case QCborStreamReader::Map: { + const char *delimiters = (reader.isArray() ? "[]" : "{}"); + result += delimiters[0]; + reader.enterContainer(); + + QLatin1String comma(""); + while (reader.lastError() == QCborError::NoError && reader.hasNext()) { + result += comma + parseOne(reader); + comma = QLatin1String(", "); + + if (reader.parentContainerType() == QCborStreamReader::Map + && reader.lastError() == QCborError::NoError) + result += ": " + parseOne(reader); + } + + if (reader.isValid()) + return QString(); + if (reader.lastError() != QCborError::NoError) + return QString(); + reader.leaveContainer(); + result += delimiters[1]; + return result; + } + case QCborStreamReader::Tag: { + QCborTag tag = reader.toTag(); + if (!reader.next()) + return QString(); + return QString("%1(%2)").arg(quint64(tag)).arg(parseOne(reader)); + } + case QCborStreamReader::SimpleType: + switch (reader.toSimpleType()) { + case QCborSimpleType::False: + result = QStringLiteral("false"); + break; + case QCborSimpleType::True: + result = QStringLiteral("true"); + break; + case QCborSimpleType::Null: + result = QStringLiteral("null"); + break; + case QCborSimpleType::Undefined: + result = QStringLiteral("undefined"); + break; + default: + result = QString("simple(%1)").arg(quint8(reader.toSimpleType())); + break; + } + break; + case QCborStreamReader::Float16: + result = makeFpString(reader.toFloat16()); + break; + case QCborStreamReader::Float: + result = makeFpString(reader.toFloat()); + break; + case QCborStreamReader::Double: + result = makeFpString(reader.toDouble()); + break; + case QCborStreamReader::Invalid: + return QStringLiteral("<invalid>"); + } + + if (!reader.next()) + return QString(); + return result; +} + +bool parseNonRecursive(QString &result, bool &printingStringChunks, QCborStreamReader &reader) +{ + while (reader.lastError() == QCborError::NoError) { + if (!reader.hasNext()) { + if (result.endsWith(", ")) + result.chop(2); + if (reader.containerDepth() == 0) + return true; + result += reader.parentContainerType() == QCborStreamReader::Map ? "}, " : "], "; + reader.leaveContainer(); + continue; + } + + switch (reader.type()) { + case QCborStreamReader::UnsignedInteger: + result += QString::number(reader.toUnsignedInteger()); + break; + case QCborStreamReader::NegativeInteger: + result += makeNegativeString(reader.toNegativeInteger()); + break; + case QCborStreamReader::ByteArray: + case QCborStreamReader::String: { + QCborStreamReader::StringResultCode status; + if (!printingStringChunks && !reader.isLengthKnown()) { + printingStringChunks = true; + result += '('; + } + if (reader.isByteArray()) { + auto r = reader.readByteArray(); + status = r.status; + if (r.status == QCborStreamReader::Ok) + escapedAppendTo(result, r.data); + } else { + auto r = reader.readString(); + status = r.status; + if (r.status == QCborStreamReader::Ok) + escapedAppendTo(result, r.data); + } + + if (status == QCborStreamReader::EndOfString) { + if (result.endsWith(", ")) + result.chop(2); + if (printingStringChunks) + result += ')'; + result += ", "; + printingStringChunks = false; + } + if (status == QCborStreamReader::Ok && printingStringChunks) + result += ", "; + + continue; + } + case QCborStreamReader::Array: + result += '['; + reader.enterContainer(); + continue; + case QCborStreamReader::Map: + result += '{'; + reader.enterContainer(); + continue; + case QCborStreamReader::Tag: + result += QString("Tag:%1:").arg(quint64(reader.toTag())); + reader.next(); + continue; // skip the comma + case QCborStreamReader::SimpleType: + switch (reader.toSimpleType()) { + case QCborSimpleType::False: + result += QStringLiteral("false"); + break; + case QCborSimpleType::True: + result += QStringLiteral("true"); + break; + case QCborSimpleType::Null: + result += QStringLiteral("null"); + break; + case QCborSimpleType::Undefined: + result += QStringLiteral("undefined"); + break; + default: + result += QString("simple(%1)").arg(quint8(reader.toSimpleType())); + break; + } + break; + case QCborStreamReader::Float16: + result += makeFpString(reader.toFloat16()); + break; + case QCborStreamReader::Float: + result += makeFpString(reader.toFloat()); + break; + case QCborStreamReader::Double: + result += makeFpString(reader.toDouble()); + break; + case QCborStreamReader::Invalid: + break; + } + + reader.next(); + result += ", "; + } + return false; +}; + + +static QString &removeIndicators(QString &str) +{ + // remove any CBOR encoding indicators from the string, since parseOne above + // doesn't produce them + static QRegularExpression rx("_(\\d+)? ?"); + return str.replace(rx, QString()); +} + +void tst_QCborStreamReader::fixed_data() +{ + addColumns(); + addFixedData(); +} + +void tst_QCborStreamReader::fixed() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + removeIndicators(expected); + + QCborStreamReader reader(data); + QVERIFY(reader.isValid()); + QCOMPARE(reader.lastError(), QCborError::NoError); + QCOMPARE(parseOne(reader), expected); + + // verify that we can re-read + reader.reset(); + QVERIFY(reader.isValid()); + QCOMPARE(reader.lastError(), QCborError::NoError); + QCOMPARE(parseOne(reader), expected); +} + +void tst_QCborStreamReader::strings_data() +{ + addColumns(); + addStringsData(); +} + +void tst_QCborStreamReader::strings() +{ + fixed(); + if (QTest::currentTestFailed()) + return; + + // Extra string checks: + // We'll compare the reads using readString() and readByteArray() + // (henceforth "control data" because fixed() above tested them) with those + // obtained with readStringChunk(). + + QFETCH(QByteArray, data); + QFETCH(QString, expected); + bool isChunked = expected.startsWith('('); + + QCborStreamReader reader(data), controlReader(data); + QVERIFY(reader.isString() || reader.isByteArray()); + QCOMPARE(reader.isLengthKnown(), !isChunked); + + if (!isChunked) + QCOMPARE(reader.currentStringChunkSize(), qsizetype(reader.length())); + + int chunks = 0; + forever { + QCborStreamReader::StringResult<QByteArray> controlData; + if (reader.isString()) { + auto r = controlReader.readString(); + controlData.data = r.data.toUtf8(); + controlData.status = r.status; + } else { + controlData = controlReader.readByteArray(); + } + QVERIFY(controlData.status != QCborStreamReader::Error); + + for (int i = 0; i < 10; ++i) { + // this call must work several times with the same result + QCOMPARE(reader.currentStringChunkSize(), controlData.data.size()); + } + + QByteArray chunk(controlData.data.size(), Qt::Uninitialized); + auto r = reader.readStringChunk(chunk.data(), chunk.size()); + QCOMPARE(r.status, controlData.status); + if (r.status == QCborStreamReader::Ok) + QCOMPARE(r.data, controlData.data.size()); + else + QCOMPARE(r.data, 0); + QCOMPARE(chunk, controlData.data); + + if (r.status == QCborStreamReader::EndOfString) + break; + ++chunks; + } + + if (!isChunked) + QCOMPARE(chunks, 1); +} + +void tst_QCborStreamReader::tags_data() +{ + addColumns(); + addTagsData(); +} + +void tst_QCborStreamReader::emptyContainers_data() +{ + addColumns(); + addEmptyContainersData(); +} + +void tst_QCborStreamReader::emptyContainers() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + removeIndicators(expected); + + QCborStreamReader reader(data); + QVERIFY(reader.isValid()); + QCOMPARE(reader.lastError(), QCborError::NoError); + if (reader.isLengthKnown()) + QCOMPARE(reader.length(), 0U); + QCOMPARE(parseOne(reader), expected); + + // verify that we can re-read + reader.reset(); + QVERIFY(reader.isValid()); + QCOMPARE(reader.lastError(), QCborError::NoError); + if (reader.isLengthKnown()) + QCOMPARE(reader.length(), 0U); + QCOMPARE(parseOne(reader), expected); +} + +void tst_QCborStreamReader::arrays_data() +{ + addColumns(); + addFixedData(); + addStringsData(); + addTagsData(); + addEmptyContainersData(); +} + +static void checkContainer(int len, const QByteArray &data, const QString &expected) +{ + QCborStreamReader reader(data); + QVERIFY(reader.isValid()); + QCOMPARE(reader.lastError(), QCborError::NoError); + if (len >= 0) { + QVERIFY(reader.isLengthKnown()); + QCOMPARE(reader.length(), uint(len)); + } + QCOMPARE(parseOne(reader), expected); + + // verify that we can re-read + reader.reset(); + QVERIFY(reader.isValid()); + QCOMPARE(reader.lastError(), QCborError::NoError); + if (len >= 0) { + QVERIFY(reader.isLengthKnown()); + QCOMPARE(reader.length(), uint(len)); + } + QCOMPARE(parseOne(reader), expected); +} + +void tst_QCborStreamReader::arrays() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + removeIndicators(expected); + + checkContainer(1, '\x81' + data, '[' + expected + ']'); + if (QTest::currentTestFailed()) + return; + + checkContainer(2, '\x82' + data + data, '[' + expected + ", " + expected + ']'); +} + +void tst_QCborStreamReader::maps() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + removeIndicators(expected); + + // int keys + checkContainer(1, "\xa1\1" + data, "{1: " + expected + '}'); + if (QTest::currentTestFailed()) + return; + + checkContainer(2, "\xa2\1" + data + '\x20' + data, + "{1: " + expected + ", -1: " + expected + '}'); + if (QTest::currentTestFailed()) + return; + + // string keys + checkContainer(1, "\xa1\x65Hello" + data, "{\"Hello\": " + expected + '}'); + if (QTest::currentTestFailed()) + return; + + checkContainer(2, "\xa2\x65World" + data + "\x65Hello" + data, + "{\"World\": " + expected + ", \"Hello\": " + expected + '}'); +} + +void tst_QCborStreamReader::undefLengthArrays() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + removeIndicators(expected); + + checkContainer(-1, '\x9f' + data + '\xff', '[' + expected + ']'); + if (QTest::currentTestFailed()) + return; + + checkContainer(-2, '\x9f' + data + data + '\xff', '[' + expected + ", " + expected + ']'); +} + +void tst_QCborStreamReader::undefLengthMaps() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + removeIndicators(expected); + + // int keys + checkContainer(-1, "\xbf\1" + data + '\xff', "{1: " + expected + '}'); + if (QTest::currentTestFailed()) + return; + + checkContainer(-2, "\xbf\1" + data + '\x20' + data + '\xff', + "{1: " + expected + ", -1: " + expected + '}'); + if (QTest::currentTestFailed()) + return; + + // string keys + checkContainer(-1, "\xbf\x65Hello" + data + '\xff', "{\"Hello\": " + expected + '}'); + if (QTest::currentTestFailed()) + return; + + checkContainer(-2, "\xbf\x65World" + data + "\x65Hello" + data + '\xff', + "{\"World\": " + expected + ", \"Hello\": " + expected + '}'); +} + +void tst_QCborStreamReader::next() +{ + QFETCH(QByteArray, data); + + auto doit = [](const QByteArray &data) { + QCborStreamReader reader(data); + return reader.next(); + }; + + QVERIFY(doit('\x81' + data)); + QVERIFY(doit('\x82' + data + data)); + QVERIFY(doit('\x9f' + data + '\xff')); + QVERIFY(doit("\x81\x9f" + data + '\xff')); + QVERIFY(doit("\x9f\x81" + data + '\xff')); + + QVERIFY(doit("\xa1\1" + data)); + QVERIFY(doit("\xa2\1" + data + '\x20' + data)); + QVERIFY(doit("\xbf\1" + data + '\xff')); + QVERIFY(doit("\xbf\x9f\1\xff\x9f" + data + "\xff\xff")); +} + +void tst_QCborStreamReader::validation_data() +{ + addValidationColumns(); + addValidationData(); +} + +void tst_QCborStreamReader::validation() +{ + QFETCH(QByteArray, data); + + QCborStreamReader reader(data); + parseOne(reader); + QVERIFY(reader.lastError() != QCborError::NoError); + + // next() should fail + reader.reset(); + QVERIFY(!reader.next()); + QVERIFY(reader.lastError() != QCborError::NoError); +} + +static const int Recursions = 3; +void tst_QCborStreamReader::recursionLimit_data() +{ + static const int recursions = Recursions + 2; + QTest::addColumn<QByteArray>("data"); + + QTest::newRow("array") << QByteArray(recursions, '\x81') + '\x20'; + QTest::newRow("_array") << QByteArray(recursions, '\x9f') + '\x20' + QByteArray(recursions, '\xff'); + + QByteArray data; + for (int i = 0; i < recursions; ++i) + data += "\xa1\x65Hello"; + data += '\2'; + QTest::newRow("map-recursive-values") << data; + + data.clear(); + for (int i = 0; i < recursions; ++i) + data += "\xbf\x65World"; + data += '\2'; + for (int i = 0; i < recursions; ++i) + data += "\xff"; + QTest::newRow("_map-recursive-values") << data; + + data = QByteArray(recursions, '\xa1'); + data += '\2'; + for (int i = 0; i < recursions; ++i) + data += "\x7f\x64quux\xff"; + QTest::newRow("map-recursive-keys") << data; + + data = QByteArray(recursions, '\xbf'); + data += '\2'; + for (int i = 0; i < recursions; ++i) + data += "\1\xff"; + QTest::newRow("_map-recursive-keys") << data; + + data.clear(); + for (int i = 0; i < recursions / 2; ++i) + data += "\x81\xa1\1"; + data += '\2'; + QTest::newRow("mixed") << data; +} + +void tst_QCborStreamReader::recursionLimit() +{ + QFETCH(QByteArray, data); + + QCborStreamReader reader('\x81' + data); + + // verify that it works normally: + QVERIFY(reader.enterContainer()); + QVERIFY(reader.next()); + QVERIFY(!reader.hasNext()); + + reader.reset(); + QVERIFY(reader.enterContainer()); + QVERIFY(!reader.next(Recursions)); + QCOMPARE(reader.lastError(), QCborError::NestingTooDeep); +} + +void tst_QCborStreamReader::addData_singleElement_data() +{ + addColumns(); + addFixedData(); + addNonChunkedStringsData(); +} + +void tst_QCborStreamReader::addData_singleElement() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + removeIndicators(expected); + + QCborStreamReader reader; + for (int i = 0; i < data.size() - 1; ++i) { + // add one byte from the data + reader.addData(data.constData() + i, 1); + parseOne(reader); + QCOMPARE(reader.lastError(), QCborError::EndOfFile); + } + + // add the last byte + reader.addData(data.right(1)); + QCOMPARE(reader.lastError(), QCborError::NoError); + QCOMPARE(parseOne(reader), expected); +} + +void tst_QCborStreamReader::addData_complex() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + removeIndicators(expected); + + // transform tags (parseNonRecursive() can't produce the usual form) + expected.replace(QRegularExpression(R"/((\d+)\(([^)]+)\))/"), "Tag:\\1:\\2"); + + auto doit = [](const QByteArray &data) { + // start with one byte + int added = 1; + QCborStreamReader reader(data.constData(), 1); + QString result; + bool printingStringChunks = false; + forever { + if (parseNonRecursive(result, printingStringChunks, reader)) + return result; + if (reader.lastError() != QCborError::EndOfFile) + return reader.lastError().toString(); + + while (reader.lastError() == QCborError::EndOfFile) { + // add more data + if (added == data.size()) + return QStringLiteral("Couldn't parse even with all data"); + reader.addData(data.constData() + added++, 1); + } + } + }; + + // plain: + QCOMPARE(doit(data), expected); + + // in an array + QCOMPARE(doit('\x81' + data), '[' + expected + ']'); + QCOMPARE(doit('\x82' + data + data), '[' + expected + ", " + expected + ']'); + + QCOMPARE(doit('\x9f' + data + '\xff'), '[' + expected + ']'); + QCOMPARE(doit('\x9f' + data + data + '\xff'), '[' + expected + ", " + expected + ']'); + + // in a map + QCOMPARE(doit("\xa1\x01" + data), "{1, " + expected + '}'); + QCOMPARE(doit("\xa1\x65Hello" + data), "{\"Hello\", " + expected + '}'); + QCOMPARE(doit("\xa1\x7f\x65Hello\x65World\xff" + data), "{(\"Hello\", \"World\"), " + expected + '}'); + QCOMPARE(doit("\xa2\x01" + data + "\x65Hello" + data), + "{1, " + expected + ", \"Hello\", " + expected + '}'); + + QCOMPARE(doit("\xbf\x01" + data + '\xff'), "{1, " + expected + '}'); + + // mixed + QCOMPARE(doit("\xbf\x01\x81" + data + '\xff'), "{1, [" + expected + "]}"); + QCOMPARE(doit("\xbf\x01\x82" + data + data + '\xff'), + "{1, [" + expected + ", " + expected + "]}"); + QCOMPARE(doit("\xbf\x01\x9f" + data + data + "\xff\xff"), + "{1, [" + expected + ", " + expected + "]}"); +} + + +QTEST_MAIN(tst_QCborStreamReader) + +#include "tst_qcborstreamreader.moc" diff --git a/tests/auto/corelib/serialization/serialization.pro b/tests/auto/corelib/serialization/serialization.pro index e8eb70f311..a33cd6af87 100644 --- a/tests/auto/corelib/serialization/serialization.pro +++ b/tests/auto/corelib/serialization/serialization.pro @@ -1,6 +1,7 @@ TEMPLATE = subdirs SUBDIRS = \ json \ + qcborstreamreader \ qcborstreamwriter \ qdatastream \ qtextstream \ |