Long live QCborStreamReader!
This is the counterpart of the previous commit. [ChangeLog][QtCore] Added QCborStreamReader and QCborStreamWriter, classes for low-level reading and writing of CBOR streams. Change-Id: Ia0aac2f09e9245339951ffff13c72e4bffdf4a56 Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>bb10
parent
e3cb38b06a
commit
81cc806e26
File diff suppressed because it is too large
Load Diff
|
|
@ -110,6 +110,146 @@ private:
|
|||
QScopedPointer<QCborStreamWriterPrivate> d;
|
||||
};
|
||||
|
||||
class QCborStreamReaderPrivate;
|
||||
class Q_CORE_EXPORT QCborStreamReader
|
||||
{
|
||||
Q_GADGET
|
||||
public:
|
||||
enum Type : quint8 {
|
||||
UnsignedInteger = 0x00,
|
||||
NegativeInteger = 0x20,
|
||||
ByteString = 0x40,
|
||||
ByteArray = ByteString,
|
||||
TextString = 0x60,
|
||||
String = TextString,
|
||||
Array = 0x80,
|
||||
Map = 0xa0,
|
||||
Tag = 0xc0,
|
||||
SimpleType = 0xe0,
|
||||
HalfFloat = 0xf9,
|
||||
Float16 = HalfFloat,
|
||||
Float = 0xfa,
|
||||
Double = 0xfb,
|
||||
|
||||
Invalid = 0xff
|
||||
};
|
||||
Q_ENUM(Type)
|
||||
|
||||
enum StringResultCode {
|
||||
EndOfString = 0,
|
||||
Ok = 1,
|
||||
Error = -1
|
||||
};
|
||||
template <typename Container> struct StringResult {
|
||||
Container data;
|
||||
StringResultCode status = Error;
|
||||
};
|
||||
Q_ENUM(StringResultCode)
|
||||
|
||||
QCborStreamReader();
|
||||
QCborStreamReader(const char *data, qsizetype len);
|
||||
QCborStreamReader(const quint8 *data, qsizetype len);
|
||||
explicit QCborStreamReader(const QByteArray &data);
|
||||
explicit QCborStreamReader(QIODevice *device);
|
||||
~QCborStreamReader();
|
||||
Q_DISABLE_COPY(QCborStreamReader)
|
||||
|
||||
void setDevice(QIODevice *device);
|
||||
QIODevice *device() const;
|
||||
void addData(const QByteArray &data);
|
||||
void addData(const char *data, qsizetype len);
|
||||
void addData(const quint8 *data, qsizetype len)
|
||||
{ addData(reinterpret_cast<const char *>(data), len); }
|
||||
void reparse();
|
||||
void clear();
|
||||
void reset();
|
||||
|
||||
QCborError lastError();
|
||||
|
||||
qint64 currentOffset() const;
|
||||
|
||||
bool isValid() const { return !isInvalid(); }
|
||||
|
||||
int containerDepth() const;
|
||||
QCborStreamReader::Type parentContainerType() const;
|
||||
bool hasNext() const Q_DECL_NOTHROW Q_DECL_PURE_FUNCTION;
|
||||
bool next(int maxRecursion = 10000);
|
||||
|
||||
Type type() const { return QCborStreamReader::Type(type_); }
|
||||
bool isUnsignedInteger() const { return type() == UnsignedInteger; }
|
||||
bool isNegativeInteger() const { return type() == NegativeInteger; }
|
||||
bool isInteger() const { return quint8(type()) <= quint8(NegativeInteger); }
|
||||
bool isByteArray() const { return type() == ByteArray; }
|
||||
bool isString() const { return type() == String; }
|
||||
bool isArray() const { return type() == Array; }
|
||||
bool isMap() const { return type() == Map; }
|
||||
bool isTag() const { return type() == Tag; }
|
||||
bool isSimpleType() const { return type() == SimpleType; }
|
||||
bool isFloat16() const { return type() == Float16; }
|
||||
bool isFloat() const { return type() == Float; }
|
||||
bool isDouble() const { return type() == Double; }
|
||||
bool isInvalid() const { return type() == Invalid; }
|
||||
|
||||
bool isSimpleType(QCborSimpleType st) const { return isSimpleType() && toSimpleType() == st; }
|
||||
bool isFalse() const { return isSimpleType(QCborSimpleType::False); }
|
||||
bool isTrue() const { return isSimpleType(QCborSimpleType::True); }
|
||||
bool isBool() const { return isFalse() || isTrue(); }
|
||||
bool isNull() const { return isSimpleType(QCborSimpleType::Null); }
|
||||
bool isUndefined() const { return isSimpleType(QCborSimpleType::Undefined); }
|
||||
|
||||
bool isLengthKnown() const Q_DECL_NOTHROW Q_DECL_PURE_FUNCTION;
|
||||
quint64 length() const;
|
||||
|
||||
bool isContainer() const { return isMap() || isArray(); }
|
||||
bool enterContainer() { Q_ASSERT(isContainer()); return _enterContainer_helper(); }
|
||||
bool leaveContainer();
|
||||
|
||||
StringResult<QString> readString() { Q_ASSERT(isString()); return _readString_helper(); }
|
||||
StringResult<QByteArray> readByteArray(){ Q_ASSERT(isByteArray()); return _readByteArray_helper(); }
|
||||
qsizetype currentStringChunkSize() const{ Q_ASSERT(isString() || isByteArray()); return _currentStringChunkSize(); }
|
||||
StringResult<qsizetype> readStringChunk(char *ptr, qsizetype maxlen);
|
||||
|
||||
bool toBool() const { Q_ASSERT(isBool()); return value64 - int(QCborSimpleType::False); }
|
||||
QCborTag toTag() const { Q_ASSERT(isTag()); return QCborTag(value64); }
|
||||
quint64 toUnsignedInteger() const { Q_ASSERT(isUnsignedInteger()); return value64; }
|
||||
QCborNegativeInteger toNegativeInteger() const { Q_ASSERT(isNegativeInteger()); return QCborNegativeInteger(value64 + 1); }
|
||||
QCborSimpleType toSimpleType() const{ Q_ASSERT(isSimpleType()); return QCborSimpleType(value64); }
|
||||
qfloat16 toFloat16() const { Q_ASSERT(isFloat16()); return _toFloatingPoint<qfloat16>(); }
|
||||
float toFloat() const { Q_ASSERT(isFloat()); return _toFloatingPoint<float>(); }
|
||||
double toDouble() const { Q_ASSERT(isDouble()); return _toFloatingPoint<double>(); }
|
||||
|
||||
qint64 toInteger() const
|
||||
{
|
||||
Q_ASSERT(isInteger());
|
||||
qint64 v = qint64(value64);
|
||||
if (isNegativeInteger())
|
||||
return -v - 1;
|
||||
return v;
|
||||
}
|
||||
|
||||
private:
|
||||
void preparse();
|
||||
bool _enterContainer_helper();
|
||||
StringResult<QString> _readString_helper();
|
||||
StringResult<QByteArray> _readByteArray_helper();
|
||||
qsizetype _currentStringChunkSize() const;
|
||||
|
||||
template <typename FP> FP _toFloatingPoint() const Q_DECL_NOTHROW
|
||||
{
|
||||
using UInt = typename QIntegerForSizeof<FP>::Unsigned;
|
||||
UInt u = UInt(value64);
|
||||
FP f;
|
||||
memcpy(static_cast<void *>(&f), &u, sizeof(f));
|
||||
return f;
|
||||
}
|
||||
|
||||
friend QCborStreamReaderPrivate;
|
||||
quint64 value64;
|
||||
QScopedPointer<QCborStreamReaderPrivate> d;
|
||||
quint8 type_;
|
||||
quint8 reserved[3] = {};
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // QCBORSTREAM_H
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
QT = core testlib
|
||||
TARGET = tst_qcborstreamreader
|
||||
CONFIG += testcase
|
||||
SOURCES += \
|
||||
tst_qcborstreamreader.cpp
|
||||
|
||||
INCLUDEPATH += \
|
||||
../../../../../src/3rdparty/tinycbor/src \
|
||||
../../../../../src/3rdparty/tinycbor/tests/parser
|
||||
|
||||
DEFINES += SRCDIR=\\\"$$PWD/\\\"
|
||||
|
|
@ -0,0 +1,935 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2018 Intel Corporation.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** 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.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 3 requirements
|
||||
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 2.0 or (at your option) the GNU General
|
||||
** Public license version 3 or any later version approved by the KDE Free
|
||||
** Qt Foundation. The licenses are as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
||||
** https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include <QtCore/qcborstream.h>
|
||||
#include <QtTest>
|
||||
|
||||
class tst_QCborStreamReader : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private Q_SLOTS:
|
||||
void basics();
|
||||
void integers_data();
|
||||
void integers();
|
||||
void fixed_data();
|
||||
void fixed();
|
||||
void strings_data();
|
||||
void strings();
|
||||
void tags_data();
|
||||
void tags() { fixed(); }
|
||||
void emptyContainers_data();
|
||||
void emptyContainers();
|
||||
|
||||
void arrays_data();
|
||||
void arrays();
|
||||
void maps_data() { arrays_data(); }
|
||||
void maps();
|
||||
void undefLengthArrays_data() { arrays_data(); }
|
||||
void undefLengthArrays();
|
||||
void undefLengthMaps_data() { arrays_data(); }
|
||||
void undefLengthMaps();
|
||||
|
||||
void next_data() { arrays_data(); }
|
||||
void next();
|
||||
void validation_data();
|
||||
void validation();
|
||||
void recursionLimit_data();
|
||||
void recursionLimit();
|
||||
|
||||
void addData_singleElement_data();
|
||||
void addData_singleElement();
|
||||
void addData_complex_data() { arrays_data(); }
|
||||
void addData_complex();
|
||||
};
|
||||
|
||||
#define FOR_CBOR_TYPE(F) \
|
||||
F(QCborStreamReader::UnsignedInteger) \
|
||||
F(QCborStreamReader::NegativeInteger) \
|
||||
F(QCborStreamReader::ByteArray) \
|
||||
F(QCborStreamReader::String) \
|
||||
F(QCborStreamReader::Array) \
|
||||
F(QCborStreamReader::Map) \
|
||||
F(QCborStreamReader::Tag) \
|
||||
F(QCborStreamReader::SimpleType) \
|
||||
F(QCborStreamReader::Float16) \
|
||||
F(QCborStreamReader::Float) \
|
||||
F(QCborStreamReader::Double) \
|
||||
F(QCborStreamReader::Invalid)
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
namespace QTest {
|
||||
template<> char *toString<QCborStreamReader::Type>(const QCborStreamReader::Type &t)
|
||||
{
|
||||
return qstrdup([=]() {
|
||||
switch (t) {
|
||||
#define TYPE(t) \
|
||||
case t: return QT_STRINGIFY(t);
|
||||
FOR_CBOR_TYPE(TYPE)
|
||||
#undef TYPE
|
||||
}
|
||||
return "<huh?>";
|
||||
}());
|
||||
}
|
||||
} // namespace QTest
|
||||
QT_END_NAMESPACE
|
||||
|
||||
// Get the data from TinyCBOR (see src/3rdparty/tinycbor/tests/parser/data.cpp)
|
||||
#include "data.cpp"
|
||||
|
||||
void tst_QCborStreamReader::basics()
|
||||
{
|
||||
QCborStreamReader reader;
|
||||
|
||||
QCOMPARE(reader.device(), nullptr);
|
||||
QCOMPARE(reader.currentOffset(), 0);
|
||||
QCOMPARE(reader.lastError(), QCborError::EndOfFile);
|
||||
|
||||
QCOMPARE(reader.type(), QCborStreamReader::Invalid);
|
||||
QVERIFY(!reader.isUnsignedInteger());
|
||||
QVERIFY(!reader.isNegativeInteger());
|
||||
QVERIFY(!reader.isByteArray());
|
||||
QVERIFY(!reader.isString());
|
||||
QVERIFY(!reader.isArray());
|
||||
QVERIFY(!reader.isContainer());
|
||||
QVERIFY(!reader.isMap());
|
||||
QVERIFY(!reader.isTag());
|
||||
QVERIFY(!reader.isSimpleType());
|
||||
QVERIFY(!reader.isBool());
|
||||
QVERIFY(!reader.isNull());
|
||||
QVERIFY(!reader.isUndefined());
|
||||
QVERIFY(!reader.isFloat16());
|
||||
QVERIFY(!reader.isFloat());
|
||||
QVERIFY(!reader.isDouble());
|
||||
QVERIFY(!reader.isValid());
|
||||
QVERIFY(reader.isInvalid());
|
||||
|
||||
QCOMPARE(reader.containerDepth(), 0);
|
||||
QCOMPARE(reader.parentContainerType(), QCborStreamReader::Invalid);
|
||||
QVERIFY(!reader.hasNext());
|
||||
QVERIFY(!reader.next());
|
||||
QCOMPARE(reader.lastError(), QCborError::EndOfFile);
|
||||
|
||||
QVERIFY(reader.isLengthKnown()); // well, it's not unknown
|
||||
QCOMPARE(reader.length(), ~0ULL);
|
||||
|
||||
reader.addData(QByteArray());
|
||||
reader.reparse();
|
||||
|
||||
// nothing changes, we added nothing
|
||||
QCOMPARE(reader.device(), nullptr);
|
||||
QCOMPARE(reader.currentOffset(), 0);
|
||||
QCOMPARE(reader.lastError(), QCborError::EndOfFile);
|
||||
|
||||
QCOMPARE(reader.type(), QCborStreamReader::Invalid);
|
||||
QVERIFY(!reader.isUnsignedInteger());
|
||||
QVERIFY(!reader.isNegativeInteger());
|
||||
QVERIFY(!reader.isByteArray());
|
||||
QVERIFY(!reader.isString());
|
||||
QVERIFY(!reader.isArray());
|
||||
QVERIFY(!reader.isContainer());
|
||||
QVERIFY(!reader.isMap());
|
||||
QVERIFY(!reader.isTag());
|
||||
QVERIFY(!reader.isSimpleType());
|
||||
QVERIFY(!reader.isBool());
|
||||
QVERIFY(!reader.isNull());
|
||||
QVERIFY(!reader.isUndefined());
|
||||
QVERIFY(!reader.isFloat16());
|
||||
QVERIFY(!reader.isFloat());
|
||||
QVERIFY(!reader.isDouble());
|
||||
QVERIFY(!reader.isValid());
|
||||
QVERIFY(reader.isInvalid());
|
||||
|
||||
QVERIFY(reader.isLengthKnown()); // well, it's not unknown
|
||||
QCOMPARE(reader.length(), ~0ULL);
|
||||
|
||||
QCOMPARE(reader.containerDepth(), 0);
|
||||
QCOMPARE(reader.parentContainerType(), QCborStreamReader::Invalid);
|
||||
QVERIFY(!reader.hasNext());
|
||||
QVERIFY(!reader.next());
|
||||
}
|
||||
|
||||
void tst_QCborStreamReader::integers_data()
|
||||
{
|
||||
addIntegers();
|
||||
}
|
||||
|
||||
void tst_QCborStreamReader::integers()
|
||||
{
|
||||
QFETCH(QByteArray, data);
|
||||
QFETCH(bool, isNegative);
|
||||
QFETCH(quint64, expectedRaw);
|
||||
QFETCH(qint64, expectedValue);
|
||||
QFETCH(bool, inInt64Range);
|
||||
quint64 absolute = (isNegative ? expectedRaw + 1 : expectedRaw);
|
||||
|
||||
QCborStreamReader reader(data);
|
||||
QVERIFY(reader.isValid());
|
||||
QCOMPARE(reader.lastError(), QCborError::NoError);
|
||||
QVERIFY(reader.isInteger());
|
||||
|
||||
if (inInt64Range)
|
||||
QCOMPARE(reader.toInteger(), expectedValue);
|
||||
if (isNegative)
|
||||
QCOMPARE(quint64(reader.toNegativeInteger()), absolute);
|
||||
else
|
||||
QCOMPARE(reader.toUnsignedInteger(), absolute);
|
||||
}
|
||||
|
||||
void escapedAppendTo(QString &result, const QByteArray &data)
|
||||
{
|
||||
result += "h'" + QString::fromLatin1(data.toHex()) + '\'';
|
||||
}
|
||||
|
||||
void escapedAppendTo(QString &result, const QString &data)
|
||||
{
|
||||
result += '"';
|
||||
for (int i = 0; i <= data.size(); i += 245) {
|
||||
// hopefully we won't have a surrogate pair split here
|
||||
QScopedArrayPointer<char> escaped(QTest::toPrettyUnicode(data.mid(i, 245)));
|
||||
QLatin1String s(escaped.data() + 1); // skip opening "
|
||||
s.chop(1); // drop the closing "
|
||||
result += s;
|
||||
}
|
||||
result += '"';
|
||||
}
|
||||
|
||||
template <typename S, QCborStreamReader::StringResult<S> (QCborStreamReader:: *Decoder)()>
|
||||
static QString parseOneString_helper(QCborStreamReader &reader)
|
||||
{
|
||||
QString result;
|
||||
bool parens = !reader.isLengthKnown();
|
||||
if (parens)
|
||||
result += '(';
|
||||
|
||||
auto r = (reader.*Decoder)();
|
||||
const char *comma = "";
|
||||
while (r.status == QCborStreamReader::Ok) {
|
||||
result += comma;
|
||||
escapedAppendTo(result, r.data);
|
||||
|
||||
r = (reader.*Decoder)();
|
||||
comma = ", ";
|
||||
}
|
||||
|
||||
if (r.status == QCborStreamReader::Error)
|
||||
return QString();
|
||||
|
||||
if (parens)
|
||||
result += ')';
|
||||
return result;
|
||||
}
|
||||
|
||||
static QString parseOneByteArray(QCborStreamReader &reader)
|
||||
{
|
||||
return parseOneString_helper<QByteArray, &QCborStreamReader::readByteArray>(reader);
|
||||
}
|
||||
|
||||
static QString parseOneString(QCborStreamReader &reader)
|
||||
{
|
||||
return parseOneString_helper<QString, &QCborStreamReader::readString>(reader);
|
||||
}
|
||||
|
||||
static QString makeNegativeString(QCborNegativeInteger n)
|
||||
{
|
||||
return n == QCborNegativeInteger(0) ?
|
||||
QString("-18446744073709551616") :
|
||||
QString("-%1").arg(quint64(n));
|
||||
}
|
||||
|
||||
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 makeFpString(double v)
|
||||
{
|
||||
if (canConvertTo<qint64>(v))
|
||||
return QString::number(qint64(v)) + '.';
|
||||
if (canConvertTo<quint64>(v))
|
||||
return QString::number(quint64(v)) + '.';
|
||||
|
||||
QString s = QString::number(v, 'g', std::numeric_limits<double>::digits10 + 2);
|
||||
if (!s.contains('.') && !s.contains('e') && !qIsInf(v) && !qIsNaN(v))
|
||||
s += '.';
|
||||
return s;
|
||||
}
|
||||
|
||||
static QString makeFpString(float v)
|
||||
{
|
||||
if (qIsInf(v))
|
||||
return v > 0 ? "inf" : "-inf";
|
||||
if (qIsNaN(v))
|
||||
return "nan";
|
||||
return makeFpString(double(v)) + 'f';
|
||||
}
|
||||
|
||||
static QString makeFpString(qfloat16 v)
|
||||
{
|
||||
if (qIsInf(v))
|
||||
return v > 0 ? "inf" : "-inf";
|
||||
if (qIsNaN(v))
|
||||
return "nan";
|
||||
return makeFpString(double(v)) + "f16";
|
||||
}
|
||||
|
||||
static QString parseOne(QCborStreamReader &reader)
|
||||
{
|
||||
QString result;
|
||||
|
||||
switch (reader.type()) {
|
||||
case QCborStreamReader::UnsignedInteger:
|
||||
result = QString::number(reader.toUnsignedInteger());
|
||||
break;
|
||||
case QCborStreamReader::NegativeInteger:
|
||||
result = makeNegativeString(reader.toNegativeInteger());
|
||||
break;
|
||||
case QCborStreamReader::ByteArray:
|
||||
return parseOneByteArray(reader);
|
||||
case QCborStreamReader::String:
|
||||
return parseOneString(reader);
|
||||
case QCborStreamReader::Array:
|
||||
case QCborStreamReader::Map: {
|
||||
const char *delimiters = (reader.isArray() ? "[]" : "{}");
|
||||
result += delimiters[0];
|
||||
reader.enterContainer();
|
||||
|
||||
QLatin1String comma("");
|
||||
while (reader.lastError() == QCborError::NoError && reader.hasNext()) {
|
||||
result += comma + parseOne(reader);
|
||||
comma = QLatin1String(", ");
|
||||
|
||||
if (reader.parentContainerType() == QCborStreamReader::Map
|
||||
&& reader.lastError() == QCborError::NoError)
|
||||
result += ": " + parseOne(reader);
|
||||
}
|
||||
|
||||
if (reader.isValid())
|
||||
return QString();
|
||||
if (reader.lastError() != QCborError::NoError)
|
||||
return QString();
|
||||
reader.leaveContainer();
|
||||
result += delimiters[1];
|
||||
return result;
|
||||
}
|
||||
case QCborStreamReader::Tag: {
|
||||
QCborTag tag = reader.toTag();
|
||||
if (!reader.next())
|
||||
return QString();
|
||||
return QString("%1(%2)").arg(quint64(tag)).arg(parseOne(reader));
|
||||
}
|
||||
case QCborStreamReader::SimpleType:
|
||||
switch (reader.toSimpleType()) {
|
||||
case QCborSimpleType::False:
|
||||
result = QStringLiteral("false");
|
||||
break;
|
||||
case QCborSimpleType::True:
|
||||
result = QStringLiteral("true");
|
||||
break;
|
||||
case QCborSimpleType::Null:
|
||||
result = QStringLiteral("null");
|
||||
break;
|
||||
case QCborSimpleType::Undefined:
|
||||
result = QStringLiteral("undefined");
|
||||
break;
|
||||
default:
|
||||
result = QString("simple(%1)").arg(quint8(reader.toSimpleType()));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case QCborStreamReader::Float16:
|
||||
result = makeFpString(reader.toFloat16());
|
||||
break;
|
||||
case QCborStreamReader::Float:
|
||||
result = makeFpString(reader.toFloat());
|
||||
break;
|
||||
case QCborStreamReader::Double:
|
||||
result = makeFpString(reader.toDouble());
|
||||
break;
|
||||
case QCborStreamReader::Invalid:
|
||||
return QStringLiteral("<invalid>");
|
||||
}
|
||||
|
||||
if (!reader.next())
|
||||
return QString();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool parseNonRecursive(QString &result, bool &printingStringChunks, QCborStreamReader &reader)
|
||||
{
|
||||
while (reader.lastError() == QCborError::NoError) {
|
||||
if (!reader.hasNext()) {
|
||||
if (result.endsWith(", "))
|
||||
result.chop(2);
|
||||
if (reader.containerDepth() == 0)
|
||||
return true;
|
||||
result += reader.parentContainerType() == QCborStreamReader::Map ? "}, " : "], ";
|
||||
reader.leaveContainer();
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (reader.type()) {
|
||||
case QCborStreamReader::UnsignedInteger:
|
||||
result += QString::number(reader.toUnsignedInteger());
|
||||
break;
|
||||
case QCborStreamReader::NegativeInteger:
|
||||
result += makeNegativeString(reader.toNegativeInteger());
|
||||
break;
|
||||
case QCborStreamReader::ByteArray:
|
||||
case QCborStreamReader::String: {
|
||||
QCborStreamReader::StringResultCode status;
|
||||
if (!printingStringChunks && !reader.isLengthKnown()) {
|
||||
printingStringChunks = true;
|
||||
result += '(';
|
||||
}
|
||||
if (reader.isByteArray()) {
|
||||
auto r = reader.readByteArray();
|
||||
status = r.status;
|
||||
if (r.status == QCborStreamReader::Ok)
|
||||
escapedAppendTo(result, r.data);
|
||||
} else {
|
||||
auto r = reader.readString();
|
||||
status = r.status;
|
||||
if (r.status == QCborStreamReader::Ok)
|
||||
escapedAppendTo(result, r.data);
|
||||
}
|
||||
|
||||
if (status == QCborStreamReader::EndOfString) {
|
||||
if (result.endsWith(", "))
|
||||
result.chop(2);
|
||||
if (printingStringChunks)
|
||||
result += ')';
|
||||
result += ", ";
|
||||
printingStringChunks = false;
|
||||
}
|
||||
if (status == QCborStreamReader::Ok && printingStringChunks)
|
||||
result += ", ";
|
||||
|
||||
continue;
|
||||
}
|
||||
case QCborStreamReader::Array:
|
||||
result += '[';
|
||||
reader.enterContainer();
|
||||
continue;
|
||||
case QCborStreamReader::Map:
|
||||
result += '{';
|
||||
reader.enterContainer();
|
||||
continue;
|
||||
case QCborStreamReader::Tag:
|
||||
result += QString("Tag:%1:").arg(quint64(reader.toTag()));
|
||||
reader.next();
|
||||
continue; // skip the comma
|
||||
case QCborStreamReader::SimpleType:
|
||||
switch (reader.toSimpleType()) {
|
||||
case QCborSimpleType::False:
|
||||
result += QStringLiteral("false");
|
||||
break;
|
||||
case QCborSimpleType::True:
|
||||
result += QStringLiteral("true");
|
||||
break;
|
||||
case QCborSimpleType::Null:
|
||||
result += QStringLiteral("null");
|
||||
break;
|
||||
case QCborSimpleType::Undefined:
|
||||
result += QStringLiteral("undefined");
|
||||
break;
|
||||
default:
|
||||
result += QString("simple(%1)").arg(quint8(reader.toSimpleType()));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case QCborStreamReader::Float16:
|
||||
result += makeFpString(reader.toFloat16());
|
||||
break;
|
||||
case QCborStreamReader::Float:
|
||||
result += makeFpString(reader.toFloat());
|
||||
break;
|
||||
case QCborStreamReader::Double:
|
||||
result += makeFpString(reader.toDouble());
|
||||
break;
|
||||
case QCborStreamReader::Invalid:
|
||||
break;
|
||||
}
|
||||
|
||||
reader.next();
|
||||
result += ", ";
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
static QString &removeIndicators(QString &str)
|
||||
{
|
||||
// remove any CBOR encoding indicators from the string, since parseOne above
|
||||
// doesn't produce them
|
||||
static QRegularExpression rx("_(\\d+)? ?");
|
||||
return str.replace(rx, QString());
|
||||
}
|
||||
|
||||
void tst_QCborStreamReader::fixed_data()
|
||||
{
|
||||
addColumns();
|
||||
addFixedData();
|
||||
}
|
||||
|
||||
void tst_QCborStreamReader::fixed()
|
||||
{
|
||||
QFETCH(QByteArray, data);
|
||||
QFETCH(QString, expected);
|
||||
removeIndicators(expected);
|
||||
|
||||
QCborStreamReader reader(data);
|
||||
QVERIFY(reader.isValid());
|
||||
QCOMPARE(reader.lastError(), QCborError::NoError);
|
||||
QCOMPARE(parseOne(reader), expected);
|
||||
|
||||
// verify that we can re-read
|
||||
reader.reset();
|
||||
QVERIFY(reader.isValid());
|
||||
QCOMPARE(reader.lastError(), QCborError::NoError);
|
||||
QCOMPARE(parseOne(reader), expected);
|
||||
}
|
||||
|
||||
void tst_QCborStreamReader::strings_data()
|
||||
{
|
||||
addColumns();
|
||||
addStringsData();
|
||||
}
|
||||
|
||||
void tst_QCborStreamReader::strings()
|
||||
{
|
||||
fixed();
|
||||
if (QTest::currentTestFailed())
|
||||
return;
|
||||
|
||||
// Extra string checks:
|
||||
// We'll compare the reads using readString() and readByteArray()
|
||||
// (henceforth "control data" because fixed() above tested them) with those
|
||||
// obtained with readStringChunk().
|
||||
|
||||
QFETCH(QByteArray, data);
|
||||
QFETCH(QString, expected);
|
||||
bool isChunked = expected.startsWith('(');
|
||||
|
||||
QCborStreamReader reader(data), controlReader(data);
|
||||
QVERIFY(reader.isString() || reader.isByteArray());
|
||||
QCOMPARE(reader.isLengthKnown(), !isChunked);
|
||||
|
||||
if (!isChunked)
|
||||
QCOMPARE(reader.currentStringChunkSize(), qsizetype(reader.length()));
|
||||
|
||||
int chunks = 0;
|
||||
forever {
|
||||
QCborStreamReader::StringResult<QByteArray> controlData;
|
||||
if (reader.isString()) {
|
||||
auto r = controlReader.readString();
|
||||
controlData.data = r.data.toUtf8();
|
||||
controlData.status = r.status;
|
||||
} else {
|
||||
controlData = controlReader.readByteArray();
|
||||
}
|
||||
QVERIFY(controlData.status != QCborStreamReader::Error);
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
// this call must work several times with the same result
|
||||
QCOMPARE(reader.currentStringChunkSize(), controlData.data.size());
|
||||
}
|
||||
|
||||
QByteArray chunk(controlData.data.size(), Qt::Uninitialized);
|
||||
auto r = reader.readStringChunk(chunk.data(), chunk.size());
|
||||
QCOMPARE(r.status, controlData.status);
|
||||
if (r.status == QCborStreamReader::Ok)
|
||||
QCOMPARE(r.data, controlData.data.size());
|
||||
else
|
||||
QCOMPARE(r.data, 0);
|
||||
QCOMPARE(chunk, controlData.data);
|
||||
|
||||
if (r.status == QCborStreamReader::EndOfString)
|
||||
break;
|
||||
++chunks;
|
||||
}
|
||||
|
||||
if (!isChunked)
|
||||
QCOMPARE(chunks, 1);
|
||||
}
|
||||
|
||||
void tst_QCborStreamReader::tags_data()
|
||||
{
|
||||
addColumns();
|
||||
addTagsData();
|
||||
}
|
||||
|
||||
void tst_QCborStreamReader::emptyContainers_data()
|
||||
{
|
||||
addColumns();
|
||||
addEmptyContainersData();
|
||||
}
|
||||
|
||||
void tst_QCborStreamReader::emptyContainers()
|
||||
{
|
||||
QFETCH(QByteArray, data);
|
||||
QFETCH(QString, expected);
|
||||
removeIndicators(expected);
|
||||
|
||||
QCborStreamReader reader(data);
|
||||
QVERIFY(reader.isValid());
|
||||
QCOMPARE(reader.lastError(), QCborError::NoError);
|
||||
if (reader.isLengthKnown())
|
||||
QCOMPARE(reader.length(), 0U);
|
||||
QCOMPARE(parseOne(reader), expected);
|
||||
|
||||
// verify that we can re-read
|
||||
reader.reset();
|
||||
QVERIFY(reader.isValid());
|
||||
QCOMPARE(reader.lastError(), QCborError::NoError);
|
||||
if (reader.isLengthKnown())
|
||||
QCOMPARE(reader.length(), 0U);
|
||||
QCOMPARE(parseOne(reader), expected);
|
||||
}
|
||||
|
||||
void tst_QCborStreamReader::arrays_data()
|
||||
{
|
||||
addColumns();
|
||||
addFixedData();
|
||||
addStringsData();
|
||||
addTagsData();
|
||||
addEmptyContainersData();
|
||||
}
|
||||
|
||||
static void checkContainer(int len, const QByteArray &data, const QString &expected)
|
||||
{
|
||||
QCborStreamReader reader(data);
|
||||
QVERIFY(reader.isValid());
|
||||
QCOMPARE(reader.lastError(), QCborError::NoError);
|
||||
if (len >= 0) {
|
||||
QVERIFY(reader.isLengthKnown());
|
||||
QCOMPARE(reader.length(), uint(len));
|
||||
}
|
||||
QCOMPARE(parseOne(reader), expected);
|
||||
|
||||
// verify that we can re-read
|
||||
reader.reset();
|
||||
QVERIFY(reader.isValid());
|
||||
QCOMPARE(reader.lastError(), QCborError::NoError);
|
||||
if (len >= 0) {
|
||||
QVERIFY(reader.isLengthKnown());
|
||||
QCOMPARE(reader.length(), uint(len));
|
||||
}
|
||||
QCOMPARE(parseOne(reader), expected);
|
||||
}
|
||||
|
||||
void tst_QCborStreamReader::arrays()
|
||||
{
|
||||
QFETCH(QByteArray, data);
|
||||
QFETCH(QString, expected);
|
||||
removeIndicators(expected);
|
||||
|
||||
checkContainer(1, '\x81' + data, '[' + expected + ']');
|
||||
if (QTest::currentTestFailed())
|
||||
return;
|
||||
|
||||
checkContainer(2, '\x82' + data + data, '[' + expected + ", " + expected + ']');
|
||||
}
|
||||
|
||||
void tst_QCborStreamReader::maps()
|
||||
{
|
||||
QFETCH(QByteArray, data);
|
||||
QFETCH(QString, expected);
|
||||
removeIndicators(expected);
|
||||
|
||||
// int keys
|
||||
checkContainer(1, "\xa1\1" + data, "{1: " + expected + '}');
|
||||
if (QTest::currentTestFailed())
|
||||
return;
|
||||
|
||||
checkContainer(2, "\xa2\1" + data + '\x20' + data,
|
||||
"{1: " + expected + ", -1: " + expected + '}');
|
||||
if (QTest::currentTestFailed())
|
||||
return;
|
||||
|
||||
// string keys
|
||||
checkContainer(1, "\xa1\x65Hello" + data, "{\"Hello\": " + expected + '}');
|
||||
if (QTest::currentTestFailed())
|
||||
return;
|
||||
|
||||
checkContainer(2, "\xa2\x65World" + data + "\x65Hello" + data,
|
||||
"{\"World\": " + expected + ", \"Hello\": " + expected + '}');
|
||||
}
|
||||
|
||||
void tst_QCborStreamReader::undefLengthArrays()
|
||||
{
|
||||
QFETCH(QByteArray, data);
|
||||
QFETCH(QString, expected);
|
||||
removeIndicators(expected);
|
||||
|
||||
checkContainer(-1, '\x9f' + data + '\xff', '[' + expected + ']');
|
||||
if (QTest::currentTestFailed())
|
||||
return;
|
||||
|
||||
checkContainer(-2, '\x9f' + data + data + '\xff', '[' + expected + ", " + expected + ']');
|
||||
}
|
||||
|
||||
void tst_QCborStreamReader::undefLengthMaps()
|
||||
{
|
||||
QFETCH(QByteArray, data);
|
||||
QFETCH(QString, expected);
|
||||
removeIndicators(expected);
|
||||
|
||||
// int keys
|
||||
checkContainer(-1, "\xbf\1" + data + '\xff', "{1: " + expected + '}');
|
||||
if (QTest::currentTestFailed())
|
||||
return;
|
||||
|
||||
checkContainer(-2, "\xbf\1" + data + '\x20' + data + '\xff',
|
||||
"{1: " + expected + ", -1: " + expected + '}');
|
||||
if (QTest::currentTestFailed())
|
||||
return;
|
||||
|
||||
// string keys
|
||||
checkContainer(-1, "\xbf\x65Hello" + data + '\xff', "{\"Hello\": " + expected + '}');
|
||||
if (QTest::currentTestFailed())
|
||||
return;
|
||||
|
||||
checkContainer(-2, "\xbf\x65World" + data + "\x65Hello" + data + '\xff',
|
||||
"{\"World\": " + expected + ", \"Hello\": " + expected + '}');
|
||||
}
|
||||
|
||||
void tst_QCborStreamReader::next()
|
||||
{
|
||||
QFETCH(QByteArray, data);
|
||||
|
||||
auto doit = [](const QByteArray &data) {
|
||||
QCborStreamReader reader(data);
|
||||
return reader.next();
|
||||
};
|
||||
|
||||
QVERIFY(doit('\x81' + data));
|
||||
QVERIFY(doit('\x82' + data + data));
|
||||
QVERIFY(doit('\x9f' + data + '\xff'));
|
||||
QVERIFY(doit("\x81\x9f" + data + '\xff'));
|
||||
QVERIFY(doit("\x9f\x81" + data + '\xff'));
|
||||
|
||||
QVERIFY(doit("\xa1\1" + data));
|
||||
QVERIFY(doit("\xa2\1" + data + '\x20' + data));
|
||||
QVERIFY(doit("\xbf\1" + data + '\xff'));
|
||||
QVERIFY(doit("\xbf\x9f\1\xff\x9f" + data + "\xff\xff"));
|
||||
}
|
||||
|
||||
void tst_QCborStreamReader::validation_data()
|
||||
{
|
||||
addValidationColumns();
|
||||
addValidationData();
|
||||
}
|
||||
|
||||
void tst_QCborStreamReader::validation()
|
||||
{
|
||||
QFETCH(QByteArray, data);
|
||||
|
||||
QCborStreamReader reader(data);
|
||||
parseOne(reader);
|
||||
QVERIFY(reader.lastError() != QCborError::NoError);
|
||||
|
||||
// next() should fail
|
||||
reader.reset();
|
||||
QVERIFY(!reader.next());
|
||||
QVERIFY(reader.lastError() != QCborError::NoError);
|
||||
}
|
||||
|
||||
static const int Recursions = 3;
|
||||
void tst_QCborStreamReader::recursionLimit_data()
|
||||
{
|
||||
static const int recursions = Recursions + 2;
|
||||
QTest::addColumn<QByteArray>("data");
|
||||
|
||||
QTest::newRow("array") << QByteArray(recursions, '\x81') + '\x20';
|
||||
QTest::newRow("_array") << QByteArray(recursions, '\x9f') + '\x20' + QByteArray(recursions, '\xff');
|
||||
|
||||
QByteArray data;
|
||||
for (int i = 0; i < recursions; ++i)
|
||||
data += "\xa1\x65Hello";
|
||||
data += '\2';
|
||||
QTest::newRow("map-recursive-values") << data;
|
||||
|
||||
data.clear();
|
||||
for (int i = 0; i < recursions; ++i)
|
||||
data += "\xbf\x65World";
|
||||
data += '\2';
|
||||
for (int i = 0; i < recursions; ++i)
|
||||
data += "\xff";
|
||||
QTest::newRow("_map-recursive-values") << data;
|
||||
|
||||
data = QByteArray(recursions, '\xa1');
|
||||
data += '\2';
|
||||
for (int i = 0; i < recursions; ++i)
|
||||
data += "\x7f\x64quux\xff";
|
||||
QTest::newRow("map-recursive-keys") << data;
|
||||
|
||||
data = QByteArray(recursions, '\xbf');
|
||||
data += '\2';
|
||||
for (int i = 0; i < recursions; ++i)
|
||||
data += "\1\xff";
|
||||
QTest::newRow("_map-recursive-keys") << data;
|
||||
|
||||
data.clear();
|
||||
for (int i = 0; i < recursions / 2; ++i)
|
||||
data += "\x81\xa1\1";
|
||||
data += '\2';
|
||||
QTest::newRow("mixed") << data;
|
||||
}
|
||||
|
||||
void tst_QCborStreamReader::recursionLimit()
|
||||
{
|
||||
QFETCH(QByteArray, data);
|
||||
|
||||
QCborStreamReader reader('\x81' + data);
|
||||
|
||||
// verify that it works normally:
|
||||
QVERIFY(reader.enterContainer());
|
||||
QVERIFY(reader.next());
|
||||
QVERIFY(!reader.hasNext());
|
||||
|
||||
reader.reset();
|
||||
QVERIFY(reader.enterContainer());
|
||||
QVERIFY(!reader.next(Recursions));
|
||||
QCOMPARE(reader.lastError(), QCborError::NestingTooDeep);
|
||||
}
|
||||
|
||||
void tst_QCborStreamReader::addData_singleElement_data()
|
||||
{
|
||||
addColumns();
|
||||
addFixedData();
|
||||
addNonChunkedStringsData();
|
||||
}
|
||||
|
||||
void tst_QCborStreamReader::addData_singleElement()
|
||||
{
|
||||
QFETCH(QByteArray, data);
|
||||
QFETCH(QString, expected);
|
||||
removeIndicators(expected);
|
||||
|
||||
QCborStreamReader reader;
|
||||
for (int i = 0; i < data.size() - 1; ++i) {
|
||||
// add one byte from the data
|
||||
reader.addData(data.constData() + i, 1);
|
||||
parseOne(reader);
|
||||
QCOMPARE(reader.lastError(), QCborError::EndOfFile);
|
||||
}
|
||||
|
||||
// add the last byte
|
||||
reader.addData(data.right(1));
|
||||
QCOMPARE(reader.lastError(), QCborError::NoError);
|
||||
QCOMPARE(parseOne(reader), expected);
|
||||
}
|
||||
|
||||
void tst_QCborStreamReader::addData_complex()
|
||||
{
|
||||
QFETCH(QByteArray, data);
|
||||
QFETCH(QString, expected);
|
||||
removeIndicators(expected);
|
||||
|
||||
// transform tags (parseNonRecursive() can't produce the usual form)
|
||||
expected.replace(QRegularExpression(R"/((\d+)\(([^)]+)\))/"), "Tag:\\1:\\2");
|
||||
|
||||
auto doit = [](const QByteArray &data) {
|
||||
// start with one byte
|
||||
int added = 1;
|
||||
QCborStreamReader reader(data.constData(), 1);
|
||||
QString result;
|
||||
bool printingStringChunks = false;
|
||||
forever {
|
||||
if (parseNonRecursive(result, printingStringChunks, reader))
|
||||
return result;
|
||||
if (reader.lastError() != QCborError::EndOfFile)
|
||||
return reader.lastError().toString();
|
||||
|
||||
while (reader.lastError() == QCborError::EndOfFile) {
|
||||
// add more data
|
||||
if (added == data.size())
|
||||
return QStringLiteral("Couldn't parse even with all data");
|
||||
reader.addData(data.constData() + added++, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// plain:
|
||||
QCOMPARE(doit(data), expected);
|
||||
|
||||
// in an array
|
||||
QCOMPARE(doit('\x81' + data), '[' + expected + ']');
|
||||
QCOMPARE(doit('\x82' + data + data), '[' + expected + ", " + expected + ']');
|
||||
|
||||
QCOMPARE(doit('\x9f' + data + '\xff'), '[' + expected + ']');
|
||||
QCOMPARE(doit('\x9f' + data + data + '\xff'), '[' + expected + ", " + expected + ']');
|
||||
|
||||
// in a map
|
||||
QCOMPARE(doit("\xa1\x01" + data), "{1, " + expected + '}');
|
||||
QCOMPARE(doit("\xa1\x65Hello" + data), "{\"Hello\", " + expected + '}');
|
||||
QCOMPARE(doit("\xa1\x7f\x65Hello\x65World\xff" + data), "{(\"Hello\", \"World\"), " + expected + '}');
|
||||
QCOMPARE(doit("\xa2\x01" + data + "\x65Hello" + data),
|
||||
"{1, " + expected + ", \"Hello\", " + expected + '}');
|
||||
|
||||
QCOMPARE(doit("\xbf\x01" + data + '\xff'), "{1, " + expected + '}');
|
||||
|
||||
// mixed
|
||||
QCOMPARE(doit("\xbf\x01\x81" + data + '\xff'), "{1, [" + expected + "]}");
|
||||
QCOMPARE(doit("\xbf\x01\x82" + data + data + '\xff'),
|
||||
"{1, [" + expected + ", " + expected + "]}");
|
||||
QCOMPARE(doit("\xbf\x01\x9f" + data + data + "\xff\xff"),
|
||||
"{1, [" + expected + ", " + expected + "]}");
|
||||
}
|
||||
|
||||
|
||||
QTEST_MAIN(tst_QCborStreamReader)
|
||||
|
||||
#include "tst_qcborstreamreader.moc"
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
TEMPLATE = subdirs
|
||||
SUBDIRS = \
|
||||
json \
|
||||
qcborstreamreader \
|
||||
qcborstreamwriter \
|
||||
qdatastream \
|
||||
qtextstream \
|
||||
|
|
|
|||
Loading…
Reference in New Issue