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 <allan.jensen@qt.io>
This commit is contained in:
parent
a298fa3786
commit
2457b1381b
14
examples/corelib/serialization/cbordump/cbordump.pro
Normal file
14
examples/corelib/serialization/cbordump/cbordump.pro
Normal file
@ -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
|
764
examples/corelib/serialization/cbordump/main.cpp
Normal file
764
examples/corelib/serialization/cbordump/main.cpp
Normal file
@ -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 <QCborStreamReader>
|
||||
#include <QCommandLineParser>
|
||||
#include <QCommandLineOption>
|
||||
#include <QCoreApplication>
|
||||
#include <QFile>
|
||||
#include <QLocale>
|
||||
#include <QStack>
|
||||
|
||||
#include <math.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/*
|
||||
* 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<quint8> 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<quint32>::max())
|
||||
normalSize += 8;
|
||||
else if (value > std::numeric_limits<quint16>::max())
|
||||
normalSize += 4;
|
||||
else if (value > std::numeric_limits<quint8>::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<char *>(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 <typename T> 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<T>::is_integer);
|
||||
|
||||
double supremum = ldexp(1, std::numeric_limits<T>::digits);
|
||||
if (v >= supremum)
|
||||
return false;
|
||||
|
||||
if (v < std::numeric_limits<T>::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<qint64>(v))
|
||||
return QString::number(qint64(v)) + ".0" + suffix;
|
||||
if (canConvertTo<quint64>(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<int>::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<const uchar *>(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<int>::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;
|
||||
}
|
25
examples/corelib/serialization/cbordump/tag-transform.xslt
Normal file
25
examples/corelib/serialization/cbordump/tag-transform.xslt
Normal file
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0"?>
|
||||
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:a="http://www.iana.org/assignments" xmlns="http://www.iana.org/assignments" xmlns:_="http://www.iana.org/assignments" xmlns:DEFAULT="http://www.iana.org/assignments" version="1.0">
|
||||
<xsl:output omit-xml-declaration="yes" indent="no" method="text"/>
|
||||
<xsl:template match="/a:registry[@id='cbor-tags']">struct CborTagDescription
|
||||
{
|
||||
QCborTag tag;
|
||||
const char *description; // with space and parentheses
|
||||
};
|
||||
|
||||
// <xsl:value-of select="a:registry/a:title"/>
|
||||
static const CborTagDescription tagDescriptions[] = {
|
||||
// from https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml
|
||||
<xsl:for-each select="a:registry/a:record">
|
||||
<xsl:sort select="a:value" data-type="number"/>
|
||||
<xsl:if test="a:semantics != ''">
|
||||
<xsl:call-template name="row"/>
|
||||
</xsl:if>
|
||||
</xsl:for-each> { QCborTag(-1), nullptr }
|
||||
};
|
||||
</xsl:template>
|
||||
<xsl:template name="row"> { QCborTag(<xsl:value-of select="a:value"/>), " (<xsl:value-of select="a:semantics"/> <xsl:call-template name="xref"/>)" },
|
||||
</xsl:template>
|
||||
<xsl:template name="xref"><xsl:if test="a:xref/@type = 'rfc'"> [<xsl:value-of select="translate(a:xref/@data,'rfc','RFC')"/>]</xsl:if>
|
||||
</xsl:template>
|
||||
</xsl:stylesheet>
|
@ -1,2 +1,4 @@
|
||||
TEMPLATE = subdirs
|
||||
SUBDIRS = savegame
|
||||
SUBDIRS = \
|
||||
cbordump \
|
||||
savegame
|
||||
|
Loading…
x
Reference in New Issue
Block a user