1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2018 Intel Corporation. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the examples of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:BSD$ |
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 | ** BSD License Usage |
18 | ** Alternatively, you may use this file under the terms of the BSD license |
19 | ** as follows: |
20 | ** |
21 | ** "Redistribution and use in source and binary forms, with or without |
22 | ** modification, are permitted provided that the following conditions are |
23 | ** met: |
24 | ** * Redistributions of source code must retain the above copyright |
25 | ** notice, this list of conditions and the following disclaimer. |
26 | ** * Redistributions in binary form must reproduce the above copyright |
27 | ** notice, this list of conditions and the following disclaimer in |
28 | ** the documentation and/or other materials provided with the |
29 | ** distribution. |
30 | ** * Neither the name of The Qt Company Ltd nor the names of its |
31 | ** contributors may be used to endorse or promote products derived |
32 | ** from this software without specific prior written permission. |
33 | ** |
34 | ** |
35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." |
46 | ** |
47 | ** $QT_END_LICENSE$ |
48 | ** |
49 | ****************************************************************************/ |
50 | |
51 | #include <QCborStreamReader> |
52 | #include <QCommandLineParser> |
53 | #include <QCommandLineOption> |
54 | #include <QCoreApplication> |
55 | #include <QFile> |
56 | #include <QLocale> |
57 | #include <QStack> |
58 | |
59 | #include <locale.h> |
60 | #include <math.h> |
61 | #include <stdarg.h> |
62 | #include <stdio.h> |
63 | |
64 | /* |
65 | * To regenerate: |
66 | * curl -O https://www.iana.org/assignments/cbor-tags/cbor-tags.xml |
67 | * xsltproc tag-transform.xslt cbor-tags.xml |
68 | */ |
69 | |
70 | // GENERATED CODE |
71 | struct CborTagDescription |
72 | { |
73 | QCborTag tag; |
74 | const char *description; // with space and parentheses |
75 | }; |
76 | |
77 | // CBOR Tags |
78 | static const CborTagDescription tagDescriptions[] = { |
79 | // from https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml |
80 | { .tag: QCborTag(0), .description: " (Standard date/time string; see Section 2.4.1 [RFC7049])" }, |
81 | { .tag: QCborTag(1), .description: " (Epoch-based date/time; see Section 2.4.1 [RFC7049])" }, |
82 | { .tag: QCborTag(2), .description: " (Positive bignum; see Section 2.4.2 [RFC7049])" }, |
83 | { .tag: QCborTag(3), .description: " (Negative bignum; see Section 2.4.2 [RFC7049])" }, |
84 | { .tag: QCborTag(4), .description: " (Decimal fraction; see Section 2.4.3 [RFC7049])" }, |
85 | { .tag: QCborTag(5), .description: " (Bigfloat; see Section 2.4.3 [RFC7049])" }, |
86 | { .tag: QCborTag(16), .description: " (COSE Single Recipient Encrypted Data Object [RFC8152])" }, |
87 | { .tag: QCborTag(17), .description: " (COSE Mac w/o Recipients Object [RFC8152])" }, |
88 | { .tag: QCborTag(18), .description: " (COSE Single Signer Data Object [RFC8152])" }, |
89 | { .tag: QCborTag(21), .description: " (Expected conversion to base64url encoding; see Section 2.4.4.2 [RFC7049])" }, |
90 | { .tag: QCborTag(22), .description: " (Expected conversion to base64 encoding; see Section 2.4.4.2 [RFC7049])" }, |
91 | { .tag: QCborTag(23), .description: " (Expected conversion to base16 encoding; see Section 2.4.4.2 [RFC7049])" }, |
92 | { .tag: QCborTag(24), .description: " (Encoded CBOR data item; see Section 2.4.4.1 [RFC7049])" }, |
93 | { .tag: QCborTag(25), .description: " (reference the nth previously seen string)" }, |
94 | { .tag: QCborTag(26), .description: " (Serialised Perl object with classname and constructor arguments)" }, |
95 | { .tag: QCborTag(27), .description: " (Serialised language-independent object with type name and constructor arguments)" }, |
96 | { .tag: QCborTag(28), .description: " (mark value as (potentially) shared)" }, |
97 | { .tag: QCborTag(29), .description: " (reference nth marked value)" }, |
98 | { .tag: QCborTag(30), .description: " (Rational number)" }, |
99 | { .tag: QCborTag(32), .description: " (URI; see Section 2.4.4.3 [RFC7049])" }, |
100 | { .tag: QCborTag(33), .description: " (base64url; see Section 2.4.4.3 [RFC7049])" }, |
101 | { .tag: QCborTag(34), .description: " (base64; see Section 2.4.4.3 [RFC7049])" }, |
102 | { .tag: QCborTag(35), .description: " (Regular expression; see Section 2.4.4.3 [RFC7049])" }, |
103 | { .tag: QCborTag(36), .description: " (MIME message; see Section 2.4.4.3 [RFC7049])" }, |
104 | { .tag: QCborTag(37), .description: " (Binary UUID ( section 4.1.2))" }, |
105 | { .tag: QCborTag(38), .description: " (Language-tagged string)" }, |
106 | { .tag: QCborTag(39), .description: " (Identifier)" }, |
107 | { .tag: QCborTag(61), .description: " (CBOR Web Token (CWT))" }, |
108 | { .tag: QCborTag(96), .description: " (COSE Encrypted Data Object [RFC8152])" }, |
109 | { .tag: QCborTag(97), .description: " (COSE MACed Data Object [RFC8152])" }, |
110 | { .tag: QCborTag(98), .description: " (COSE Signed Data Object [RFC8152])" }, |
111 | { .tag: QCborTag(256), .description: " (mark value as having string references)" }, |
112 | { .tag: QCborTag(257), .description: " (Binary MIME message)" }, |
113 | { .tag: QCborTag(258), .description: " (Mathematical finite set)" }, |
114 | { .tag: QCborTag(260), .description: " (Network Address (IPv4 or IPv6 or MAC Address))" }, |
115 | { .tag: QCborTag(264), .description: " (Decimal fraction with arbitrary exponent)" }, |
116 | { .tag: QCborTag(265), .description: " (Bigfloat with arbitrary exponent)" }, |
117 | { .tag: QCborTag(1001), .description: " (extended time)" }, |
118 | { .tag: QCborTag(1002), .description: " (duration)" }, |
119 | { .tag: QCborTag(1003), .description: " (period)" }, |
120 | { .tag: QCborTag(22098), .description: " (hint that indicates an additional level of indirection)" }, |
121 | { .tag: QCborTag(55799), .description: " (Self-describe CBOR; see Section 2.4.5 [RFC7049])" }, |
122 | { .tag: QCborTag(15309736), .description: " (RAINS Message)" }, |
123 | { .tag: QCborTag(-1), .description: nullptr } |
124 | }; |
125 | // END GENERATED CODE |
126 | |
127 | enum { |
128 | // See RFC 7049 section 2. |
129 | SmallValueBitLength = 5, |
130 | SmallValueMask = (1 << SmallValueBitLength) - 1, /* 0x1f */ |
131 | Value8Bit = 24, |
132 | Value16Bit = 25, |
133 | Value32Bit = 26, |
134 | Value64Bit = 27 |
135 | }; |
136 | |
137 | struct CborDumper |
138 | { |
139 | enum DumpOption { |
140 | ShowCompact = 0x01, |
141 | ShowWidthIndicators = 0x02, |
142 | ShowAnnotated = 0x04 |
143 | }; |
144 | Q_DECLARE_FLAGS(DumpOptions, DumpOption) |
145 | |
146 | CborDumper(QFile *f, DumpOptions opts_); |
147 | QCborError dump(); |
148 | |
149 | private: |
150 | void dumpOne(int nestingLevel); |
151 | void dumpOneDetailed(int nestingLevel); |
152 | |
153 | void printByteArray(const QByteArray &ba); |
154 | void printWidthIndicator(quint64 value, char space = '\0'); |
155 | void printStringWidthIndicator(quint64 value); |
156 | |
157 | QCborStreamReader reader; |
158 | QByteArray data; |
159 | QStack<quint8> byteArrayEncoding; |
160 | qint64 offset = 0; |
161 | DumpOptions opts; |
162 | }; |
163 | Q_DECLARE_OPERATORS_FOR_FLAGS(CborDumper::DumpOptions) |
164 | |
165 | static int cborNumberSize(quint64 value) |
166 | { |
167 | int normalSize = 1; |
168 | if (value > std::numeric_limits<quint32>::max()) |
169 | normalSize += 8; |
170 | else if (value > std::numeric_limits<quint16>::max()) |
171 | normalSize += 4; |
172 | else if (value > std::numeric_limits<quint8>::max()) |
173 | normalSize += 2; |
174 | else if (value >= Value8Bit) |
175 | normalSize += 1; |
176 | return normalSize; |
177 | } |
178 | |
179 | CborDumper::CborDumper(QFile *f, DumpOptions opts_) |
180 | : opts(opts_) |
181 | { |
182 | // try to mmap the file, this is faster |
183 | char *ptr = reinterpret_cast<char *>(f->map(offset: 0, size: f->size(), flags: QFile::MapPrivateOption)); |
184 | if (ptr) { |
185 | // worked |
186 | data = QByteArray::fromRawData(ptr, size: f->size()); |
187 | reader.addData(data); |
188 | } else if ((opts & ShowAnnotated) || f->isSequential()) { |
189 | // details requires full contents, so allocate memory |
190 | data = f->readAll(); |
191 | reader.addData(data); |
192 | } else { |
193 | // just use the QIODevice |
194 | reader.setDevice(f); |
195 | } |
196 | } |
197 | |
198 | QCborError CborDumper::dump() |
199 | { |
200 | byteArrayEncoding << quint8(QCborKnownTags::ExpectedBase16); |
201 | if (!reader.lastError()) { |
202 | if (opts & ShowAnnotated) |
203 | dumpOneDetailed(nestingLevel: 0); |
204 | else |
205 | dumpOne(nestingLevel: 0); |
206 | } |
207 | |
208 | QCborError err = reader.lastError(); |
209 | offset = reader.currentOffset(); |
210 | if (err) { |
211 | fflush(stdout); |
212 | fprintf(stderr, format: "cbordump: decoding failed at %lld: %s\n" , |
213 | offset, qPrintable(err.toString())); |
214 | if (!data.isEmpty()) |
215 | fprintf(stderr, format: " bytes at %lld: %s\n" , offset, |
216 | data.mid(index: offset, len: 9).toHex(separator: ' ').constData()); |
217 | } else { |
218 | if (!opts.testFlag(flag: ShowAnnotated)) |
219 | printf(format: "\n" ); |
220 | if (offset < data.size() || (reader.device() && reader.device()->bytesAvailable())) |
221 | fprintf(stderr, format: "Warning: bytes remaining at the end of the CBOR stream\n" ); |
222 | } |
223 | |
224 | return err; |
225 | } |
226 | |
227 | template <typename T> static inline bool canConvertTo(double v) |
228 | { |
229 | // The [conv.fpint] (7.10 Floating-integral conversions) section of the |
230 | // standard says only exact conversions are guaranteed. Converting |
231 | // integrals to floating-point with loss of precision has implementation- |
232 | // defined behavior whether the next higher or next lower is returned; |
233 | // converting FP to integral is UB if it can't be represented.; |
234 | Q_STATIC_ASSERT(std::numeric_limits<T>::is_integer); |
235 | |
236 | double supremum = ldexp(1, std::numeric_limits<T>::digits); |
237 | if (v >= supremum) |
238 | return false; |
239 | |
240 | if (v < std::numeric_limits<T>::min()) // either zero or a power of two, so it's exact |
241 | return false; |
242 | |
243 | // we're in range |
244 | return v == floor(x: v); |
245 | } |
246 | |
247 | static QString fpToString(double v, const char *suffix) |
248 | { |
249 | if (qIsInf(d: v)) |
250 | return v < 0 ? QStringLiteral("-inf" ) : QStringLiteral("inf" ); |
251 | if (qIsNaN(d: v)) |
252 | return QStringLiteral("nan" ); |
253 | if (canConvertTo<qint64>(v)) |
254 | return QString::number(qint64(v)) + ".0" + suffix; |
255 | if (canConvertTo<quint64>(v)) |
256 | return QString::number(quint64(v)) + ".0" + suffix; |
257 | |
258 | QString s = QString::number(v, f: 'g', prec: QLocale::FloatingPointShortest); |
259 | if (!s.contains(c: '.') && !s.contains(c: 'e')) |
260 | s += '.'; |
261 | s += suffix; |
262 | return s; |
263 | }; |
264 | |
265 | void CborDumper::dumpOne(int nestingLevel) |
266 | { |
267 | QString indent(1, QLatin1Char(' ')); |
268 | QString indented = indent; |
269 | if (!opts.testFlag(flag: ShowCompact)) { |
270 | indent = QLatin1Char('\n') + QString(4 * nestingLevel, QLatin1Char(' ')); |
271 | indented = QLatin1Char('\n') + QString(4 + 4 * nestingLevel, QLatin1Char(' ')); |
272 | } |
273 | |
274 | switch (reader.type()) { |
275 | case QCborStreamReader::UnsignedInteger: { |
276 | quint64 u = reader.toUnsignedInteger(); |
277 | printf(format: "%llu" , u); |
278 | reader.next(); |
279 | printWidthIndicator(value: u); |
280 | return; |
281 | } |
282 | |
283 | case QCborStreamReader::NegativeInteger: { |
284 | quint64 n = quint64(reader.toNegativeInteger()); |
285 | if (n == 0) // -2^64 (wrapped around) |
286 | printf(format: "-18446744073709551616" ); |
287 | else |
288 | printf(format: "-%llu" , n); |
289 | reader.next(); |
290 | printWidthIndicator(value: n); |
291 | return; |
292 | } |
293 | |
294 | case QCborStreamReader::ByteArray: |
295 | case QCborStreamReader::String: { |
296 | bool isLengthKnown = reader.isLengthKnown(); |
297 | if (!isLengthKnown) { |
298 | printf(format: "(_ " ); |
299 | ++offset; |
300 | } |
301 | |
302 | QString comma; |
303 | if (reader.isByteArray()) { |
304 | auto r = reader.readByteArray(); |
305 | while (r.status == QCborStreamReader::Ok) { |
306 | printf(format: "%s" , qPrintable(comma)); |
307 | printByteArray(ba: r.data); |
308 | printStringWidthIndicator(value: r.data.size()); |
309 | |
310 | r = reader.readByteArray(); |
311 | comma = QLatin1Char(',') + indented; |
312 | } |
313 | } else { |
314 | auto r = reader.readString(); |
315 | while (r.status == QCborStreamReader::Ok) { |
316 | printf(format: "%s\"%s\"" , qPrintable(comma), qPrintable(r.data)); |
317 | printStringWidthIndicator(value: r.data.toUtf8().size()); |
318 | |
319 | r = reader.readString(); |
320 | comma = QLatin1Char(',') + indented; |
321 | } |
322 | } |
323 | |
324 | if (!isLengthKnown && !reader.lastError()) |
325 | printf(format: ")" ); |
326 | break; |
327 | } |
328 | |
329 | case QCborStreamReader::Array: |
330 | case QCborStreamReader::Map: { |
331 | const char *delimiters = (reader.isArray() ? "[]" : "{}" ); |
332 | printf(format: "%c" , delimiters[0]); |
333 | |
334 | if (reader.isLengthKnown()) { |
335 | quint64 len = reader.length(); |
336 | reader.enterContainer(); |
337 | printWidthIndicator(value: len, space: ' '); |
338 | } else { |
339 | reader.enterContainer(); |
340 | offset = reader.currentOffset(); |
341 | printf(format: "_ " ); |
342 | } |
343 | |
344 | const char *comma = "" ; |
345 | while (!reader.lastError() && reader.hasNext()) { |
346 | printf(format: "%s%s" , comma, qPrintable(indented)); |
347 | comma = "," ; |
348 | dumpOne(nestingLevel: nestingLevel + 1); |
349 | |
350 | if (reader.parentContainerType() != QCborStreamReader::Map) |
351 | continue; |
352 | if (reader.lastError()) |
353 | break; |
354 | printf(format: ": " ); |
355 | dumpOne(nestingLevel: nestingLevel + 1); |
356 | } |
357 | |
358 | if (!reader.lastError()) { |
359 | reader.leaveContainer(); |
360 | printf(format: "%s%c" , qPrintable(indent), delimiters[1]); |
361 | } |
362 | break; |
363 | } |
364 | |
365 | case QCborStreamReader::Tag: { |
366 | QCborTag tag = reader.toTag(); |
367 | printf(format: "%llu" , quint64(tag)); |
368 | |
369 | if (tag == QCborKnownTags::ExpectedBase16 || tag == QCborKnownTags::ExpectedBase64 |
370 | || tag == QCborKnownTags::ExpectedBase64url) |
371 | byteArrayEncoding.push(t: quint8(tag)); |
372 | |
373 | if (reader.next()) { |
374 | printWidthIndicator(value: quint64(tag)); |
375 | printf(format: "(" ); |
376 | dumpOne(nestingLevel); // same level! |
377 | printf(format: ")" ); |
378 | } |
379 | |
380 | if (tag == QCborKnownTags::ExpectedBase16 || tag == QCborKnownTags::ExpectedBase64 |
381 | || tag == QCborKnownTags::ExpectedBase64url) |
382 | byteArrayEncoding.pop(); |
383 | break; |
384 | } |
385 | |
386 | case QCborStreamReader::SimpleType: |
387 | switch (reader.toSimpleType()) { |
388 | case QCborSimpleType::False: |
389 | printf(format: "false" ); |
390 | break; |
391 | case QCborSimpleType::True: |
392 | printf(format: "true" ); |
393 | break; |
394 | case QCborSimpleType::Null: |
395 | printf(format: "null" ); |
396 | break; |
397 | case QCborSimpleType::Undefined: |
398 | printf(format: "undefined" ); |
399 | break; |
400 | default: |
401 | printf(format: "simple(%u)" , quint8(reader.toSimpleType())); |
402 | break; |
403 | } |
404 | reader.next(); |
405 | break; |
406 | |
407 | case QCborStreamReader::Float16: |
408 | printf(format: "%s" , qPrintable(fpToString(reader.toFloat16(), "f16" ))); |
409 | reader.next(); |
410 | break; |
411 | case QCborStreamReader::Float: |
412 | printf(format: "%s" , qPrintable(fpToString(reader.toFloat(), "f" ))); |
413 | reader.next(); |
414 | break; |
415 | case QCborStreamReader::Double: |
416 | printf(format: "%s" , qPrintable(fpToString(reader.toDouble(), "" ))); |
417 | reader.next(); |
418 | break; |
419 | case QCborStreamReader::Invalid: |
420 | return; |
421 | } |
422 | |
423 | offset = reader.currentOffset(); |
424 | } |
425 | |
426 | void CborDumper::dumpOneDetailed(int nestingLevel) |
427 | { |
428 | auto tagDescription = [](QCborTag tag) { |
429 | for (auto entry : tagDescriptions) { |
430 | if (entry.tag == tag) |
431 | return entry.description; |
432 | if (entry.tag > tag) |
433 | break; |
434 | } |
435 | return "" ; |
436 | }; |
437 | auto printOverlong = [](int actualSize, quint64 value) { |
438 | if (cborNumberSize(value) != actualSize) |
439 | printf(format: " (overlong)" ); |
440 | }; |
441 | auto print = [=](const char *descr, const char *fmt, ...) { |
442 | qint64 prevOffset = offset; |
443 | offset = reader.currentOffset(); |
444 | if (prevOffset == offset) |
445 | return; |
446 | |
447 | QByteArray bytes = data.mid(index: prevOffset, len: offset - prevOffset); |
448 | QByteArray indent(nestingLevel * 2, ' '); |
449 | printf(format: "%-50s # %s " , (indent + bytes.toHex(separator: ' ')).constData(), descr); |
450 | |
451 | va_list va; |
452 | va_start(va, fmt); |
453 | vprintf(format: fmt, arg: va); |
454 | va_end(va); |
455 | |
456 | if (strstr(haystack: fmt, needle: "%ll" )) { |
457 | // Only works because all callers below that use %ll, use it as the |
458 | // first arg |
459 | va_start(va, fmt); |
460 | quint64 value = va_arg(va, quint64); |
461 | va_end(va); |
462 | printOverlong(bytes.size(), value); |
463 | } |
464 | |
465 | puts(s: "" ); |
466 | }; |
467 | |
468 | auto printFp = [=](const char *descr, double d) { |
469 | QString s = fpToString(v: d, suffix: "" ); |
470 | if (s.size() <= 6) |
471 | return print(descr, "%s" , qPrintable(s)); |
472 | return print(descr, "%a" , d); |
473 | }; |
474 | |
475 | auto printString = [=](const char *descr) { |
476 | QByteArray indent(nestingLevel * 2, ' '); |
477 | const char *chunkStr = (reader.isLengthKnown() ? "" : "chunk " ); |
478 | int width = 48 - indent.size(); |
479 | int bytesPerLine = qMax(a: width / 3, b: 5); |
480 | |
481 | qsizetype size = reader.currentStringChunkSize(); |
482 | if (size < 0) |
483 | return; // error |
484 | if (size >= std::numeric_limits<int>::max()) { |
485 | fprintf(stderr, format: "String length too big, %lli\n" , qint64(size)); |
486 | exit(EXIT_FAILURE); |
487 | } |
488 | |
489 | // if asking for the current string chunk changes the offset, then it |
490 | // was chunked |
491 | print(descr, "(indeterminate length)" ); |
492 | |
493 | QByteArray bytes(size, Qt::Uninitialized); |
494 | auto r = reader.readStringChunk(ptr: bytes.data(), maxlen: bytes.size()); |
495 | while (r.status == QCborStreamReader::Ok) { |
496 | // We'll have to decode the length's width directly from CBOR... |
497 | const char *lenstart = data.constData() + offset; |
498 | const char *lenend = lenstart + 1; |
499 | quint8 additionalInformation = (*lenstart & SmallValueMask); |
500 | |
501 | // Decode this number directly from CBOR (see RFC 7049 section 2) |
502 | if (additionalInformation >= Value8Bit) { |
503 | if (additionalInformation == Value8Bit) |
504 | lenend += 1; |
505 | else if (additionalInformation == Value16Bit) |
506 | lenend += 2; |
507 | else if (additionalInformation == Value32Bit) |
508 | lenend += 4; |
509 | else |
510 | lenend += 8; |
511 | } |
512 | |
513 | { |
514 | QByteArray lenbytes = QByteArray::fromRawData(lenstart, size: lenend - lenstart); |
515 | printf(format: "%-50s # %s %slength %llu" , |
516 | (indent + lenbytes.toHex(separator: ' ')).constData(), descr, chunkStr, quint64(size)); |
517 | printOverlong(lenbytes.size(), size); |
518 | puts(s: "" ); |
519 | } |
520 | |
521 | offset = reader.currentOffset(); |
522 | |
523 | for (int i = 0; i < r.data; i += bytesPerLine) { |
524 | QByteArray section = bytes.mid(index: i, len: bytesPerLine); |
525 | printf(format: " %s%s" , indent.constData(), section.toHex(separator: ' ').constData()); |
526 | |
527 | // print the decode |
528 | QByteArray spaces(width > 0 ? width - section.size() * 3 + 1: 0, ' '); |
529 | printf(format: "%s # \"" , spaces.constData()); |
530 | auto ptr = reinterpret_cast<const uchar *>(section.constData()); |
531 | for (int j = 0; j < section.size(); ++j) |
532 | printf(format: "%c" , ptr[j] >= 0x80 || ptr[j] < 0x20 ? '.' : ptr[j]); |
533 | |
534 | puts(s: "\"" ); |
535 | } |
536 | |
537 | // get the next chunk |
538 | size = reader.currentStringChunkSize(); |
539 | if (size < 0) |
540 | return; // error |
541 | if (size >= std::numeric_limits<int>::max()) { |
542 | fprintf(stderr, format: "String length too big, %lli\n" , qint64(size)); |
543 | exit(EXIT_FAILURE); |
544 | } |
545 | bytes.resize(size); |
546 | r = reader.readStringChunk(ptr: bytes.data(), maxlen: bytes.size()); |
547 | } |
548 | }; |
549 | |
550 | if (reader.lastError()) |
551 | return; |
552 | |
553 | switch (reader.type()) { |
554 | case QCborStreamReader::UnsignedInteger: { |
555 | quint64 u = reader.toUnsignedInteger(); |
556 | reader.next(); |
557 | if (u < 65536 || (u % 100000) == 0) |
558 | print("Unsigned integer" , "%llu" , u); |
559 | else |
560 | print("Unsigned integer" , "0x%llx" , u); |
561 | return; |
562 | } |
563 | |
564 | case QCborStreamReader::NegativeInteger: { |
565 | quint64 n = quint64(reader.toNegativeInteger()); |
566 | reader.next(); |
567 | print("Negative integer" , n == 0 ? "-18446744073709551616" : "-%llu" , n); |
568 | return; |
569 | } |
570 | |
571 | case QCborStreamReader::ByteArray: |
572 | case QCborStreamReader::String: { |
573 | bool isLengthKnown = reader.isLengthKnown(); |
574 | const char *descr = (reader.isString() ? "Text string" : "Byte string" ); |
575 | if (!isLengthKnown) |
576 | ++nestingLevel; |
577 | |
578 | printString(descr); |
579 | if (reader.lastError()) |
580 | return; |
581 | |
582 | if (!isLengthKnown) { |
583 | --nestingLevel; |
584 | print("Break" , "" ); |
585 | } |
586 | break; |
587 | } |
588 | |
589 | case QCborStreamReader::Array: |
590 | case QCborStreamReader::Map: { |
591 | const char *descr = (reader.isArray() ? "Array" : "Map" ); |
592 | if (reader.isLengthKnown()) { |
593 | quint64 len = reader.length(); |
594 | reader.enterContainer(); |
595 | print(descr, "length %llu" , len); |
596 | } else { |
597 | reader.enterContainer(); |
598 | print(descr, "(indeterminate length)" ); |
599 | } |
600 | |
601 | while (!reader.lastError() && reader.hasNext()) |
602 | dumpOneDetailed(nestingLevel: nestingLevel + 1); |
603 | |
604 | if (!reader.lastError()) { |
605 | reader.leaveContainer(); |
606 | print("Break" , "" ); |
607 | } |
608 | break; |
609 | } |
610 | |
611 | case QCborStreamReader::Tag: { |
612 | QCborTag tag = reader.toTag(); |
613 | reader.next(); |
614 | print("Tag" , "%llu%s" , quint64(tag), tagDescription(tag)); |
615 | dumpOneDetailed(nestingLevel: nestingLevel + 1); |
616 | break; |
617 | } |
618 | |
619 | case QCborStreamReader::SimpleType: { |
620 | QCborSimpleType st = reader.toSimpleType(); |
621 | reader.next(); |
622 | switch (st) { |
623 | case QCborSimpleType::False: |
624 | print("Simple Type" , "false" ); |
625 | break; |
626 | case QCborSimpleType::True: |
627 | print("Simple Type" , "true" ); |
628 | break; |
629 | case QCborSimpleType::Null: |
630 | print("Simple Type" , "null" ); |
631 | break; |
632 | case QCborSimpleType::Undefined: |
633 | print("Simple Type" , "undefined" ); |
634 | break; |
635 | default: |
636 | print("Simple Type" , "%u" , quint8(st)); |
637 | break; |
638 | } |
639 | break; |
640 | } |
641 | |
642 | case QCborStreamReader::Float16: { |
643 | double d = reader.toFloat16(); |
644 | reader.next(); |
645 | printFp("Float16" , d); |
646 | break; |
647 | } |
648 | case QCborStreamReader::Float: { |
649 | double d = reader.toFloat(); |
650 | reader.next(); |
651 | printFp("Float" , d); |
652 | break; |
653 | } |
654 | case QCborStreamReader::Double: { |
655 | double d = reader.toDouble(); |
656 | reader.next(); |
657 | printFp("Double" , d); |
658 | break; |
659 | } |
660 | case QCborStreamReader::Invalid: |
661 | return; |
662 | } |
663 | |
664 | offset = reader.currentOffset(); |
665 | } |
666 | |
667 | void CborDumper::printByteArray(const QByteArray &ba) |
668 | { |
669 | switch (byteArrayEncoding.top()) { |
670 | default: |
671 | printf(format: "h'%s'" , ba.toHex(separator: ' ').constData()); |
672 | break; |
673 | |
674 | case quint8(QCborKnownTags::ExpectedBase64): |
675 | printf(format: "b64'%s'" , ba.toBase64().constData()); |
676 | break; |
677 | |
678 | case quint8(QCborKnownTags::ExpectedBase64url): |
679 | printf(format: "b64'%s'" , ba.toBase64(options: QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals).constData()); |
680 | break; |
681 | } |
682 | } |
683 | |
684 | void printIndicator(quint64 value, qint64 previousOffset, qint64 offset, char space) |
685 | { |
686 | int normalSize = cborNumberSize(value); |
687 | int actualSize = offset - previousOffset; |
688 | |
689 | if (actualSize != normalSize) { |
690 | Q_ASSERT(actualSize > 1); |
691 | actualSize -= 2; |
692 | printf(format: "_%d" , qPopulationCount(v: uint(actualSize))); |
693 | if (space) |
694 | printf(format: "%c" , space); |
695 | } |
696 | } |
697 | |
698 | void CborDumper::printWidthIndicator(quint64 value, char space) |
699 | { |
700 | qint64 previousOffset = offset; |
701 | offset = reader.currentOffset(); |
702 | if (opts & ShowWidthIndicators) |
703 | printIndicator(value, previousOffset, offset, space); |
704 | } |
705 | |
706 | void CborDumper::printStringWidthIndicator(quint64 value) |
707 | { |
708 | qint64 previousOffset = offset; |
709 | offset = reader.currentOffset(); |
710 | if (opts & ShowWidthIndicators) |
711 | printIndicator(value, previousOffset, offset: offset - uint(value), space: '\0'); |
712 | } |
713 | |
714 | int main(int argc, char *argv[]) |
715 | { |
716 | QCoreApplication app(argc, argv); |
717 | setlocale(LC_ALL, locale: "C" ); |
718 | |
719 | QCommandLineParser parser; |
720 | parser.setApplicationDescription(QStringLiteral("CBOR Dumper tool" )); |
721 | parser.addHelpOption(); |
722 | |
723 | QCommandLineOption compact({QStringLiteral("c" ), QStringLiteral("compact" )}, |
724 | QStringLiteral("Use compact form (no line breaks)" )); |
725 | parser.addOption(commandLineOption: compact); |
726 | |
727 | QCommandLineOption showIndicators({QStringLiteral("i" ), QStringLiteral("indicators" )}, |
728 | QStringLiteral("Show indicators for width of lengths and integrals" )); |
729 | parser.addOption(commandLineOption: showIndicators); |
730 | |
731 | QCommandLineOption verbose({QStringLiteral("a" ), QStringLiteral("annotated" )}, |
732 | QStringLiteral("Show bytes and annotated decoding" )); |
733 | parser.addOption(commandLineOption: verbose); |
734 | |
735 | parser.addPositionalArgument(QStringLiteral("[source]" ), |
736 | QStringLiteral("CBOR file to read from" )); |
737 | |
738 | parser.process(app); |
739 | |
740 | CborDumper::DumpOptions opts; |
741 | if (parser.isSet(option: compact)) |
742 | opts |= CborDumper::ShowCompact; |
743 | if (parser.isSet(option: showIndicators)) |
744 | opts |= CborDumper::ShowWidthIndicators; |
745 | if (parser.isSet(option: verbose)) |
746 | opts |= CborDumper::ShowAnnotated; |
747 | |
748 | QStringList files = parser.positionalArguments(); |
749 | if (files.isEmpty()) |
750 | files << "-" ; |
751 | for (const QString &file : qAsConst(t&: files)) { |
752 | QFile f(file); |
753 | if (file == "-" ? f.open(stdin, ioFlags: QIODevice::ReadOnly) : f.open(flags: QIODevice::ReadOnly)) { |
754 | if (files.size() > 1) |
755 | printf(format: "/ From \"%s\" /\n" , qPrintable(file)); |
756 | |
757 | CborDumper dumper(&f, opts); |
758 | QCborError err = dumper.dump(); |
759 | if (err) |
760 | return EXIT_FAILURE; |
761 | } |
762 | } |
763 | |
764 | return EXIT_SUCCESS; |
765 | } |
766 | |