From 2457b1381bb326228ee377e601b659c4fcedafb2 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Wed, 17 Jan 2018 23:44:07 -0800 Subject: Example: add a simple CBOR dumper tool This is useful as well when trying to figure out what a CBOR file contains or how it may fail to parse. Change-Id: Ic38ec929fc3f4bb795dafffd150ad7c0bd8e9887 Reviewed-by: Allan Sandfeld Jensen --- .../corelib/serialization/cbordump/cbordump.pro | 14 + examples/corelib/serialization/cbordump/main.cpp | 764 +++++++++++++++++++++ .../serialization/cbordump/tag-transform.xslt | 25 + examples/corelib/serialization/serialization.pro | 4 +- 4 files changed, 806 insertions(+), 1 deletion(-) create mode 100644 examples/corelib/serialization/cbordump/cbordump.pro create mode 100644 examples/corelib/serialization/cbordump/main.cpp create mode 100644 examples/corelib/serialization/cbordump/tag-transform.xslt (limited to 'examples') diff --git a/examples/corelib/serialization/cbordump/cbordump.pro b/examples/corelib/serialization/cbordump/cbordump.pro new file mode 100644 index 0000000000..7fb2ef69f0 --- /dev/null +++ b/examples/corelib/serialization/cbordump/cbordump.pro @@ -0,0 +1,14 @@ +QT += core +QT -= gui + +TARGET = cbordump +CONFIG += console +CONFIG -= app_bundle + +TEMPLATE = app + +# install +target.path = $$[QT_INSTALL_EXAMPLES]/corelib/serialization/cbordump +INSTALLS += target + +SOURCES += main.cpp diff --git a/examples/corelib/serialization/cbordump/main.cpp b/examples/corelib/serialization/cbordump/main.cpp new file mode 100644 index 0000000000..4cbd03b98d --- /dev/null +++ b/examples/corelib/serialization/cbordump/main.cpp @@ -0,0 +1,764 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Intel Corporation. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* + * To regenerate: + * curl -O https://www.iana.org/assignments/cbor-tags/cbor-tags.xml + * xsltproc tag-transform.xslt cbor-tags.xml + */ + +// GENERATED CODE +struct CborTagDescription +{ + QCborTag tag; + const char *description; // with space and parentheses +}; + +// CBOR Tags +static const CborTagDescription tagDescriptions[] = { + // from https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml + { QCborTag(0), " (Standard date/time string; see Section 2.4.1 [RFC7049])" }, + { QCborTag(1), " (Epoch-based date/time; see Section 2.4.1 [RFC7049])" }, + { QCborTag(2), " (Positive bignum; see Section 2.4.2 [RFC7049])" }, + { QCborTag(3), " (Negative bignum; see Section 2.4.2 [RFC7049])" }, + { QCborTag(4), " (Decimal fraction; see Section 2.4.3 [RFC7049])" }, + { QCborTag(5), " (Bigfloat; see Section 2.4.3 [RFC7049])" }, + { QCborTag(16), " (COSE Single Recipient Encrypted Data Object [RFC8152])" }, + { QCborTag(17), " (COSE Mac w/o Recipients Object [RFC8152])" }, + { QCborTag(18), " (COSE Single Signer Data Object [RFC8152])" }, + { QCborTag(21), " (Expected conversion to base64url encoding; see Section 2.4.4.2 [RFC7049])" }, + { QCborTag(22), " (Expected conversion to base64 encoding; see Section 2.4.4.2 [RFC7049])" }, + { QCborTag(23), " (Expected conversion to base16 encoding; see Section 2.4.4.2 [RFC7049])" }, + { QCborTag(24), " (Encoded CBOR data item; see Section 2.4.4.1 [RFC7049])" }, + { QCborTag(25), " (reference the nth previously seen string)" }, + { QCborTag(26), " (Serialised Perl object with classname and constructor arguments)" }, + { QCborTag(27), " (Serialised language-independent object with type name and constructor arguments)" }, + { QCborTag(28), " (mark value as (potentially) shared)" }, + { QCborTag(29), " (reference nth marked value)" }, + { QCborTag(30), " (Rational number)" }, + { QCborTag(32), " (URI; see Section 2.4.4.3 [RFC7049])" }, + { QCborTag(33), " (base64url; see Section 2.4.4.3 [RFC7049])" }, + { QCborTag(34), " (base64; see Section 2.4.4.3 [RFC7049])" }, + { QCborTag(35), " (Regular expression; see Section 2.4.4.3 [RFC7049])" }, + { QCborTag(36), " (MIME message; see Section 2.4.4.3 [RFC7049])" }, + { QCborTag(37), " (Binary UUID ( section 4.1.2))" }, + { QCborTag(38), " (Language-tagged string)" }, + { QCborTag(39), " (Identifier)" }, + { QCborTag(61), " (CBOR Web Token (CWT))" }, + { QCborTag(96), " (COSE Encrypted Data Object [RFC8152])" }, + { QCborTag(97), " (COSE MACed Data Object [RFC8152])" }, + { QCborTag(98), " (COSE Signed Data Object [RFC8152])" }, + { QCborTag(256), " (mark value as having string references)" }, + { QCborTag(257), " (Binary MIME message)" }, + { QCborTag(258), " (Mathematical finite set)" }, + { QCborTag(260), " (Network Address (IPv4 or IPv6 or MAC Address))" }, + { QCborTag(264), " (Decimal fraction with arbitrary exponent)" }, + { QCborTag(265), " (Bigfloat with arbitrary exponent)" }, + { QCborTag(1001), " (extended time)" }, + { QCborTag(1002), " (duration)" }, + { QCborTag(1003), " (period)" }, + { QCborTag(22098), " (hint that indicates an additional level of indirection)" }, + { QCborTag(55799), " (Self-describe CBOR; see Section 2.4.5 [RFC7049])" }, + { QCborTag(15309736), " (RAINS Message)" }, + { QCborTag(-1), nullptr } +}; +// END GENERATED CODE + +enum { + // See RFC 7049 section 2. + SmallValueBitLength = 5, + SmallValueMask = (1 << SmallValueBitLength) - 1, /* 0x1f */ + Value8Bit = 24, + Value16Bit = 25, + Value32Bit = 26, + Value64Bit = 27 +}; + +struct CborDumper +{ + enum DumpOption { + ShowCompact = 0x01, + ShowWidthIndicators = 0x02, + ShowAnnotated = 0x04 + }; + Q_DECLARE_FLAGS(DumpOptions, DumpOption) + + CborDumper(QFile *f, DumpOptions opts_); + QCborError dump(); + +private: + void dumpOne(int nestingLevel); + void dumpOneDetailed(int nestingLevel); + + void printByteArray(const QByteArray &ba); + void printWidthIndicator(quint64 value, char space = '\0'); + void printStringWidthIndicator(quint64 value); + + QCborStreamReader reader; + QByteArray data; + QStack byteArrayEncoding; + qint64 offset = 0; + DumpOptions opts; +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(CborDumper::DumpOptions) + +static int cborNumberSize(quint64 value) +{ + int normalSize = 1; + if (value > std::numeric_limits::max()) + normalSize += 8; + else if (value > std::numeric_limits::max()) + normalSize += 4; + else if (value > std::numeric_limits::max()) + normalSize += 2; + else if (value >= Value8Bit) + normalSize += 1; + return normalSize; +} + +CborDumper::CborDumper(QFile *f, DumpOptions opts_) + : opts(opts_) +{ + // try to mmap the file, this is faster + char *ptr = reinterpret_cast(f->map(0, f->size(), QFile::MapPrivateOption)); + if (ptr) { + // worked + data = QByteArray::fromRawData(ptr, f->size()); + reader.addData(data); + } else if ((opts & ShowAnnotated) || f->isSequential()) { + // details requires full contents, so allocate memory + data = f->readAll(); + reader.addData(data); + } else { + // just use the QIODevice + reader.setDevice(f); + } +} + +QCborError CborDumper::dump() +{ + byteArrayEncoding << quint8(QCborKnownTags::ExpectedBase16); + if (!reader.lastError()) { + if (opts & ShowAnnotated) + dumpOneDetailed(0); + else + dumpOne(0); + } + + QCborError err = reader.lastError(); + offset = reader.currentOffset(); + if (err) { + fflush(stdout); + fprintf(stderr, "cbordump: decoding failed at %lld: %s\n", + offset, qPrintable(err.toString())); + if (!data.isEmpty()) + fprintf(stderr, " bytes at %lld: %s\n", offset, + data.mid(offset, 9).toHex(' ').constData()); + } else { + if (!opts.testFlag(ShowAnnotated)) + printf("\n"); + if (offset < data.size() || (reader.device() && reader.device()->bytesAvailable())) + fprintf(stderr, "Warning: bytes remaining at the end of the CBOR stream\n"); + } + + return err; +} + +template static inline bool canConvertTo(double v) +{ + // The [conv.fpint] (7.10 Floating-integral conversions) section of the + // standard says only exact conversions are guaranteed. Converting + // integrals to floating-point with loss of precision has implementation- + // defined behavior whether the next higher or next lower is returned; + // converting FP to integral is UB if it can't be represented.; + Q_STATIC_ASSERT(std::numeric_limits::is_integer); + + double supremum = ldexp(1, std::numeric_limits::digits); + if (v >= supremum) + return false; + + if (v < std::numeric_limits::min()) // either zero or a power of two, so it's exact + return false; + + // we're in range + return v == floor(v); +} + +static QString fpToString(double v, const char *suffix) +{ + if (qIsInf(v)) + return v < 0 ? QStringLiteral("-inf") : QStringLiteral("inf"); + if (qIsNaN(v)) + return QStringLiteral("nan"); + if (canConvertTo(v)) + return QString::number(qint64(v)) + ".0" + suffix; + if (canConvertTo(v)) + return QString::number(quint64(v)) + ".0" + suffix; + + QString s = QString::number(v, 'g', QLocale::FloatingPointShortest); + if (!s.contains('.') && !s.contains('e')) + s += '.'; + s += suffix; + return s; +}; + +void CborDumper::dumpOne(int nestingLevel) +{ + QString indent(1, QLatin1Char(' ')); + QString indented = indent; + if (!opts.testFlag(ShowCompact)) { + indent = QLatin1Char('\n') + QString(4 * nestingLevel, QLatin1Char(' ')); + indented = QLatin1Char('\n') + QString(4 + 4 * nestingLevel, QLatin1Char(' ')); + } + + switch (reader.type()) { + case QCborStreamReader::UnsignedInteger: { + quint64 u = reader.toUnsignedInteger(); + printf("%llu", u); + reader.next(); + printWidthIndicator(u); + return; + } + + case QCborStreamReader::NegativeInteger: { + quint64 n = quint64(reader.toNegativeInteger()); + if (n == 0) // -2^64 (wrapped around) + printf("-18446744073709551616"); + else + printf("-%llu", n); + reader.next(); + printWidthIndicator(n); + return; + } + + case QCborStreamReader::ByteArray: + case QCborStreamReader::String: { + bool isLengthKnown = reader.isLengthKnown(); + if (!isLengthKnown) { + printf("(_ "); + ++offset; + } + + QString comma; + if (reader.isByteArray()) { + auto r = reader.readByteArray(); + while (r.status == QCborStreamReader::Ok) { + printf("%s", qPrintable(comma)); + printByteArray(r.data); + printStringWidthIndicator(r.data.size()); + + r = reader.readByteArray(); + comma = QLatin1Char(',') + indented; + } + } else { + auto r = reader.readString(); + while (r.status == QCborStreamReader::Ok) { + printf("%s\"%s\"", qPrintable(comma), qPrintable(r.data)); + printStringWidthIndicator(r.data.toUtf8().size()); + + r = reader.readString(); + comma = QLatin1Char(',') + indented; + } + } + + if (!isLengthKnown && !reader.lastError()) + printf(")"); + break; + } + + case QCborStreamReader::Array: + case QCborStreamReader::Map: { + const char *delimiters = (reader.isArray() ? "[]" : "{}"); + printf("%c", delimiters[0]); + + if (reader.isLengthKnown()) { + quint64 len = reader.length(); + reader.enterContainer(); + printWidthIndicator(len, ' '); + } else { + reader.enterContainer(); + offset = reader.currentOffset(); + printf("_ "); + } + + const char *comma = ""; + while (!reader.lastError() && reader.hasNext()) { + printf("%s%s", comma, qPrintable(indented)); + comma = ","; + dumpOne(nestingLevel + 1); + + if (reader.parentContainerType() != QCborStreamReader::Map) + continue; + if (reader.lastError()) + break; + printf(": "); + dumpOne(nestingLevel + 1); + } + + if (!reader.lastError()) { + reader.leaveContainer(); + printf("%s%c", qPrintable(indent), delimiters[1]); + } + break; + } + + case QCborStreamReader::Tag: { + QCborTag tag = reader.toTag(); + printf("%llu", quint64(tag)); + + if (tag == QCborKnownTags::ExpectedBase16 || tag == QCborKnownTags::ExpectedBase64 + || tag == QCborKnownTags::ExpectedBase64url) + byteArrayEncoding.push(quint8(tag)); + + if (reader.next()) { + printWidthIndicator(quint64(tag)); + printf("("); + dumpOne(nestingLevel); // same level! + printf(")"); + } + + if (tag == QCborKnownTags::ExpectedBase16 || tag == QCborKnownTags::ExpectedBase64 + || tag == QCborKnownTags::ExpectedBase64url) + byteArrayEncoding.pop(); + break; + } + + case QCborStreamReader::SimpleType: + switch (reader.toSimpleType()) { + case QCborSimpleType::False: + printf("false"); + break; + case QCborSimpleType::True: + printf("true"); + break; + case QCborSimpleType::Null: + printf("null"); + break; + case QCborSimpleType::Undefined: + printf("undefined"); + break; + default: + printf("simple(%u)", quint8(reader.toSimpleType())); + break; + } + reader.next(); + break; + + case QCborStreamReader::Float16: + printf("%s", qPrintable(fpToString(reader.toFloat16(), "f16"))); + reader.next(); + break; + case QCborStreamReader::Float: + printf("%s", qPrintable(fpToString(reader.toFloat(), "f"))); + reader.next(); + break; + case QCborStreamReader::Double: + printf("%s", qPrintable(fpToString(reader.toDouble(), ""))); + reader.next(); + break; + case QCborStreamReader::Invalid: + return; + } + + offset = reader.currentOffset(); +} + +void CborDumper::dumpOneDetailed(int nestingLevel) +{ + auto tagDescription = [](QCborTag tag) { + for (auto entry : tagDescriptions) { + if (entry.tag == tag) + return entry.description; + if (entry.tag > tag) + break; + } + return ""; + }; + auto printOverlong = [](int actualSize, quint64 value) { + if (cborNumberSize(value) != actualSize) + printf(" (overlong)"); + }; + auto print = [=](const char *descr, const char *fmt, ...) { + qint64 prevOffset = offset; + offset = reader.currentOffset(); + if (prevOffset == offset) + return; + + QByteArray bytes = data.mid(prevOffset, offset - prevOffset); + QByteArray indent(nestingLevel * 2, ' '); + printf("%-50s # %s ", (indent + bytes.toHex(' ')).constData(), descr); + + va_list va; + va_start(va, fmt); + vprintf(fmt, va); + va_end(va); + + if (strstr(fmt, "%ll")) { + // Only works because all callers below that use %ll, use it as the + // first arg + va_start(va, fmt); + quint64 value = va_arg(va, quint64); + va_end(va); + printOverlong(bytes.size(), value); + } + + puts(""); + }; + + auto printFp = [=](const char *descr, double d) { + QString s = fpToString(d, ""); + if (s.size() <= 6) + return print(descr, "%s", qPrintable(s)); + return print(descr, "%a", d); + }; + + auto printString = [=](const char *descr) { + QByteArray indent(nestingLevel * 2, ' '); + const char *chunkStr = (reader.isLengthKnown() ? "" : "chunk "); + int width = 48 - indent.size(); + int bytesPerLine = qMax(width / 3, 5); + + qsizetype size = reader.currentStringChunkSize(); + if (size < 0) + return; // error + if (size >= std::numeric_limits::max()) { + fprintf(stderr, "String length too big, %lli\n", qint64(size)); + exit(EXIT_FAILURE); + } + + // if asking for the current string chunk changes the offset, then it + // was chunked + print(descr, "(indeterminate length)"); + + QByteArray bytes(size, Qt::Uninitialized); + auto r = reader.readStringChunk(bytes.data(), bytes.size()); + while (r.status == QCborStreamReader::Ok) { + // We'll have to decode the length's width directly from CBOR... + const char *lenstart = data.constData() + offset; + const char *lenend = lenstart + 1; + quint8 additionalInformation = (*lenstart & SmallValueMask); + + // Decode this number directly from CBOR (see RFC 7049 section 2) + if (additionalInformation >= Value8Bit) { + if (additionalInformation == Value8Bit) + lenend += 1; + else if (additionalInformation == Value16Bit) + lenend += 2; + else if (additionalInformation == Value32Bit) + lenend += 4; + else + lenend += 8; + } + + { + QByteArray lenbytes = QByteArray::fromRawData(lenstart, lenend - lenstart); + printf("%-50s # %s %slength %llu", + (indent + lenbytes.toHex(' ')).constData(), descr, chunkStr, quint64(size)); + printOverlong(lenbytes.size(), size); + puts(""); + } + + offset = reader.currentOffset(); + + for (int i = 0; i < r.data; i += bytesPerLine) { + QByteArray section = bytes.mid(i, bytesPerLine); + printf(" %s%s", indent.constData(), section.toHex(' ').constData()); + + // print the decode + QByteArray spaces(width > 0 ? width - section.size() * 3 + 1: 0, ' '); + printf("%s # \"", spaces.constData()); + auto ptr = reinterpret_cast(section.constData()); + for (int j = 0; j < section.size(); ++j) + printf("%c", ptr[j] >= 0x80 || ptr[j] < 0x20 ? '.' : ptr[j]); + + puts("\""); + } + + // get the next chunk + size = reader.currentStringChunkSize(); + if (size < 0) + return; // error + if (size >= std::numeric_limits::max()) { + fprintf(stderr, "String length too big, %lli\n", qint64(size)); + exit(EXIT_FAILURE); + } + bytes.resize(size); + r = reader.readStringChunk(bytes.data(), bytes.size()); + } + }; + + if (reader.lastError()) + return; + + switch (reader.type()) { + case QCborStreamReader::UnsignedInteger: { + quint64 u = reader.toUnsignedInteger(); + reader.next(); + if (u < 65536 || (u % 100000) == 0) + print("Unsigned integer", "%llu", u); + else + print("Unsigned integer", "0x%llx", u); + return; + } + + case QCborStreamReader::NegativeInteger: { + quint64 n = quint64(reader.toNegativeInteger()); + reader.next(); + print("Negative integer", n == 0 ? "-18446744073709551616" : "-%llu", n); + return; + } + + case QCborStreamReader::ByteArray: + case QCborStreamReader::String: { + bool isLengthKnown = reader.isLengthKnown(); + const char *descr = (reader.isString() ? "Text string" : "Byte string"); + if (!isLengthKnown) + ++nestingLevel; + + printString(descr); + if (reader.lastError()) + return; + + if (!isLengthKnown) { + --nestingLevel; + print("Break", ""); + } + break; + } + + case QCborStreamReader::Array: + case QCborStreamReader::Map: { + const char *descr = (reader.isArray() ? "Array" : "Map"); + if (reader.isLengthKnown()) { + quint64 len = reader.length(); + reader.enterContainer(); + print(descr, "length %llu", len); + } else { + reader.enterContainer(); + print(descr, "(indeterminate length)"); + } + + while (!reader.lastError() && reader.hasNext()) + dumpOneDetailed(nestingLevel + 1); + + if (!reader.lastError()) { + reader.leaveContainer(); + print("Break", ""); + } + break; + } + + case QCborStreamReader::Tag: { + QCborTag tag = reader.toTag(); + reader.next(); + print("Tag", "%llu%s", quint64(tag), tagDescription(tag)); + dumpOneDetailed(nestingLevel + 1); + break; + } + + case QCborStreamReader::SimpleType: { + QCborSimpleType st = reader.toSimpleType(); + reader.next(); + switch (st) { + case QCborSimpleType::False: + print("Simple Type", "false"); + break; + case QCborSimpleType::True: + print("Simple Type", "true"); + break; + case QCborSimpleType::Null: + print("Simple Type", "null"); + break; + case QCborSimpleType::Undefined: + print("Simple Type", "undefined"); + break; + default: + print("Simple Type", "%u", quint8(st)); + break; + } + break; + } + + case QCborStreamReader::Float16: { + double d = reader.toFloat16(); + reader.next(); + printFp("Float16", d); + break; + } + case QCborStreamReader::Float: { + double d = reader.toFloat(); + reader.next(); + printFp("Float", d); + break; + } + case QCborStreamReader::Double: { + double d = reader.toDouble(); + reader.next(); + printFp("Double", d); + break; + } + case QCborStreamReader::Invalid: + return; + } + + offset = reader.currentOffset(); +} + +void CborDumper::printByteArray(const QByteArray &ba) +{ + switch (byteArrayEncoding.top()) { + default: + printf("h'%s'", ba.toHex(' ').constData()); + break; + + case quint8(QCborKnownTags::ExpectedBase64): + printf("b64'%s'", ba.toBase64().constData()); + break; + + case quint8(QCborKnownTags::ExpectedBase64url): + printf("b64'%s'", ba.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals).constData()); + break; + } +} + +void printIndicator(quint64 value, qint64 previousOffset, qint64 offset, char space) +{ + int normalSize = cborNumberSize(value); + int actualSize = offset - previousOffset; + + if (actualSize != normalSize) { + Q_ASSERT(actualSize > 1); + actualSize -= 2; + printf("_%d", qPopulationCount(uint(actualSize))); + if (space) + printf("%c", space); + } +} + +void CborDumper::printWidthIndicator(quint64 value, char space) +{ + qint64 previousOffset = offset; + offset = reader.currentOffset(); + if (opts & ShowWidthIndicators) + printIndicator(value, previousOffset, offset, space); +} + +void CborDumper::printStringWidthIndicator(quint64 value) +{ + qint64 previousOffset = offset; + offset = reader.currentOffset(); + if (opts & ShowWidthIndicators) + printIndicator(value, previousOffset, offset - uint(value), '\0'); +} + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + setlocale(LC_ALL, "C"); + + QCommandLineParser parser; + parser.setApplicationDescription(QStringLiteral("CBOR Dumper tool")); + parser.addHelpOption(); + + QCommandLineOption compact({QStringLiteral("c"), QStringLiteral("compact")}, + QStringLiteral("Use compact form (no line breaks)")); + parser.addOption(compact); + + QCommandLineOption showIndicators({QStringLiteral("i"), QStringLiteral("indicators")}, + QStringLiteral("Show indicators for width of lengths and integrals")); + parser.addOption(showIndicators); + + QCommandLineOption verbose({QStringLiteral("a"), QStringLiteral("annotated")}, + QStringLiteral("Show bytes and annotated decoding")); + parser.addOption(verbose); + + parser.addPositionalArgument(QStringLiteral("[source]"), + QStringLiteral("CBOR file to read from")); + + parser.process(app); + + CborDumper::DumpOptions opts; + if (parser.isSet(compact)) + opts |= CborDumper::ShowCompact; + if (parser.isSet(showIndicators)) + opts |= CborDumper::ShowWidthIndicators; + if (parser.isSet(verbose)) + opts |= CborDumper::ShowAnnotated; + + QStringList files = parser.positionalArguments(); + if (files.isEmpty()) + files << "-"; + for (const QString &file : qAsConst(files)) { + QFile f(file); + if (file == "-" ? f.open(stdin, QIODevice::ReadOnly) : f.open(QIODevice::ReadOnly)) { + if (files.size() > 1) + printf("/ From \"%s\" /\n", qPrintable(file)); + + CborDumper dumper(&f, opts); + QCborError err = dumper.dump(); + if (err) + return EXIT_FAILURE; + } + } + + return EXIT_SUCCESS; +} diff --git a/examples/corelib/serialization/cbordump/tag-transform.xslt b/examples/corelib/serialization/cbordump/tag-transform.xslt new file mode 100644 index 0000000000..3cc1b9b293 --- /dev/null +++ b/examples/corelib/serialization/cbordump/tag-transform.xslt @@ -0,0 +1,25 @@ + + + +struct CborTagDescription +{ + QCborTag tag; + const char *description; // with space and parentheses +}; + +// +static const CborTagDescription tagDescriptions[] = { + // from https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml + + + + + + { QCborTag(-1), nullptr } +}; + + { QCborTag(), " ( )" }, + + [] + + diff --git a/examples/corelib/serialization/serialization.pro b/examples/corelib/serialization/serialization.pro index af4d3e6f0f..34fea0c11a 100644 --- a/examples/corelib/serialization/serialization.pro +++ b/examples/corelib/serialization/serialization.pro @@ -1,2 +1,4 @@ TEMPLATE = subdirs -SUBDIRS = savegame +SUBDIRS = \ + cbordump \ + savegame -- cgit v1.2.3