1/****************************************************************************
2**
3** Copyright (C) 2020 Intel Corporation.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtCore module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include <QtCore/qcborstream.h>
41#include <QtTest>
42
43#include <QtCore/private/qbytearray_p.h>
44
45class tst_QCborStreamReader : public QObject
46{
47 Q_OBJECT
48
49private Q_SLOTS:
50 void initTestCase_data();
51 void basics();
52 void clear_data();
53 void clear();
54 void integers_data();
55 void integers();
56 void fixed_data();
57 void fixed();
58 void strings_data();
59 void strings();
60 void tags_data();
61 void tags() { fixed(); }
62 void emptyContainers_data();
63 void emptyContainers();
64
65 void arrays_data();
66 void arrays();
67 void maps_data() { arrays_data(); }
68 void maps();
69 void undefLengthArrays_data() { arrays_data(); }
70 void undefLengthArrays();
71 void undefLengthMaps_data() { arrays_data(); }
72 void undefLengthMaps();
73
74 void next_data() { arrays_data(); }
75 void next();
76 void validation_data();
77 void validation();
78 void hugeDeviceValidation_data();
79 void hugeDeviceValidation();
80 void recursionLimit_data();
81 void recursionLimit();
82
83 void addData_singleElement_data();
84 void addData_singleElement();
85 void addData_complex_data() { arrays_data(); }
86 void addData_complex();
87
88 void duplicatedData_data() { arrays_data(); }
89 void duplicatedData();
90 void extraData_data() { arrays_data(); }
91 void extraData();
92};
93
94#define FOR_CBOR_TYPE(F) \
95 F(QCborStreamReader::UnsignedInteger) \
96 F(QCborStreamReader::NegativeInteger) \
97 F(QCborStreamReader::ByteArray) \
98 F(QCborStreamReader::String) \
99 F(QCborStreamReader::Array) \
100 F(QCborStreamReader::Map) \
101 F(QCborStreamReader::Tag) \
102 F(QCborStreamReader::SimpleType) \
103 F(QCborStreamReader::Float16) \
104 F(QCborStreamReader::Float) \
105 F(QCborStreamReader::Double) \
106 F(QCborStreamReader::Invalid)
107
108QT_BEGIN_NAMESPACE
109namespace QTest {
110template<> char *toString<QCborStreamReader::Type>(const QCborStreamReader::Type &t)
111{
112 return qstrdup([=]() {
113 switch (t) {
114#define TYPE(t) \
115 case t: return QT_STRINGIFY(t);
116 FOR_CBOR_TYPE(TYPE)
117#undef TYPE
118 }
119 return "<huh?>";
120 }());
121}
122} // namespace QTest
123QT_END_NAMESPACE
124
125// Get the data from TinyCBOR (see src/3rdparty/tinycbor/tests/parser/data.cpp)
126#include "data.cpp"
127
128void tst_QCborStreamReader::initTestCase_data()
129{
130 QTest::addColumn<bool>(name: "useDevice");
131 QTest::newRow(dataTag: "QByteArray") << false;
132 QTest::newRow(dataTag: "QBuffer") << true;
133}
134
135void tst_QCborStreamReader::basics()
136{
137 QFETCH_GLOBAL(bool, useDevice);
138 QBuffer buffer;
139 QCborStreamReader reader;
140
141 if (useDevice) {
142 buffer.open(openMode: QIODevice::ReadOnly);
143 reader.setDevice(&buffer);
144 QVERIFY(reader.device() != nullptr);
145 } else {
146 QCOMPARE(reader.device(), nullptr);
147 }
148
149 QCOMPARE(reader.currentOffset(), 0);
150 QCOMPARE(reader.lastError(), QCborError::EndOfFile);
151
152 QCOMPARE(reader.type(), QCborStreamReader::Invalid);
153 QVERIFY(!reader.isUnsignedInteger());
154 QVERIFY(!reader.isNegativeInteger());
155 QVERIFY(!reader.isByteArray());
156 QVERIFY(!reader.isString());
157 QVERIFY(!reader.isArray());
158 QVERIFY(!reader.isContainer());
159 QVERIFY(!reader.isMap());
160 QVERIFY(!reader.isTag());
161 QVERIFY(!reader.isSimpleType());
162 QVERIFY(!reader.isBool());
163 QVERIFY(!reader.isNull());
164 QVERIFY(!reader.isUndefined());
165 QVERIFY(!reader.isFloat16());
166 QVERIFY(!reader.isFloat());
167 QVERIFY(!reader.isDouble());
168 QVERIFY(!reader.isValid());
169 QVERIFY(reader.isInvalid());
170
171 QCOMPARE(reader.containerDepth(), 0);
172 QCOMPARE(reader.parentContainerType(), QCborStreamReader::Invalid);
173 QVERIFY(!reader.hasNext());
174 QVERIFY(!reader.next());
175 QCOMPARE(reader.lastError(), QCborError::EndOfFile);
176
177 QVERIFY(reader.isLengthKnown()); // well, it's not unknown
178 QCOMPARE(reader.length(), ~0ULL);
179
180 if (useDevice) {
181 reader.reparse();
182 QVERIFY(reader.device() != nullptr);
183 } else {
184 reader.addData(data: QByteArray());
185 QCOMPARE(reader.device(), nullptr);
186 }
187
188 // nothing changes, we added nothing
189 QCOMPARE(reader.currentOffset(), 0);
190 QCOMPARE(reader.lastError(), QCborError::EndOfFile);
191
192 QCOMPARE(reader.type(), QCborStreamReader::Invalid);
193 QVERIFY(!reader.isUnsignedInteger());
194 QVERIFY(!reader.isNegativeInteger());
195 QVERIFY(!reader.isByteArray());
196 QVERIFY(!reader.isString());
197 QVERIFY(!reader.isArray());
198 QVERIFY(!reader.isContainer());
199 QVERIFY(!reader.isMap());
200 QVERIFY(!reader.isTag());
201 QVERIFY(!reader.isSimpleType());
202 QVERIFY(!reader.isBool());
203 QVERIFY(!reader.isNull());
204 QVERIFY(!reader.isUndefined());
205 QVERIFY(!reader.isFloat16());
206 QVERIFY(!reader.isFloat());
207 QVERIFY(!reader.isDouble());
208 QVERIFY(!reader.isValid());
209 QVERIFY(reader.isInvalid());
210
211 QVERIFY(reader.isLengthKnown()); // well, it's not unknown
212 QCOMPARE(reader.length(), ~0ULL);
213
214 QCOMPARE(reader.containerDepth(), 0);
215 QCOMPARE(reader.parentContainerType(), QCborStreamReader::Invalid);
216 QVERIFY(!reader.hasNext());
217 QVERIFY(!reader.next());
218
219 reader.clear();
220 QCOMPARE(reader.device(), nullptr);
221 QCOMPARE(reader.currentOffset(), 0);
222 QCOMPARE(reader.lastError(), QCborError::EndOfFile);
223}
224
225void tst_QCborStreamReader::clear_data()
226{
227 QTest::addColumn<QByteArray>(name: "data");
228 QTest::addColumn<QCborError>(name: "firstError");
229 QTest::addColumn<int>(name: "offsetAfterSkip");
230 QTest::newRow(dataTag: "invalid") << QByteArray(512, '\xff') << QCborError{.c: QCborError::UnexpectedBreak} << 0;
231 QTest::newRow(dataTag: "valid") << QByteArray(512, '\0') << QCborError{.c: QCborError::NoError} << 0;
232 QTest::newRow(dataTag: "skipped") << QByteArray(512, '\0') << QCborError{.c: QCborError::NoError} << 1;
233}
234
235void tst_QCborStreamReader::clear()
236{
237 QFETCH_GLOBAL(bool, useDevice);
238 QFETCH(QByteArray, data);
239 QFETCH(QCborError, firstError);
240 QFETCH(int, offsetAfterSkip);
241
242 QBuffer buffer(&data);
243 QCborStreamReader reader(data);
244 if (useDevice) {
245 buffer.open(openMode: QIODevice::ReadOnly);
246 reader.setDevice(&buffer);
247 }
248 QCOMPARE(reader.isValid(), !firstError);
249 QCOMPARE(reader.currentOffset(), 0);
250 QCOMPARE(reader.lastError(), firstError);
251
252 if (offsetAfterSkip) {
253 reader.next();
254 QVERIFY(!reader.isValid());
255 QCOMPARE(reader.currentOffset(), 1);
256 QCOMPARE(reader.lastError(), QCborError::NoError);
257 }
258
259 reader.clear();
260 QCOMPARE(reader.device(), nullptr);
261 QCOMPARE(reader.currentOffset(), 0);
262 QCOMPARE(reader.lastError(), QCborError::EndOfFile);
263}
264
265void tst_QCborStreamReader::integers_data()
266{
267 addIntegers();
268}
269
270void tst_QCborStreamReader::integers()
271{
272 QFETCH_GLOBAL(bool, useDevice);
273 QFETCH(QByteArray, data);
274 QFETCH(bool, isNegative);
275 QFETCH(quint64, expectedRaw);
276 QFETCH(qint64, expectedValue);
277 QFETCH(bool, inInt64Range);
278 quint64 absolute = (isNegative ? expectedRaw + 1 : expectedRaw);
279
280 QBuffer buffer(&data);
281 QCborStreamReader reader(useDevice ? QByteArray() : data);
282 if (useDevice) {
283 buffer.open(openMode: QIODevice::ReadOnly);
284 reader.setDevice(&buffer);
285 }
286 QVERIFY(reader.isValid());
287 QCOMPARE(reader.lastError(), QCborError::NoError);
288 QVERIFY(reader.isInteger());
289
290 if (inInt64Range)
291 QCOMPARE(reader.toInteger(), expectedValue);
292 if (isNegative)
293 QCOMPARE(quint64(reader.toNegativeInteger()), absolute);
294 else
295 QCOMPARE(reader.toUnsignedInteger(), absolute);
296}
297
298void escapedAppendTo(QString &result, const QByteArray &data)
299{
300 result += "h'" + QString::fromLatin1(str: data.toHex()) + '\'';
301}
302
303void escapedAppendTo(QString &result, const QString &data)
304{
305 result += '"';
306 for (int i = 0; i <= data.size(); i += 245) {
307 // hopefully we won't have a surrogate pair split here
308 QScopedArrayPointer<char> escaped(QTest::toPrettyUnicode(string: data.mid(position: i, n: 245)));
309 QLatin1String s(escaped.data() + 1); // skip opening "
310 s.chop(n: 1); // drop the closing "
311 result += s;
312 }
313 result += '"';
314}
315
316template <typename S, QCborStreamReader::StringResult<S> (QCborStreamReader:: *Decoder)()>
317static QString parseOneString_helper(QCborStreamReader &reader)
318{
319 QString result;
320 bool parens = !reader.isLengthKnown();
321 if (parens)
322 result += '(';
323
324 auto r = (reader.*Decoder)();
325 const char *comma = "";
326 while (r.status == QCborStreamReader::Ok) {
327 result += comma;
328 escapedAppendTo(result, r.data);
329
330 r = (reader.*Decoder)();
331 comma = ", ";
332 }
333
334 if (r.status == QCborStreamReader::Error)
335 return QString();
336
337 if (parens)
338 result += ')';
339 return result;
340}
341
342static QString parseOneByteArray(QCborStreamReader &reader)
343{
344 return parseOneString_helper<QByteArray, &QCborStreamReader::readByteArray>(reader);
345}
346
347static QString parseOneString(QCborStreamReader &reader)
348{
349 return parseOneString_helper<QString, &QCborStreamReader::readString>(reader);
350}
351
352static QString makeNegativeString(QCborNegativeInteger n)
353{
354 return n == QCborNegativeInteger(0) ?
355 QString("-18446744073709551616") :
356 QString("-%1").arg(a: quint64(n));
357}
358
359template <typename T> static inline bool canConvertTo(double v)
360{
361 // The [conv.fpint] (7.10 Floating-integral conversions) section of the
362 // standard says only exact conversions are guaranteed. Converting
363 // integrals to floating-point with loss of precision has implementation-
364 // defined behavior whether the next higher or next lower is returned;
365 // converting FP to integral is UB if it can't be represented.;
366 Q_STATIC_ASSERT(std::numeric_limits<T>::is_integer);
367
368 double supremum = ldexp(1, std::numeric_limits<T>::digits);
369 if (v >= supremum)
370 return false;
371
372 if (v < std::numeric_limits<T>::min()) // either zero or a power of two, so it's exact
373 return false;
374
375 // we're in range
376 return v == floor(x: v);
377}
378
379static QString makeFpString(double v)
380{
381 if (canConvertTo<qint64>(v))
382 return QString::number(qint64(v)) + '.';
383 if (canConvertTo<quint64>(v))
384 return QString::number(quint64(v)) + '.';
385
386 QString s = QString::number(v, f: 'g', prec: std::numeric_limits<double>::digits10 + 2);
387 if (!s.contains(c: '.') && !s.contains(c: 'e') && !qIsInf(d: v) && !qIsNaN(d: v))
388 s += '.';
389 return s;
390}
391
392static QString makeFpString(float v)
393{
394 if (qIsInf(f: v))
395 return v > 0 ? "inf" : "-inf";
396 if (qIsNaN(f: v))
397 return "nan";
398 return makeFpString(v: double(v)) + 'f';
399}
400
401static QString makeFpString(qfloat16 v)
402{
403 if (qIsInf(f: v))
404 return v > 0 ? "inf" : "-inf";
405 if (qIsNaN(f: v))
406 return "nan";
407 return makeFpString(v: double(v)) + "f16";
408}
409
410static QString parseOne(QCborStreamReader &reader)
411{
412 QString result;
413
414 switch (reader.type()) {
415 case QCborStreamReader::UnsignedInteger:
416 result = QString::number(reader.toUnsignedInteger());
417 break;
418 case QCborStreamReader::NegativeInteger:
419 result = makeNegativeString(n: reader.toNegativeInteger());
420 break;
421 case QCborStreamReader::ByteArray:
422 return parseOneByteArray(reader);
423 case QCborStreamReader::String:
424 return parseOneString(reader);
425 case QCborStreamReader::Array:
426 case QCborStreamReader::Map: {
427 const char *delimiters = (reader.isArray() ? "[]" : "{}");
428 result += delimiters[0];
429 reader.enterContainer();
430
431 QLatin1String comma("");
432 while (reader.lastError() == QCborError::NoError && reader.hasNext()) {
433 result += comma + parseOne(reader);
434 comma = QLatin1String(", ");
435
436 if (reader.parentContainerType() == QCborStreamReader::Map
437 && reader.lastError() == QCborError::NoError)
438 result += ": " + parseOne(reader);
439 }
440
441 if (reader.isValid())
442 return QString();
443 if (reader.lastError() != QCborError::NoError)
444 return QString();
445 reader.leaveContainer();
446 result += delimiters[1];
447 return result;
448 }
449 case QCborStreamReader::Tag: {
450 QCborTag tag = reader.toTag();
451 if (!reader.next())
452 return QString();
453 return QString("%1(%2)").arg(a: quint64(tag)).arg(a: parseOne(reader));
454 }
455 case QCborStreamReader::SimpleType:
456 switch (reader.toSimpleType()) {
457 case QCborSimpleType::False:
458 result = QStringLiteral("false");
459 break;
460 case QCborSimpleType::True:
461 result = QStringLiteral("true");
462 break;
463 case QCborSimpleType::Null:
464 result = QStringLiteral("null");
465 break;
466 case QCborSimpleType::Undefined:
467 result = QStringLiteral("undefined");
468 break;
469 default:
470 result = QString("simple(%1)").arg(a: quint8(reader.toSimpleType()));
471 break;
472 }
473 break;
474 case QCborStreamReader::Float16:
475 result = makeFpString(v: reader.toFloat16());
476 break;
477 case QCborStreamReader::Float:
478 result = makeFpString(v: reader.toFloat());
479 break;
480 case QCborStreamReader::Double:
481 result = makeFpString(v: reader.toDouble());
482 break;
483 case QCborStreamReader::Invalid:
484 return QStringLiteral("<invalid>");
485 }
486
487 if (!reader.next())
488 return QString();
489 return result;
490}
491
492static QString parse(QCborStreamReader &reader, const QByteArray &data)
493{
494 qint64 oldPos = 0;
495 if (QIODevice *dev = reader.device())
496 oldPos = dev->pos();
497
498 QString r = parseOne(reader);
499 if (r.isEmpty())
500 return r;
501
502 if (reader.currentOffset() - oldPos != data.size())
503 r = QString("Number of parsed bytes (%1) not expected (%2)")
504 .arg(a: reader.currentOffset()).arg(a: data.size());
505 if (QIODevice *dev = reader.device()) {
506 if (dev->pos() - oldPos != data.size())
507 r = QString("QIODevice not advanced (%1) as expected (%2)")
508 .arg(a: dev->pos()).arg(a: data.size());
509 }
510
511 return r;
512}
513
514bool parseNonRecursive(QString &result, bool &printingStringChunks, QCborStreamReader &reader)
515{
516 while (reader.lastError() == QCborError::NoError) {
517 if (!reader.hasNext()) {
518 if (result.endsWith(s: ", "))
519 result.chop(n: 2);
520 if (reader.containerDepth() == 0)
521 return true;
522 result += reader.parentContainerType() == QCborStreamReader::Map ? "}, " : "], ";
523 reader.leaveContainer();
524 continue;
525 }
526
527 switch (reader.type()) {
528 case QCborStreamReader::UnsignedInteger:
529 result += QString::number(reader.toUnsignedInteger());
530 break;
531 case QCborStreamReader::NegativeInteger:
532 result += makeNegativeString(n: reader.toNegativeInteger());
533 break;
534 case QCborStreamReader::ByteArray:
535 case QCborStreamReader::String: {
536 QCborStreamReader::StringResultCode status;
537 if (!printingStringChunks && !reader.isLengthKnown()) {
538 printingStringChunks = true;
539 result += '(';
540 }
541 if (reader.isByteArray()) {
542 auto r = reader.readByteArray();
543 status = r.status;
544 if (r.status == QCborStreamReader::Ok)
545 escapedAppendTo(result, data: r.data);
546 } else {
547 auto r = reader.readString();
548 status = r.status;
549 if (r.status == QCborStreamReader::Ok)
550 escapedAppendTo(result, data: r.data);
551 }
552
553 if (status == QCborStreamReader::EndOfString) {
554 if (result.endsWith(s: ", "))
555 result.chop(n: 2);
556 if (printingStringChunks)
557 result += ')';
558 result += ", ";
559 printingStringChunks = false;
560 }
561 if (status == QCborStreamReader::Ok && printingStringChunks)
562 result += ", ";
563
564 continue;
565 }
566 case QCborStreamReader::Array:
567 result += '[';
568 reader.enterContainer();
569 continue;
570 case QCborStreamReader::Map:
571 result += '{';
572 reader.enterContainer();
573 continue;
574 case QCborStreamReader::Tag:
575 result += QString("Tag:%1:").arg(a: quint64(reader.toTag()));
576 reader.next();
577 continue; // skip the comma
578 case QCborStreamReader::SimpleType:
579 switch (reader.toSimpleType()) {
580 case QCborSimpleType::False:
581 result += QStringLiteral("false");
582 break;
583 case QCborSimpleType::True:
584 result += QStringLiteral("true");
585 break;
586 case QCborSimpleType::Null:
587 result += QStringLiteral("null");
588 break;
589 case QCborSimpleType::Undefined:
590 result += QStringLiteral("undefined");
591 break;
592 default:
593 result += QString("simple(%1)").arg(a: quint8(reader.toSimpleType()));
594 break;
595 }
596 break;
597 case QCborStreamReader::Float16:
598 result += makeFpString(v: reader.toFloat16());
599 break;
600 case QCborStreamReader::Float:
601 result += makeFpString(v: reader.toFloat());
602 break;
603 case QCborStreamReader::Double:
604 result += makeFpString(v: reader.toDouble());
605 break;
606 case QCborStreamReader::Invalid:
607 break;
608 }
609
610 reader.next();
611 result += ", ";
612 }
613 return false;
614};
615
616
617static QString &removeIndicators(QString &str)
618{
619 // remove any CBOR encoding indicators from the string, since parseOne above
620 // doesn't produce them
621 static QRegularExpression rx("_(\\d+)? ?");
622 return str.replace(re: rx, after: QString());
623}
624
625void tst_QCborStreamReader::fixed_data()
626{
627 addColumns();
628 addFixedData();
629}
630
631void tst_QCborStreamReader::fixed()
632{
633 QFETCH_GLOBAL(bool, useDevice);
634 QFETCH(QByteArray, data);
635 QFETCH(QString, expected);
636 removeIndicators(str&: expected);
637
638 QBuffer buffer(&data);
639 QCborStreamReader reader(useDevice ? QByteArray() : data);
640 if (useDevice) {
641 buffer.open(openMode: QIODevice::ReadOnly);
642 reader.setDevice(&buffer);
643 }
644 QVERIFY(reader.isValid());
645 QCOMPARE(reader.lastError(), QCborError::NoError);
646 QCOMPARE(parse(reader, data), expected);
647
648 // verify that we can re-read
649 reader.reset();
650 QVERIFY(reader.isValid());
651 QCOMPARE(reader.lastError(), QCborError::NoError);
652 QCOMPARE(parse(reader, data), expected);
653}
654
655void tst_QCborStreamReader::strings_data()
656{
657 addColumns();
658 addStringsData();
659}
660
661void tst_QCborStreamReader::strings()
662{
663 fixed();
664 if (QTest::currentTestFailed())
665 return;
666
667 // Extra string checks:
668 // We'll compare the reads using readString() and readByteArray()
669 // (henceforth "control data" because fixed() above tested them) with those
670 // obtained with readStringChunk().
671
672 QFETCH(QByteArray, data);
673 QFETCH(QString, expected);
674 QFETCH_GLOBAL(bool, useDevice);
675 bool isChunked = expected.startsWith(c: '(');
676
677 QBuffer buffer(&data), controlBuffer(&data);
678 QCborStreamReader reader(data), controlReader(data);
679 if (useDevice) {
680 buffer.open(openMode: QIODevice::ReadOnly);
681 controlBuffer.open(openMode: QIODevice::ReadOnly);
682 reader.setDevice(&buffer);
683 controlReader.setDevice(&controlBuffer);
684 }
685 QVERIFY(reader.isString() || reader.isByteArray());
686 QCOMPARE(reader.isLengthKnown(), !isChunked);
687
688 if (!isChunked)
689 QCOMPARE(reader.currentStringChunkSize(), qsizetype(reader.length()));
690
691 int chunks = 0;
692 forever {
693 QCborStreamReader::StringResult<QByteArray> controlData;
694 if (reader.isString()) {
695 auto r = controlReader.readString();
696 controlData.data = r.data.toUtf8();
697 controlData.status = r.status;
698 } else {
699 controlData = controlReader.readByteArray();
700 }
701 QVERIFY(controlData.status != QCborStreamReader::Error);
702
703 for (int i = 0; i < 10; ++i) {
704 // this call must work several times with the same result
705 QCOMPARE(reader.currentStringChunkSize(), controlData.data.size());
706 }
707
708 QByteArray chunk(controlData.data.size(), Qt::Uninitialized);
709 auto r = reader.readStringChunk(ptr: chunk.data(), maxlen: chunk.size());
710 QCOMPARE(r.status, controlData.status);
711 if (r.status == QCborStreamReader::Ok)
712 QCOMPARE(r.data, controlData.data.size());
713 else
714 QCOMPARE(r.data, 0);
715 QCOMPARE(chunk, controlData.data);
716
717 if (r.status == QCborStreamReader::EndOfString)
718 break;
719 ++chunks;
720 }
721
722 if (!isChunked)
723 QCOMPARE(chunks, 1);
724}
725
726void tst_QCborStreamReader::tags_data()
727{
728 addColumns();
729 addTagsData();
730}
731
732void tst_QCborStreamReader::emptyContainers_data()
733{
734 addColumns();
735 addEmptyContainersData();
736}
737
738void tst_QCborStreamReader::emptyContainers()
739{
740 QFETCH_GLOBAL(bool, useDevice);
741 QFETCH(QByteArray, data);
742 QFETCH(QString, expected);
743 removeIndicators(str&: expected);
744
745 QBuffer buffer(&data);
746 QCborStreamReader reader(data);
747 if (useDevice) {
748 buffer.open(openMode: QIODevice::ReadOnly);
749 reader.setDevice(&buffer);
750 }
751 QVERIFY(reader.isValid());
752 QCOMPARE(reader.lastError(), QCborError::NoError);
753 if (reader.isLengthKnown())
754 QCOMPARE(reader.length(), 0U);
755 QCOMPARE(parse(reader, data), expected);
756
757 // verify that we can re-read
758 reader.reset();
759 QVERIFY(reader.isValid());
760 QCOMPARE(reader.lastError(), QCborError::NoError);
761 if (reader.isLengthKnown())
762 QCOMPARE(reader.length(), 0U);
763 QCOMPARE(parse(reader, data), expected);
764}
765
766void tst_QCborStreamReader::arrays_data()
767{
768 addColumns();
769 addFixedData();
770 addStringsData();
771 addTagsData();
772 addEmptyContainersData();
773}
774
775static void checkContainer(int len, const QByteArray &data, const QString &expected)
776{
777 QFETCH_GLOBAL(bool, useDevice);
778
779 QByteArray copy = data;
780 QBuffer buffer(&copy);
781 QCborStreamReader reader(data);
782 if (useDevice) {
783 buffer.open(openMode: QIODevice::ReadOnly);
784 reader.setDevice(&buffer);
785 }
786 QVERIFY(reader.isValid());
787 QCOMPARE(reader.lastError(), QCborError::NoError);
788 if (len >= 0) {
789 QVERIFY(reader.isLengthKnown());
790 QCOMPARE(reader.length(), uint(len));
791 }
792 QCOMPARE(parse(reader, data), expected);
793
794 // verify that we can re-read
795 reader.reset();
796 QVERIFY(reader.isValid());
797 QCOMPARE(reader.lastError(), QCborError::NoError);
798 if (len >= 0) {
799 QVERIFY(reader.isLengthKnown());
800 QCOMPARE(reader.length(), uint(len));
801 }
802 QCOMPARE(parse(reader, data), expected);
803}
804
805void tst_QCborStreamReader::arrays()
806{
807 QFETCH(QByteArray, data);
808 QFETCH(QString, expected);
809 removeIndicators(str&: expected);
810
811 checkContainer(len: 1, data: '\x81' + data, expected: '[' + expected + ']');
812 if (QTest::currentTestFailed())
813 return;
814
815 checkContainer(len: 2, data: '\x82' + data + data, expected: '[' + expected + ", " + expected + ']');
816}
817
818void tst_QCborStreamReader::maps()
819{
820 QFETCH(QByteArray, data);
821 QFETCH(QString, expected);
822 removeIndicators(str&: expected);
823
824 // int keys
825 checkContainer(len: 1, data: "\xa1\1" + data, expected: "{1: " + expected + '}');
826 if (QTest::currentTestFailed())
827 return;
828
829 checkContainer(len: 2, data: "\xa2\1" + data + '\x20' + data,
830 expected: "{1: " + expected + ", -1: " + expected + '}');
831 if (QTest::currentTestFailed())
832 return;
833
834 // string keys
835 checkContainer(len: 1, data: "\xa1\x65Hello" + data, expected: "{\"Hello\": " + expected + '}');
836 if (QTest::currentTestFailed())
837 return;
838
839 checkContainer(len: 2, data: "\xa2\x65World" + data + "\x65Hello" + data,
840 expected: "{\"World\": " + expected + ", \"Hello\": " + expected + '}');
841}
842
843void tst_QCborStreamReader::undefLengthArrays()
844{
845 QFETCH(QByteArray, data);
846 QFETCH(QString, expected);
847 removeIndicators(str&: expected);
848
849 checkContainer(len: -1, data: '\x9f' + data + '\xff', expected: '[' + expected + ']');
850 if (QTest::currentTestFailed())
851 return;
852
853 checkContainer(len: -2, data: '\x9f' + data + data + '\xff', expected: '[' + expected + ", " + expected + ']');
854}
855
856void tst_QCborStreamReader::undefLengthMaps()
857{
858 QFETCH(QByteArray, data);
859 QFETCH(QString, expected);
860 removeIndicators(str&: expected);
861
862 // int keys
863 checkContainer(len: -1, data: "\xbf\1" + data + '\xff', expected: "{1: " + expected + '}');
864 if (QTest::currentTestFailed())
865 return;
866
867 checkContainer(len: -2, data: "\xbf\1" + data + '\x20' + data + '\xff',
868 expected: "{1: " + expected + ", -1: " + expected + '}');
869 if (QTest::currentTestFailed())
870 return;
871
872 // string keys
873 checkContainer(len: -1, data: "\xbf\x65Hello" + data + '\xff', expected: "{\"Hello\": " + expected + '}');
874 if (QTest::currentTestFailed())
875 return;
876
877 checkContainer(len: -2, data: "\xbf\x65World" + data + "\x65Hello" + data + '\xff',
878 expected: "{\"World\": " + expected + ", \"Hello\": " + expected + '}');
879}
880
881void tst_QCborStreamReader::next()
882{
883 QFETCH(QByteArray, data);
884
885 auto doit = [](QByteArray data) {
886 QFETCH_GLOBAL(bool, useDevice);
887
888 QBuffer buffer(&data);
889 QCborStreamReader reader(data);
890 if (useDevice) {
891 buffer.open(openMode: QIODevice::ReadOnly);
892 reader.setDevice(&buffer);
893 }
894 return reader.next();
895 };
896
897 QVERIFY(doit('\x81' + data));
898 QVERIFY(doit('\x82' + data + data));
899 QVERIFY(doit('\x9f' + data + '\xff'));
900 QVERIFY(doit("\x81\x9f" + data + '\xff'));
901 QVERIFY(doit("\x9f\x81" + data + '\xff'));
902
903 QVERIFY(doit("\xa1\1" + data));
904 QVERIFY(doit("\xa2\1" + data + '\x20' + data));
905 QVERIFY(doit("\xbf\1" + data + '\xff'));
906 QVERIFY(doit("\xbf\x9f\1\xff\x9f" + data + "\xff\xff"));
907}
908
909#include "../cborlargedatavalidation.cpp"
910
911void tst_QCborStreamReader::validation_data()
912{
913 // Add QCborStreamReader-specific limitations due to use of QByteArray and
914 // QString, which are allocated by QArrayData::allocate().
915 const qsizetype MaxInvalid = std::numeric_limits<QByteArray::size_type>::max();
916 const qsizetype MinInvalid = MaxByteArraySize + 1;
917
918 addValidationColumns();
919 addValidationData(minInvalid: MinInvalid);
920 addValidationLargeData(minInvalid: MinInvalid, maxInvalid: MaxInvalid);
921}
922
923void tst_QCborStreamReader::validation()
924{
925 QFETCH_GLOBAL(bool, useDevice);
926 QFETCH(QByteArray, data);
927 QFETCH(CborError, expectedError);
928 QCborError error = { .c: QCborError::Code(expectedError) };
929
930 QBuffer buffer(&data);
931 QCborStreamReader reader(data);
932 if (useDevice) {
933 buffer.open(openMode: QIODevice::ReadOnly);
934 reader.setDevice(&buffer);
935 }
936 parse(reader, data);
937 QCOMPARE(reader.lastError(), error);
938
939 // next() should fail
940 reader.reset();
941 QVERIFY(!reader.next());
942 QCOMPARE(reader.lastError(), error);
943}
944
945void tst_QCborStreamReader::hugeDeviceValidation_data()
946{
947 addValidationHugeDevice(byteArrayInvalid: MaxByteArraySize + 1, stringInvalid: MaxStringSize + 1);
948}
949
950void tst_QCborStreamReader::hugeDeviceValidation()
951{
952 QFETCH_GLOBAL(bool, useDevice);
953 if (!useDevice)
954 return;
955
956 QFETCH(QSharedPointer<QIODevice>, device);
957 QFETCH(CborError, expectedError);
958 QCborError error = { .c: QCborError::Code(expectedError) };
959
960 device->open(mode: QIODevice::ReadOnly | QIODevice::Unbuffered);
961 QCborStreamReader reader(device.data());
962
963 QVERIFY(parseOne(reader).isEmpty());
964 QCOMPARE(reader.lastError(), error);
965
966 // next() should fail
967 reader.reset();
968 QVERIFY(!reader.next());
969 QCOMPARE(reader.lastError(), error);
970}
971
972static const int Recursions = 3;
973void tst_QCborStreamReader::recursionLimit_data()
974{
975 static const int recursions = Recursions + 2;
976 QTest::addColumn<QByteArray>(name: "data");
977
978 QTest::newRow(dataTag: "array") << QByteArray(recursions, '\x81') + '\x20';
979 QTest::newRow(dataTag: "_array") << QByteArray(recursions, '\x9f') + '\x20' + QByteArray(recursions, '\xff');
980
981 QByteArray data;
982 for (int i = 0; i < recursions; ++i)
983 data += "\xa1\x65Hello";
984 data += '\2';
985 QTest::newRow(dataTag: "map-recursive-values") << data;
986
987 data.clear();
988 for (int i = 0; i < recursions; ++i)
989 data += "\xbf\x65World";
990 data += '\2';
991 for (int i = 0; i < recursions; ++i)
992 data += "\xff";
993 QTest::newRow(dataTag: "_map-recursive-values") << data;
994
995 data = QByteArray(recursions, '\xa1');
996 data += '\2';
997 for (int i = 0; i < recursions; ++i)
998 data += "\x7f\x64quux\xff";
999 QTest::newRow(dataTag: "map-recursive-keys") << data;
1000
1001 data = QByteArray(recursions, '\xbf');
1002 data += '\2';
1003 for (int i = 0; i < recursions; ++i)
1004 data += "\1\xff";
1005 QTest::newRow(dataTag: "_map-recursive-keys") << data;
1006
1007 data.clear();
1008 for (int i = 0; i < recursions / 2; ++i)
1009 data += "\x81\xa1\1";
1010 data += '\2';
1011 QTest::newRow(dataTag: "mixed") << data;
1012}
1013
1014void tst_QCborStreamReader::recursionLimit()
1015{
1016 QFETCH_GLOBAL(bool, useDevice);
1017 QFETCH(QByteArray, data);
1018
1019 data.prepend(c: '\x81');
1020 QBuffer buffer(&data);
1021 QCborStreamReader reader(data);
1022 if (useDevice) {
1023 buffer.open(openMode: QIODevice::ReadOnly);
1024 reader.setDevice(&buffer);
1025 }
1026
1027 // verify that it works normally:
1028 QVERIFY(reader.enterContainer());
1029 QVERIFY(reader.next());
1030 QVERIFY(!reader.hasNext());
1031
1032 reader.reset();
1033 QVERIFY(reader.enterContainer());
1034 QVERIFY(!reader.next(Recursions));
1035 QCOMPARE(reader.lastError(), QCborError::NestingTooDeep);
1036}
1037
1038void tst_QCborStreamReader::addData_singleElement_data()
1039{
1040 addColumns();
1041 addFixedData();
1042 addNonChunkedStringsData();
1043}
1044
1045void tst_QCborStreamReader::addData_singleElement()
1046{
1047 QFETCH_GLOBAL(bool, useDevice);
1048 QFETCH(QByteArray, data);
1049 QFETCH(QString, expected);
1050 removeIndicators(str&: expected);
1051
1052 QByteArray growing;
1053 QBuffer buffer(&growing);
1054 QCborStreamReader reader;
1055 if (useDevice) {
1056 buffer.open(openMode: QIODevice::ReadOnly);
1057 reader.setDevice(&buffer);
1058 }
1059 for (int i = 0; i < data.size() - 1; ++i) {
1060 // add one byte from the data
1061 if (useDevice) {
1062 growing.append(c: data.at(i));
1063 reader.reparse();
1064 } else {
1065 reader.addData(data: data.constData() + i, len: 1);
1066 }
1067
1068 parse(reader, data);
1069 QCOMPARE(reader.lastError(), QCborError::EndOfFile);
1070 }
1071
1072 // add the last byte
1073 if (useDevice) {
1074 growing.append(a: data.right(len: 1));
1075 reader.reparse();
1076 } else {
1077 reader.addData(data: data.right(len: 1));
1078 }
1079 QCOMPARE(reader.lastError(), QCborError::NoError);
1080 QCOMPARE(parse(reader, data), expected);
1081}
1082
1083void tst_QCborStreamReader::addData_complex()
1084{
1085 QFETCH(QByteArray, data);
1086 QFETCH(QString, expected);
1087 removeIndicators(str&: expected);
1088
1089 // transform tags (parseNonRecursive can't produce the usual form)
1090 expected.replace(re: QRegularExpression(R"/((\d+)\(([^)]+)\))/"), after: "Tag:\\1:\\2");
1091
1092 auto doit = [](const QByteArray &data) {
1093 QFETCH_GLOBAL(bool, useDevice);
1094
1095 // start with one byte
1096 int added = 1;
1097 QByteArray growing = data.left(len: added);
1098 QBuffer buffer(&growing);
1099 QCborStreamReader reader(growing);
1100 if (useDevice) {
1101 buffer.open(openMode: QIODevice::ReadOnly);
1102 reader.setDevice(&buffer);
1103 }
1104
1105 QString result;
1106 bool printingStringChunks = false;
1107 forever {
1108 if (parseNonRecursive(result, printingStringChunks, reader))
1109 return result;
1110 if (reader.lastError() != QCborError::EndOfFile)
1111 return reader.lastError().toString();
1112
1113 while (reader.lastError() == QCborError::EndOfFile) {
1114 // add more data
1115 if (added == data.size())
1116 return QStringLiteral("Couldn't parse even with all data");
1117
1118 if (useDevice) {
1119 growing.append(c: data.at(i: added));
1120 reader.reparse();
1121 } else {
1122 reader.addData(data: data.constData() + added, len: 1);
1123 }
1124 ++added;
1125 }
1126 }
1127 };
1128
1129 // plain:
1130 QCOMPARE(doit(data), expected);
1131
1132 // in an array
1133 QCOMPARE(doit('\x81' + data), '[' + expected + ']');
1134 QCOMPARE(doit('\x82' + data + data), '[' + expected + ", " + expected + ']');
1135
1136 QCOMPARE(doit('\x9f' + data + '\xff'), '[' + expected + ']');
1137 QCOMPARE(doit('\x9f' + data + data + '\xff'), '[' + expected + ", " + expected + ']');
1138
1139 // in a map
1140 QCOMPARE(doit("\xa1\x01" + data), "{1, " + expected + '}');
1141 QCOMPARE(doit("\xa1\x65Hello" + data), "{\"Hello\", " + expected + '}');
1142 QCOMPARE(doit("\xa1\x7f\x65Hello\x65World\xff" + data), "{(\"Hello\", \"World\"), " + expected + '}');
1143 QCOMPARE(doit("\xa2\x01" + data + "\x65Hello" + data),
1144 "{1, " + expected + ", \"Hello\", " + expected + '}');
1145
1146 QCOMPARE(doit("\xbf\x01" + data + '\xff'), "{1, " + expected + '}');
1147
1148 // mixed
1149 QCOMPARE(doit("\xbf\x01\x81" + data + '\xff'), "{1, [" + expected + "]}");
1150 QCOMPARE(doit("\xbf\x01\x82" + data + data + '\xff'),
1151 "{1, [" + expected + ", " + expected + "]}");
1152 QCOMPARE(doit("\xbf\x01\x9f" + data + data + "\xff\xff"),
1153 "{1, [" + expected + ", " + expected + "]}");
1154}
1155
1156void tst_QCborStreamReader::duplicatedData()
1157{
1158 QFETCH_GLOBAL(bool, useDevice);
1159 QFETCH(QByteArray, data);
1160 QFETCH(QString, expected);
1161 removeIndicators(str&: expected);
1162
1163 // double the data up
1164 QByteArray doubledata = data + data;
1165
1166 QBuffer buffer(&doubledata);
1167 QCborStreamReader reader(doubledata);
1168 if (useDevice) {
1169 buffer.open(openMode: QIODevice::ReadOnly);
1170 reader.setDevice(&buffer);
1171 }
1172 QVERIFY(reader.isValid());
1173 QCOMPARE(reader.lastError(), QCborError::NoError);
1174 QCOMPARE(parse(reader, data), expected); // yes, data
1175
1176 QVERIFY(reader.currentOffset() < doubledata.size());
1177 if (useDevice) {
1178 reader.setDevice(&buffer);
1179 QVERIFY(reader.isValid());
1180 QCOMPARE(reader.lastError(), QCborError::NoError);
1181 QCOMPARE(parse(reader, data), expected);
1182 QCOMPARE(buffer.pos(), doubledata.size());
1183 } else {
1184 // there's no reader.setData()
1185 }
1186}
1187
1188void tst_QCborStreamReader::extraData()
1189{
1190 QFETCH_GLOBAL(bool, useDevice);
1191 QFETCH(QByteArray, data);
1192 QFETCH(QString, expected);
1193 removeIndicators(str&: expected);
1194
1195 QByteArray extension(9, '\0');
1196
1197 // stress test everything with extra bytes (just one byte changing;
1198 // TinyCBOR used to have a bug where the next byte got sometimes read)
1199 for (int c = '\0'; c < 0x100; ++c) {
1200 extension[0] = c;
1201 QByteArray extendeddata = data + extension;
1202
1203 QBuffer buffer(&extendeddata);
1204 QCborStreamReader reader(extendeddata);
1205 if (useDevice) {
1206 buffer.open(openMode: QIODevice::ReadOnly);
1207 reader.setDevice(&buffer);
1208 }
1209 QVERIFY(reader.isValid());
1210 QCOMPARE(reader.lastError(), QCborError::NoError);
1211 QCOMPARE(parse(reader, data), expected); // yes, data
1212
1213 // if we were a parser, we could parse the next payload
1214 if (useDevice)
1215 QCOMPARE(buffer.readAll(), extension);
1216 }
1217}
1218
1219
1220QTEST_MAIN(tst_QCborStreamReader)
1221
1222#include "tst_qcborstreamreader.moc"
1223

source code of qtbase/tests/auto/corelib/serialization/qcborstreamreader/tst_qcborstreamreader.cpp