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 | |
45 | class tst_QCborStreamReader : public QObject |
46 | { |
47 | Q_OBJECT |
48 | |
49 | private 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 () { 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 | |
108 | QT_BEGIN_NAMESPACE |
109 | namespace QTest { |
110 | template<> 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 |
123 | QT_END_NAMESPACE |
124 | |
125 | // Get the data from TinyCBOR (see src/3rdparty/tinycbor/tests/parser/data.cpp) |
126 | #include "data.cpp" |
127 | |
128 | void 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 | |
135 | void 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 | |
225 | void 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 | |
235 | void 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 | |
265 | void tst_QCborStreamReader::integers_data() |
266 | { |
267 | addIntegers(); |
268 | } |
269 | |
270 | void 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 | |
298 | void escapedAppendTo(QString &result, const QByteArray &data) |
299 | { |
300 | result += "h'" + QString::fromLatin1(str: data.toHex()) + '\''; |
301 | } |
302 | |
303 | void 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 | |
316 | template <typename S, QCborStreamReader::StringResult<S> (QCborStreamReader:: *Decoder)()> |
317 | static 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 | |
342 | static QString parseOneByteArray(QCborStreamReader &reader) |
343 | { |
344 | return parseOneString_helper<QByteArray, &QCborStreamReader::readByteArray>(reader); |
345 | } |
346 | |
347 | static QString parseOneString(QCborStreamReader &reader) |
348 | { |
349 | return parseOneString_helper<QString, &QCborStreamReader::readString>(reader); |
350 | } |
351 | |
352 | static QString makeNegativeString(QCborNegativeInteger n) |
353 | { |
354 | return n == QCborNegativeInteger(0) ? |
355 | QString("-18446744073709551616" ) : |
356 | QString("-%1" ).arg(a: quint64(n)); |
357 | } |
358 | |
359 | template <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 | |
379 | static 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 | |
392 | static 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 | |
401 | static 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 | |
410 | static 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 | |
492 | static 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 | |
514 | bool 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 | |
617 | static 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 | |
625 | void tst_QCborStreamReader::fixed_data() |
626 | { |
627 | addColumns(); |
628 | addFixedData(); |
629 | } |
630 | |
631 | void 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 | |
655 | void tst_QCborStreamReader::strings_data() |
656 | { |
657 | addColumns(); |
658 | addStringsData(); |
659 | } |
660 | |
661 | void 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 | |
726 | void tst_QCborStreamReader::tags_data() |
727 | { |
728 | addColumns(); |
729 | addTagsData(); |
730 | } |
731 | |
732 | void tst_QCborStreamReader::emptyContainers_data() |
733 | { |
734 | addColumns(); |
735 | addEmptyContainersData(); |
736 | } |
737 | |
738 | void 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 | |
766 | void tst_QCborStreamReader::arrays_data() |
767 | { |
768 | addColumns(); |
769 | addFixedData(); |
770 | addStringsData(); |
771 | addTagsData(); |
772 | addEmptyContainersData(); |
773 | } |
774 | |
775 | static void checkContainer(int len, const QByteArray &data, const QString &expected) |
776 | { |
777 | QFETCH_GLOBAL(bool, useDevice); |
778 | |
779 | QByteArray copy = data; |
780 | QBuffer buffer(©); |
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 | |
805 | void 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 | |
818 | void 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 | |
843 | void 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 | |
856 | void 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 | |
881 | void 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 | |
911 | void 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 | |
923 | void 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 | |
945 | void tst_QCborStreamReader::hugeDeviceValidation_data() |
946 | { |
947 | addValidationHugeDevice(byteArrayInvalid: MaxByteArraySize + 1, stringInvalid: MaxStringSize + 1); |
948 | } |
949 | |
950 | void 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 | |
972 | static const int Recursions = 3; |
973 | void 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 | |
1014 | void 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 | |
1038 | void tst_QCborStreamReader::addData_singleElement_data() |
1039 | { |
1040 | addColumns(); |
1041 | addFixedData(); |
1042 | addNonChunkedStringsData(); |
1043 | } |
1044 | |
1045 | void 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 | |
1083 | void 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 | |
1156 | void 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 | |
1188 | void tst_QCborStreamReader::() |
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 | |
1220 | QTEST_MAIN(tst_QCborStreamReader) |
1221 | |
1222 | #include "tst_qcborstreamreader.moc" |
1223 | |