diff --git a/src/cborpretty.c b/src/cborpretty.c index 02b5706b..704f3ce7 100644 --- a/src/cborpretty.c +++ b/src/cborpretty.c @@ -272,9 +272,11 @@ static const char *resolve_indicator(const CborValue *it, int flags) return no_indicator; /* determine whether to show anything */ - if ((flags & CborPrettyIndicateIndeterminateLength) && - additional_information == IndefiniteLength) - return indicators[IndefiniteLength - Value8Bit]; + if (additional_information == IndefiniteLength) { + if (flags & CborPrettyIndicateIndeterminateLength) + return indicators[IndefiniteLength - Value8Bit]; + return no_indicator; + } if ((flags & CborPrettyIndicateOverlongNumbers) == 0) return no_indicator; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1747927c..143dbd25 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -14,6 +14,7 @@ if(Qt6_FOUND) add_subdirectory(cpp) add_subdirectory(encoder) add_subdirectory(parser) + add_subdirectory(pretty) if(WITH_CBOR2JSON) add_subdirectory(tojson) endif() diff --git a/tests/parser/data.cpp b/tests/parser/data.cpp index 05df28a1..af923cb0 100644 --- a/tests/parser/data.cpp +++ b/tests/parser/data.cpp @@ -550,10 +550,14 @@ void addValidationData(size_t minInvalid = ~size_t(0)) QTest::newRow("array-no-break2") << raw("\x81\x9f\0") << 0 << CborErrorUnexpectedEOF; QTest::newRow("map-no-break1") << raw("\x81\xbf") << 0 << CborErrorUnexpectedEOF; QTest::newRow("map-no-break2") << raw("\x81\xbf\0\0") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("array-element-is-break") << raw("\x81\x81\xff") << 0 << CborErrorUnexpectedBreak; + QTest::newRow("map-key-is-break") << raw("\x81\xa1\xff\0") << 0 << CborErrorUnexpectedBreak; + QTest::newRow("map-value-is-break") << raw("\x81\xa1\0\xff") << 0 << CborErrorUnexpectedBreak; QTest::newRow("map-break-after-key") << raw("\x81\xbf\0\xff") << 0 << CborErrorUnexpectedBreak; QTest::newRow("map-break-after-second-key") << raw("\x81\xbf\x64xyzw\x04\x00\xff") << 0 << CborErrorUnexpectedBreak; QTest::newRow("map-break-after-value-tag") << raw("\x81\xbf\0\xc0\xff") << 0 << CborErrorUnexpectedBreak; QTest::newRow("map-break-after-value-tag2") << raw("\x81\xbf\0\xd8\x20\xff") << 0 << CborErrorUnexpectedBreak; + QTest::newRow("tagged-break") << raw("\xc0\xff") << 0 << CborErrorUnexpectedBreak; // check for pointer additions wrapping over the limit of the address space auto wraparoundError = [minInvalid](uint64_t encodedSize) { diff --git a/tests/pretty/CMakeLists.txt b/tests/pretty/CMakeLists.txt new file mode 100644 index 00000000..dda48a2d --- /dev/null +++ b/tests/pretty/CMakeLists.txt @@ -0,0 +1,3 @@ +# Copyright (C) 2026 Intel Corporation +# SPDX-License-Identifier: MIT +tinycbor_add_qtest(tst_pretty tst_pretty.cpp) diff --git a/tests/pretty/tst_pretty.cpp b/tests/pretty/tst_pretty.cpp new file mode 100644 index 00000000..efcff05e --- /dev/null +++ b/tests/pretty/tst_pretty.cpp @@ -0,0 +1,511 @@ +/**************************************************************************** +** +** Copyright (C) 2026 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#include +#include "cbor.h" +#include +#include +#include + +#ifndef CBOR_PARSER_MAX_RECURSIONS +# define CBOR_PARSER_MAX_RECURSIONS 1024 +#endif + +#ifndef QCOMPARE_EQ +// added for Qt 6.4 +# define QCOMPARE_EQ QCOMPARE +#endif + +Q_DECLARE_METATYPE(CborError) + +namespace QTest { +template<> char *toString(const CborError &err) +{ + return qstrdup(cbor_error_string(err)); +} +} + +template static QByteArray raw(const char (&data)[N]) +{ + return QByteArray::fromRawData(data, N - 1); +} + +static CborError qstring_printf(void *out, const char *fmt, ...) +{ + auto str = static_cast(out); + va_list va; + va_start(va, fmt); + *str += QString::vasprintf(fmt, va); + va_end(va); + return CborNoError; +}; + +static CborError parseOne(CborValue *it, QString *parsed, int flags) +{ + parsed->clear(); + return cbor_value_to_pretty_stream(qstring_printf, parsed, it, flags); +} + +// Convenience combinations of CborPrettyFlags used throughout this file. +enum { + FlagsNone = 0, + FlagsFragments = CborPrettyShowStringFragments, + FlagsIndeterminate = CborPrettyIndicateIndeterminateLength, + FlagsOverlong = CborPrettyIndicateOverlongNumbers, + FlagsFragmentsIndeterminate = FlagsFragments | FlagsIndeterminate, + FlagsIndeterminateOverlong = FlagsIndeterminate | FlagsOverlong, + FlagsAll = FlagsFragments | FlagsIndeterminate | FlagsOverlong +}; + +class tst_Pretty : public QObject +{ + Q_OBJECT +private slots: + void validInput_data(); + void validInput(); + void incompleteValidInput_data() { validInput_data(); } + void incompleteValidInput(); + void invalidInput_data(); + void invalidInput(); + void recursionLimit_data(); + void recursionLimit(); +}; + +void tst_Pretty::validInput_data() +{ + QTest::addColumn("data"); + QTest::addColumn("flags"); + QTest::addColumn("expected"); + + // integers: flags are irrelevant here (no overlong encoding used) + QTest::newRow("0") << raw("\x00") << int(FlagsAll) << "0"; + QTest::newRow("1") << raw("\x01") << int(FlagsAll) << "1"; + QTest::newRow("23") << raw("\x17") << int(FlagsAll) << "23"; + QTest::newRow("24") << raw("\x18\x18") << int(FlagsAll) << "24"; + QTest::newRow("UINT8_MAX") << raw("\x18\xff") << int(FlagsAll) << "255"; + QTest::newRow("UINT16_MAX") << raw("\x19\xff\xff") << int(FlagsAll) << "65535"; + QTest::newRow("UINT32_MAX") << raw("\x1a\xff\xff\xff\xff") << int(FlagsAll) << "4294967295"; + QTest::newRow("UINT64_MAX") << raw("\x1b" "\xff\xff\xff\xff" "\xff\xff\xff\xff") << int(FlagsAll) + << QString::number(std::numeric_limits::max()); + QTest::newRow("-1") << raw("\x20") << int(FlagsAll) << "-1"; + QTest::newRow("-24") << raw("\x37") << int(FlagsAll) << "-24"; + QTest::newRow("-25") << raw("\x38\x18") << int(FlagsAll) << "-25"; + QTest::newRow("INT64_MIN") << raw("\x3b\x7f\xff\xff\xff""\xff\xff\xff\xff") << int(FlagsAll) + << QString::number(std::numeric_limits::min()); + QTest::newRow("INT64_MIN-1") << raw("\x3b\x80\x00\x00\x00""\x00\x00\x00\x00") << int(FlagsAll) + << "-9223372036854775809"; + QTest::newRow("-UINT64_MAX-1") << raw("\x3b\xff\xff\xff\xff""\xff\xff\xff\xff") << int(FlagsAll) + << "-18446744073709551616"; + + // overlong integers - tests CborPrettyIndicateOverlongNumbers + QTest::newRow("0*1-all") << raw("\x18\x00") << int(FlagsAll) << "0_0"; + QTest::newRow("0*1-none") << raw("\x18\x00") << int(FlagsNone) << "0"; + QTest::newRow("0*2-all") << raw("\x19\x00\x00") << int(FlagsAll) << "0_1"; + QTest::newRow("0*2-none") << raw("\x19\x00\x00") << int(FlagsNone) << "0"; + QTest::newRow("0*4-all") << raw("\x1a\0\0\0\0") << int(FlagsAll) << "0_2"; + QTest::newRow("0*4-none") << raw("\x1a\0\0\0\0") << int(FlagsNone) << "0"; + QTest::newRow("0*8-all") << raw("\x1b\0\0\0\0\0\0\0\0") << int(FlagsAll) << "0_3"; + QTest::newRow("0*8-none") << raw("\x1b\0\0\0\0\0\0\0\0") << int(FlagsNone) << "0"; + QTest::newRow("-1*1-all") << raw("\x38\x00") << int(FlagsAll) << "-1_0"; + QTest::newRow("-1*1-none") << raw("\x38\x00") << int(FlagsNone) << "-1"; + QTest::newRow("-1*2-all") << raw("\x39\x00\x00") << int(FlagsAll) << "-1_1"; + QTest::newRow("-1*2-none") << raw("\x39\x00\x00") << int(FlagsNone) << "-1"; + + // simple types and booleans: flags are irrelevant + QTest::newRow("simple0") << raw("\xe0") << int(FlagsAll) << "simple(0)"; + QTest::newRow("simple255") << raw("\xf8\xff") << int(FlagsAll) << "simple(255)"; + QTest::newRow("false") << raw("\xf4") << int(FlagsAll) << "false"; + QTest::newRow("true") << raw("\xf5") << int(FlagsAll) << "true"; + QTest::newRow("null") << raw("\xf6") << int(FlagsAll) << "null"; + QTest::newRow("undefined") << raw("\xf7") << int(FlagsAll) << "undefined"; + + // floating point: flags are irrelevant (no overlong encoding for floats) + QTest::newRow("0.f16") << raw("\xf9\0\0") << int(FlagsAll) << "0.f16"; + QTest::newRow("0.f") << raw("\xfa\0\0\0\0") << int(FlagsAll) << "0.f"; + QTest::newRow("0.") << raw("\xfb\0\0\0\0\0\0\0\0") << int(FlagsAll) << "0."; + QTest::newRow("-1.f16") << raw("\xf9\xbc\x00") << int(FlagsAll) << "-1.f16"; + QTest::newRow("nan") << raw("\xfb\x7f\xf8\0\0\0\0\0\0") << int(FlagsAll) << "nan"; + QTest::newRow("+inf") << raw("\xfb\x7f\xf0\0\0\0\0\0\0") << int(FlagsAll) << "inf"; + + // non-chunked byte/text strings, without overlong length: flags are irrelevant. + QTest::newRow("emptybytestring") << raw("\x40") << int(FlagsAll) << "h''"; + QTest::newRow("bytestring5") << raw("\x45Hello") << int(FlagsAll) << "h'48656c6c6f'"; + QTest::newRow("emptytextstring") << raw("\x60") << int(FlagsAll) << "\"\""; + QTest::newRow("textstring5") << raw("\x65Hello") << int(FlagsAll) << "\"Hello\""; + QTest::newRow("textstring1-nul") << raw("\x61\0") << int(FlagsAll) << "\"\\u0000\""; + + // overlong string lengths + QTest::newRow("emptybytestring*1-all") << raw("\x58\x00") << int(FlagsAll) << "h''_0"; + QTest::newRow("emptybytestring*1-none") << raw("\x58\x00") << int(FlagsNone) << "h''"; + QTest::newRow("emptytextstring*1-all") << raw("\x78\x00") << int(FlagsAll) << "\"\"_0"; + QTest::newRow("emptytextstring*1-none") << raw("\x78\x00") << int(FlagsNone) << "\"\""; + QTest::newRow("bytestring5*2-all") << raw("\x59\0\5Hello") << int(FlagsAll) << "h'48656c6c6f'_1"; + QTest::newRow("bytestring5*2-none") << raw("\x59\0\5Hello") << int(FlagsNone) << "h'48656c6c6f'"; + + // tags: no indeterminate length involved, but overlong tags are present + QTest::newRow("tag0") << raw("\xc0\x00") << int(FlagsAll) << "0(0)"; + QTest::newRow("tag1") << raw("\xc1\x00") << int(FlagsAll) << "1(0)"; + QTest::newRow("tag256") << raw("\xd9\1\0\x00") << int(FlagsAll) << "256(0)"; + QTest::newRow("tag0*1-none") << raw("\xd8\0\x00") << int(FlagsNone) << "0(0)"; + QTest::newRow("tag0*1-all") << raw("\xd8\0\x00") << int(FlagsAll) << "0_0(0)"; + QTest::newRow("tag0*2-all") << raw("\xd9\0\0\x00") << int(FlagsAll) << "0_1(0)"; + QTest::newRow("tag0*3-all") << raw("\xda\0\0\0\0\x00") << int(FlagsAll) << "0_2(0)"; + QTest::newRow("tag0*4-all") << raw("\xdb\0\0\0\0\0\0\0\0\x00") << int(FlagsAll) << "0_3(0)"; + QTest::newRow("unixtime") << raw("\xc1\x1a\x55\x4b\xbf\xd3") << int(FlagsAll) << "1(1431027667)"; + + // definite-length arrays and maps, with determinate length: flags are + // irrelevant + QTest::newRow("emptyarray") << raw("\x80") << int(FlagsAll) << "[]"; + QTest::newRow("emptymap") << raw("\xa0") << int(FlagsAll) << "{}"; + QTest::newRow("array123") << raw("\x83\1\2\3") << int(FlagsAll) << "[1, 2, 3]"; + QTest::newRow("map-0-24") << raw("\xa1\0\x18\x18") << int(FlagsAll) << "{0: 24}"; + QTest::newRow("nestedarray") << raw("\x81\x81\1") << int(FlagsAll) << "[[1]]"; + + // overlong array/map lengths: only safe together with the overlong + // flag; the indeterminate flag does not matter since these are + // definite-length containers. + QTest::newRow("array1*1-all") << raw("\x98\1\1") << int(FlagsAll) << "[_0 1]"; + QTest::newRow("array1*1-none") << raw("\x98\1\1") << int(FlagsNone) << "[1]"; + QTest::newRow("array1*1-overlongonly") << raw("\x98\1\1") << int(FlagsOverlong) << "[_0 1]"; + QTest::newRow("map-0*1-24-all") << raw("\xa1\x18\0\x18\x18") << int(FlagsAll) << "{0_0: 24}"; + QTest::newRow("map-0*1-24-none") << raw("\xa1\x18\0\x18\x18") << int(FlagsNone) << "{0: 24}"; + + // indeterminate-length (chunked) strings + QTest::newRow("_emptybytestring-frag-indet") << raw("\x5f\xff") << int(FlagsFragmentsIndeterminate) << "(_ )"; + QTest::newRow("_emptybytestring-indet") << raw("\x5f\xff") << int(FlagsIndeterminate) << "h''_"; + QTest::newRow("_emptybytestring-none") << raw("\x5f\xff") << int(FlagsNone) << "h''"; + QTest::newRow("_emptybytestring-overlong-only") << raw("\x5f\xff") << int(FlagsOverlong) << "h''"; + QTest::newRow("_emptytextstring-overlong-only") << raw("\x7f\xff") << int(FlagsOverlong) << "\"\""; + QTest::newRow("_bytestring2-frag-indet") << raw("\x5f\x41 \x41z\xff") << int(FlagsFragmentsIndeterminate) + << "(_ h'20', h'7a')"; + QTest::newRow("_bytestring2-indet") << raw("\x5f\x41 \x41z\xff") << int(FlagsIndeterminate) << "h'207a'"; + QTest::newRow("_bytestring2-none") << raw("\x5f\x41 \x41z\xff") << int(FlagsNone) << "h'207a'"; + QTest::newRow("_textstring2-frag-indet") << raw("\x7f\x61 \x61z\xff") << int(FlagsFragmentsIndeterminate) + << "(_ \" \", \"z\")"; + QTest::newRow("_textstring2-indet") << raw("\x7f\x61 \x61z\xff") << int(FlagsIndeterminate) << "\" z\""; + QTest::newRow("_textstring2-none") << raw("\x7f\x61 \x61z\xff") << int(FlagsNone) << "\" z\""; + + // chunks with overlong lengths + QTest::newRow("_bytestring5x2-overlong-indet-frag") + << raw("\x5f\x5b\0\0\0\0\0\0\0\3Hel\x5a\0\0\0\2lo\xff") << int(FlagsAll) + << "(_ h'48656c'_3, h'6c6f'_2)"; + QTest::newRow("_bytestring5x2-overlong-indet") + << raw("\x5f\x5b\0\0\0\0\0\0\0\3Hel\x5a\0\0\0\2lo\xff") << int(FlagsIndeterminateOverlong) + << "h'48656c6c6f'_3"; + QTest::newRow("_bytestring5x2-overlong-only") + << raw("\x5f\x5b\0\0\0\0\0\0\0\3Hel\x5a\0\0\0\2lo\xff") << int(FlagsOverlong) + << "h'48656c6c6f'_3"; + + // indeterminate-length arrays and maps + QTest::newRow("_emptyarray-indet") << raw("\x9f\xff") << int(FlagsIndeterminate) << "[_ ]"; + QTest::newRow("_emptyarray-none") << raw("\x9f\xff") << int(FlagsNone) << "[]"; + QTest::newRow("_emptymap-indet") << raw("\xbf\xff") << int(FlagsIndeterminate) << "{_ }"; + QTest::newRow("_emptymap-none") << raw("\xbf\xff") << int(FlagsNone) << "{}"; + QTest::newRow("_array1-indet") << raw("\x9f\1\xff") << int(FlagsIndeterminate) << "[_ 1]"; + QTest::newRow("_array1-none") << raw("\x9f\1\xff") << int(FlagsNone) << "[1]"; + QTest::newRow("_array1-indet-overlong") << raw("\x9f\1\xff") << int(FlagsIndeterminateOverlong) << "[_ 1]"; + QTest::newRow("_map-0-24-indet") << raw("\xbf\0\x18\x18\xff") << int(FlagsIndeterminate) << "{_ 0: 24}"; + QTest::newRow("_map-0-24-none") << raw("\xbf\0\x18\x18\xff") << int(FlagsNone) << "{0: 24}"; + QTest::newRow("_nestedarray-indet") << raw("\x9f\x9f\1\xff\xff") << int(FlagsIndeterminate) << "[_ [_ 1]]"; + QTest::newRow("_arrayofmap-indet") << raw("\x9f\xa1\1\2\xff") << int(FlagsIndeterminate) << "[_ {1: 2}]"; + QTest::newRow("_emptyarray-overlong-only") << raw("\x9f\xff") << int(FlagsOverlong) << "[]"; + QTest::newRow("_emptymap-overlong-only") << raw("\xbf\xff") << int(FlagsOverlong) << "{}"; +} + +void tst_Pretty::validInput() +{ + QFETCH(QByteArray, data); + QFETCH(int, flags); + QFETCH(QString, expected); + + // make the first element definitely valid so cbor_parse_init() succeeds; + // we want to test cbor_value_to_pretty_stream() + data.prepend(0x81); // 1-element array + + for (int len = 1; len < data.length() - 1; ++len) { + CborParser parser; + CborValue value; + CborError err = cbor_parser_init(reinterpret_cast(data.constData()), len, 0, + &parser, &value); + QVERIFY2(!err, cbor_error_string(err)); + + QString decoded; + err = parseOne(&value, &decoded, flags); + if (err != CborErrorUnexpectedEOF) + qDebug() << "Length is" << len; + QCOMPARE(err, CborErrorUnexpectedEOF); + } +} + +void tst_Pretty::incompleteValidInput() +{ + QFETCH(QByteArray, data); + QFETCH(int, flags); + QFETCH(QString, expected); + + CborParser parser; + CborValue value; + CborError err = cbor_parser_init(reinterpret_cast(data.constData()), data.size(), 0, + &parser, &value); + QVERIFY2(!err, cbor_error_string(err)); + + QString decoded; + err = parseOne(&value, &decoded, flags); + QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\"; decoded stream:\n" + + decoded.toLatin1()); + QCOMPARE(decoded, expected); + +} + +void tst_Pretty::invalidInput_data() +{ + QTest::addColumn("data"); + QTest::addColumn("flags"); + QTest::addColumn("expectedError"); + + auto addRow = [](const char *name, const QByteArray &data, CborError expectedError) { + QTest::addRow("%s:none", name) << data << int(FlagsNone) << expectedError; + QTest::addRow("%s:fragments", name) << data << int(FlagsFragments) << expectedError; + QTest::addRow("%s:indeterminate", name) << data << int(FlagsIndeterminate) << expectedError; + QTest::addRow("%s:overlong", name) << data << int(FlagsOverlong) << expectedError; + QTest::addRow("%s:fragments+indeterminate", name) << data << int(FlagsFragmentsIndeterminate) << expectedError; + QTest::addRow("%s:indeterminate+overlong", name) << data << int(FlagsIndeterminateOverlong) << expectedError; + QTest::addRow("%s:all", name) << data << int(FlagsAll) << expectedError; + }; + + // illegal numbers are future extension points + addRow("illegal-number-in-unsigned-1", raw("\x1c"), CborErrorIllegalNumber); + addRow("illegal-number-in-unsigned-2", raw("\x1d"), CborErrorIllegalNumber); + addRow("illegal-number-in-unsigned-3", raw("\x1e"), CborErrorIllegalNumber); + addRow("illegal-number-in-unsigned-4", raw("\x1f"), CborErrorIllegalNumber); + addRow("illegal-number-in-negative-1", raw("\x3c"), CborErrorIllegalNumber); + addRow("illegal-number-in-negative-2", raw("\x3d"), CborErrorIllegalNumber); + addRow("illegal-number-in-negative-3", raw("\x3e"), CborErrorIllegalNumber); + addRow("illegal-number-in-negative-4", raw("\x3f"), CborErrorIllegalNumber); + addRow("illegal-number-in-bytearray-length-1", raw("\x5c"), CborErrorIllegalNumber); + addRow("illegal-number-in-bytearray-length-2", raw("\x5d"), CborErrorIllegalNumber); + addRow("illegal-number-in-bytearray-length-3", raw("\x5e"), CborErrorIllegalNumber); + addRow("illegal-number-in-string-length-1", raw("\x7c"), CborErrorIllegalNumber); + addRow("illegal-number-in-string-length-2", raw("\x7d"), CborErrorIllegalNumber); + addRow("illegal-number-in-string-length-3", raw("\x7e"), CborErrorIllegalNumber); + addRow("illegal-number-in-array-length-1", raw("\x9c"), CborErrorIllegalNumber); + addRow("illegal-number-in-array-length-2", raw("\x9d"), CborErrorIllegalNumber); + addRow("illegal-number-in-array-length-3", raw("\x9e"), CborErrorIllegalNumber); + addRow("illegal-number-in-map-length-1", raw("\xbc"), CborErrorIllegalNumber); + addRow("illegal-number-in-map-length-2", raw("\xbd"), CborErrorIllegalNumber); + addRow("illegal-number-in-map-length-3", raw("\xbe"), CborErrorIllegalNumber); + addRow("illegal-number-in-tag-1", raw("\xdc"), CborErrorIllegalNumber); + addRow("illegal-number-in-tag-2", raw("\xdd"), CborErrorIllegalNumber); + addRow("illegal-number-in-tag-3", raw("\xde"), CborErrorIllegalNumber); + addRow("illegal-number-in-tag-4", raw("\xdf"), CborErrorIllegalNumber); + // major type 7 has future types + addRow("future-type-28", raw("\xfc"), CborErrorUnknownType); + addRow("future-type-29", raw("\xfd"), CborErrorUnknownType); + addRow("future-type-30", raw("\xfe"), CborErrorUnknownType); + addRow("unexpected-break", raw("\xff"), CborErrorUnexpectedBreak); + // simple types 0-31 must be encoded in a single byte + addRow("illegal-simple-0", raw("\xf8\0"), CborErrorIllegalSimpleType); + addRow("illegal-simple-31", raw("\xf8\x1f"), CborErrorIllegalSimpleType); + + // Break in bad positions + addRow("array-element-is-break", raw("\x81\xff"), CborErrorUnexpectedBreak); + addRow("map-key-is-break", raw("\xa1\xff\0"), CborErrorUnexpectedBreak); + addRow("map-value-is-break", raw("\xa1\0\xff"), CborErrorUnexpectedBreak); + addRow("map-break-after-key", raw("\xbf\0\xff"), CborErrorUnexpectedBreak); + addRow("map-break-after-second-key", raw("\xbf\x64xyzw\x04\x00\xff"), CborErrorUnexpectedBreak); + addRow("map-break-after-value-tag", raw("\xbf\0\xc0\xff"), CborErrorUnexpectedBreak); + addRow("map-break-after-value-tag2", raw("\xbf\0\xd8\x20\xff"), CborErrorUnexpectedBreak); + addRow("tagged-break", raw("\xc0\xff"), CborErrorUnexpectedBreak); + + // check for pointer additions wrapping over the limit of the address space + auto wraparoundError = [](uint64_t encodedSize) { + return encodedSize > SIZE_MAX ? CborErrorDataTooLarge : CborErrorUnexpectedEOF; + }; + constexpr uint64_t FourGB = UINT32_MAX + UINT64_C(1); + // on 32-bit systems, this is a -1 + addRow("bytearray-wraparound1", raw("\x5a\xff\xff\xff\xff"), wraparoundError(UINT32_MAX)); + addRow("string-wraparound1", raw("\x7a\xff\xff\xff\xff"), wraparoundError(UINT32_MAX)); + // on 32-bit systems, a 4GB addition could be dropped + addRow("bytearray-wraparound2", raw("\x5b\0\0\0\1\0\0\0\0"), wraparoundError(FourGB)); + addRow("string-wraparound2", raw("\x7b\0\0\0\1\0\0\0\0"), wraparoundError(FourGB)); + // on 64-bit systems, this could be a -1 + addRow("bytearray-wraparound3", raw("\x5b\xff\xff\xff\xff\xff\xff\xff\xff"), + wraparoundError(UINT64_MAX)); + addRow("string-wraparound3", raw("\x7b\xff\xff\xff\xff\xff\xff\xff\xff"), + wraparoundError(UINT64_MAX)); + + // ditto on chunks + addRow("bytearray-chunk-wraparound1", raw("\x5f\x5a\xff\xff\xff\xff"), wraparoundError(UINT32_MAX)); + addRow("string-chunk-wraparound1", raw("\x7f\x7a\xff\xff\xff\xff"), wraparoundError(UINT32_MAX)); + // on 32-bit systems, a 4GB addition could be dropped + addRow("bytearray-chunk-wraparound2", raw("\x5f\x5b\0\0\0\1\0\0\0\0"), wraparoundError(FourGB)); + addRow("string-chunk-wraparound2", raw("\x7f\x7b\0\0\0\1\0\0\0\0"), wraparoundError(FourGB)); + // on 64-bit systems, this could be a -1 + addRow("bytearray-chunk-wraparound3", raw("\x5f\x5b\xff\xff\xff\xff\xff\xff\xff\xff"), + wraparoundError(UINT64_MAX)); + addRow("string-chunk-wraparound3", raw("\x7f\x7b\xff\xff\xff\xff\xff\xff\xff\xff"), + wraparoundError(UINT64_MAX)); + + // bad types inside chunked strings + addRow("string-chunk-unsigned", raw("\x7f\0\xff"), CborErrorIllegalType); + addRow("string-chunk-negative", raw("\x7f\x20\xff"), CborErrorIllegalType); + addRow("string-chunk-bytearray", raw("\x7f\x40\xff"), CborErrorIllegalType); + addRow("string-chunk-array", raw("\x7f\x80\xff"), CborErrorIllegalType); + addRow("string-chunk-map", raw("\x7f\xa0\xff"), CborErrorIllegalType); + addRow("string-chunk-tag", raw("\x7f\xc0\xff"), CborErrorIllegalType); + addRow("string-chunk-tagged-string", raw("\x7f\xc0\x60\xff"), CborErrorIllegalType); + addRow("string-chunk-simple0", raw("\x7f\xe0\xff"), CborErrorIllegalType); + addRow("string-chunk-false", raw("\x7f\xf4\xff"), CborErrorIllegalType); + addRow("string-chunk-true", raw("\x7f\xf5\xff"), CborErrorIllegalType); + addRow("string-chunk-null", raw("\x7f\xf6\xff"), CborErrorIllegalType); + addRow("string-chunk-undefined", raw("\x7f\xf7\xff"), CborErrorIllegalType); + addRow("bytearray-chunk-string", raw("\x5f\x60\xff"), CborErrorIllegalType); + addRow("bytearray-chunk-tagged-bytearray", raw("\x7f\xc0\x40\xff"), CborErrorIllegalType); + + // Bad UTF-8 + addRow("invalid-utf8-bad-continuation-1char", raw("\x61\x80"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-bad-continuation-2chars-1", raw("\x62\xc2\xc0"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-bad-continuation-2chars-2", raw("\x62\xc3\xdf"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-bad-continuation-2chars-3", raw("\x62\xc7\xf0"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-bad-continuation-3chars-1", raw("\x63\xe0\xa0\xc0"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-bad-continuation-3chars-2", raw("\x63\xe0\xc0\xa0"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-bad-continuation-4chars-1", raw("\x64\xf0\x90\x80\xc0"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-bad-continuation-4chars-2", raw("\x64\xf0\x90\xc0\x80"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-bad-continuation-4chars-3", raw("\x64\xf0\xc0\x80\x80"), CborErrorInvalidUtf8TextString); + // Too short UTF-8 sequences (in an array so there's a byte after that would make it valid UTF-8 if it were part of the string) + addRow("invalid-utf8-too-short-2chars", raw("\x82\x61\xc2\x80"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-too-short-3chars-1", raw("\x82\x61\xe0\x80"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-too-short-3chars-2", raw("\x82\x62\xe0\xa0\x80"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-too-short-4chars-1", raw("\x82\x61\xf0\x80"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-too-short-4chars-2", raw("\x82\x62\xf0\x90\x80"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-too-short-4chars-3", raw("\x82\x63\xf0\x90\x80\x80"), CborErrorInvalidUtf8TextString); + // UTF-16 surrogages encoded in UTF-8 + addRow("invalid-utf8-hi-surrogate", raw("\x63\xed\xa0\x80"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-lo-surrogate", raw("\x63\xed\xb0\x80"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-surrogate-pair", raw("\x66\xed\xa0\x80\xed\xb0\x80"), CborErrorInvalidUtf8TextString); + // Non-Unicode UTF-8 sequences + addRow("invalid-utf8-non-unicode-1", raw("\x64\xf4\x90\x80\x80"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-non-unicode-2", raw("\x65\xf8\x88\x80\x80\x80"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-non-unicode-3", raw("\x66\xfc\x84\x80\x80\x80\x80"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-non-unicode-4", raw("\x66\xfd\xbf\xbf\xbf\xbf\xbf"), CborErrorInvalidUtf8TextString); + // invalid bytes in UTF-8 + addRow("invalid-utf8-fe", raw("\x61\xfe"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-ff", raw("\x61\xff"), CborErrorInvalidUtf8TextString); + // Overlong sequences + addRow("invalid-utf8-overlong-1-2", raw("\x62\xc1\x81"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-overlong-1-3", raw("\x63\xe0\x81\x81"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-overlong-1-4", raw("\x64\xf0\x80\x81\x81"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-overlong-1-5", raw("\x65\xf8\x80\x80\x81\x81"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-overlong-1-6", raw("\x66\xfc\x80\x80\x80\x81\x81"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-overlong-2-3", raw("\x63\xe0\x82\x80"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-overlong-2-4", raw("\x64\xf0\x80\x82\x80"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-overlong-2-5", raw("\x65\xf8\x80\x80\x82\x80"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-overlong-2-6", raw("\x66\xfc\x80\x80\x80\x82\x80"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-overlong-3-4", raw("\x64\xf0\x80\xa0\x80"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-overlong-3-5", raw("\x65\xf8\x80\x80\xa0\x80"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-overlong-3-6", raw("\x66\xfc\x80\x80\x80\xa0\x80"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-overlong-4-5", raw("\x65\xf8\x80\x84\x80\x80"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-overlong-4-6", raw("\x66\xfc\x80\x80\x84\x80\x80"), CborErrorInvalidUtf8TextString); + // valid UTF-8 but split into CBOR chunks + addRow("invalid-utf8-split-u0080", raw("\x7f\x61\xc2\x61\x80\xff"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-split-u00ff", raw("\x7f\x61\xc3\x61\xbf\xff"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-split-u0100", raw("\x7f\x61\xc4\x61\x80\xff"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-split-u07ff", raw("\x7f\x61\xdf\x61\xbf\xff"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-split-u0800", raw("\x7f\x61\xe0\x62\xa0\x80\xff"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-split-u1000", raw("\x7f\x62\xe1\x80\x61\x80\xff"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-split-uffff", raw("\x7f\x61\xef\x62\xbf\xbf\xff"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-split-U00010000", raw("\x7f\x61\xf0\x63\x90\x80\x80\xff"), CborErrorInvalidUtf8TextString); + addRow("invalid-utf8-split-U0010ffff", raw("\x7f\x62\xf4\x8f\x62\xbf\xbf\xff"), CborErrorInvalidUtf8TextString); +} + +void tst_Pretty::invalidInput() +{ + QFETCH(QByteArray, data); + QFETCH(int, flags); + QFETCH(CborError, expectedError); + + // make the first element definitely valid so cbor_parse_init() succeeds; + // we want to test cbor_value_to_pretty_stream() + data.prepend(0x81); // 1-element array + + CborParser parser; + CborValue value; + CborError err = cbor_parser_init(reinterpret_cast(data.constData()), data.size(), 0, + &parser, &value); + QVERIFY2(!err, cbor_error_string(err)); + + QString decoded; + err = parseOne(&value, &decoded, flags); + QCOMPARE(err, expectedError); +} + +void tst_Pretty::recursionLimit_data() +{ + QTest::addColumn("data"); + QTest::addColumn("flags"); + + static const int recursions = CBOR_PARSER_MAX_RECURSIONS + 2; + + QTest::newRow("array") << QByteArray(recursions, '\x81') + '\x20' << int(FlagsAll); + QTest::newRow("_array") << QByteArray(recursions, '\x9f') + '\x20' + QByteArray(recursions, '\xff') + << int(FlagsAll); + + QByteArray data; + for (int i = 0; i < recursions; ++i) + data += "\xa1\x65Hello"; + data += '\2'; + QTest::newRow("map") << data << int(FlagsAll); + + 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") << data << int(FlagsAll); + + QTest::newRow("tag") << QByteArray(recursions, '\xc1') + '\x20' << int(FlagsAll); +} + +void tst_Pretty::recursionLimit() +{ + QFETCH(QByteArray, data); + QFETCH(int, flags); + + CborParser parser; + CborValue value; + CborError err = cbor_parser_init(reinterpret_cast(data.constData()), data.size(), 0, + &parser, &value); + QVERIFY2(!err, cbor_error_string(err)); + + QString decoded; + err = parseOne(&value, &decoded, flags); + QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\"; decoded stream:\n" + + decoded.toLatin1()); + QVERIFY2(decoded.contains(QLatin1String("")), + qPrintable(decoded)); +} + +QTEST_MAIN(tst_Pretty) +#include "tst_pretty.moc"