| /**************************************************************************** |
| ** |
| ** 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 "qcborstream.h" |
| |
| #include <private/qnumeric_p.h> |
| #include <private/qutfcodec_p.h> |
| #include <qbuffer.h> |
| #include <qdebug.h> |
| #include <qstack.h> |
| #include <qdatastream.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| #ifdef QT_NO_DEBUG |
| # define NDEBUG 1 |
| #endif |
| #undef assert |
| #define assert Q_ASSERT |
| |
| QT_WARNING_PUSH |
| QT_WARNING_DISABLE_GCC("-Wunused-function") |
| QT_WARNING_DISABLE_CLANG("-Wunused-function") |
| QT_WARNING_DISABLE_CLANG("-Wundefined-internal") |
| QT_WARNING_DISABLE_MSVC(4334) // '<<': result of 32-bit shift implicitly converted to 64 bits (was 64-bit shift intended?) |
| |
| #define CBOR_ENCODER_NO_CHECK_USER |
| |
| #define CBOR_NO_VALIDATION_API 1 |
| #define CBOR_NO_PRETTY_API 1 |
| #define CBOR_API static inline |
| #define CBOR_PRIVATE_API static inline |
| #define CBOR_INLINE_API static inline |
| |
| #include <cbor.h> |
| |
| static CborError qt_cbor_encoder_write_callback(void *token, const void *data, size_t len, CborEncoderAppendType); |
| static bool qt_cbor_decoder_can_read(void *token, size_t len); |
| static void qt_cbor_decoder_advance(void *token, size_t len); |
| static void *qt_cbor_decoder_read(void *token, void *userptr, size_t offset, size_t len); |
| static CborError qt_cbor_decoder_transfer_string(void *token, const void **userptr, size_t offset, size_t len); |
| |
| #define CBOR_ENCODER_WRITER_CONTROL 1 |
| #define CBOR_ENCODER_WRITE_FUNCTION qt_cbor_encoder_write_callback |
| |
| #define CBOR_PARSER_READER_CONTROL 1 |
| #define CBOR_PARSER_CAN_READ_BYTES_FUNCTION qt_cbor_decoder_can_read |
| #define CBOR_PARSER_ADVANCE_BYTES_FUNCTION qt_cbor_decoder_advance |
| #define CBOR_PARSER_TRANSFER_STRING_FUNCTION qt_cbor_decoder_transfer_string |
| #define CBOR_PARSER_READ_BYTES_FUNCTION qt_cbor_decoder_read |
| |
| #include "../3rdparty/tinycbor/src/cborencoder.c" |
| #include "../3rdparty/tinycbor/src/cborerrorstrings.c" |
| #include "../3rdparty/tinycbor/src/cborparser.c" |
| |
| // silence compilers that complain about this being a static function declared |
| // but never defined |
| static CborError cbor_encoder_close_container_checked(CborEncoder*, const CborEncoder*) |
| { |
| Q_UNREACHABLE(); |
| return CborErrorInternalError; |
| } |
| static CborError _cbor_value_dup_string(const CborValue *, void **, size_t *, CborValue *) |
| { |
| Q_UNREACHABLE(); |
| return CborErrorInternalError; |
| } |
| static CborError cbor_value_get_half_float_as_float(const CborValue *, float *) |
| { |
| Q_UNREACHABLE(); |
| return CborErrorInternalError; |
| } |
| static CborError cbor_encode_float_as_half_float(CborEncoder *, float) |
| { |
| Q_UNREACHABLE(); |
| return CborErrorInternalError; |
| } |
| QT_WARNING_POP |
| |
| Q_DECLARE_TYPEINFO(CborEncoder, Q_PRIMITIVE_TYPE); |
| Q_DECLARE_TYPEINFO(CborValue, Q_PRIMITIVE_TYPE); |
| |
| // confirm our constants match TinyCBOR's |
| Q_STATIC_ASSERT(int(QCborStreamReader::UnsignedInteger) == CborIntegerType); |
| Q_STATIC_ASSERT(int(QCborStreamReader::ByteString) == CborByteStringType); |
| Q_STATIC_ASSERT(int(QCborStreamReader::TextString) == CborTextStringType); |
| Q_STATIC_ASSERT(int(QCborStreamReader::Array) == CborArrayType); |
| Q_STATIC_ASSERT(int(QCborStreamReader::Map) == CborMapType); |
| Q_STATIC_ASSERT(int(QCborStreamReader::Tag) == CborTagType); |
| Q_STATIC_ASSERT(int(QCborStreamReader::SimpleType) == CborSimpleType); |
| Q_STATIC_ASSERT(int(QCborStreamReader::HalfFloat) == CborHalfFloatType); |
| Q_STATIC_ASSERT(int(QCborStreamReader::Float) == CborFloatType); |
| Q_STATIC_ASSERT(int(QCborStreamReader::Double) == CborDoubleType); |
| Q_STATIC_ASSERT(int(QCborStreamReader::Invalid) == CborInvalidType); |
| |
| /*! |
| \headerfile <QtCborCommon> |
| |
| \brief The <QtCborCommon> header contains definitions common to both the |
| streaming classes (QCborStreamReader and QCborStreamWriter) and to |
| QCborValue. |
| */ |
| |
| /*! |
| \enum QCborSimpleType |
| \relates <QtCborCommon> |
| |
| This enum contains the possible "Simple Types" for CBOR. Simple Types range |
| from 0 to 255 and are types that carry no further value. |
| |
| The following values are currently known: |
| |
| \value False A "false" boolean. |
| \value True A "true" boolean. |
| \value Null Absence of value (null). |
| \value Undefined Missing or deleted value, usually an error. |
| |
| Qt CBOR API supports encoding and decoding any Simple Type, whether one of |
| those above or any other value. |
| |
| Applications should only use further values if a corresponding specification |
| has been published, otherwise interpretation and validation by the remote |
| may fail. Values 24 to 31 are reserved and must not be used. |
| |
| The current authoritative list is maintained by IANA in the |
| \l{https://www.iana.org/assignments/cbor-simple-values/cbor-simple-values.xml}{Simple |
| Values registry}. |
| |
| \sa QCborStreamWriter::append(QCborSimpleType), QCborStreamReader::isSimpleType(), |
| QCborStreamReader::toSimpleType(), QCborValue::isSimpleType(), QCborValue::toSimpleType() |
| */ |
| |
| Q_CORE_EXPORT const char *qt_cbor_simpletype_id(QCborSimpleType st) |
| { |
| switch (st) { |
| case QCborSimpleType::False: |
| return "False"; |
| case QCborSimpleType::True: |
| return "True"; |
| case QCborSimpleType::Null: |
| return "Null"; |
| case QCborSimpleType::Undefined: |
| return "Undefined"; |
| } |
| return nullptr; |
| } |
| |
| #if !defined(QT_NO_DATASTREAM) |
| QDataStream &operator<<(QDataStream &ds, QCborSimpleType st) |
| { |
| return ds << quint8(st); |
| } |
| |
| QDataStream &operator>>(QDataStream &ds, QCborSimpleType &st) |
| { |
| quint8 v; |
| ds >> v; |
| st = QCborSimpleType(v); |
| return ds; |
| } |
| #endif |
| |
| #if !defined(QT_NO_DEBUG_STREAM) |
| QDebug operator<<(QDebug dbg, QCborSimpleType st) |
| { |
| QDebugStateSaver saver(dbg); |
| const char *id = qt_cbor_simpletype_id(st); |
| if (id) |
| return dbg.nospace() << "QCborSimpleType::" << id; |
| |
| return dbg.nospace() << "QCborSimpleType(" << uint(st) << ')'; |
| } |
| #endif |
| |
| /*! |
| \enum QCborTag |
| \relates <QtCborCommon> |
| |
| This enum contains no enumeration and is used only to provide type-safe |
| access to a CBOR tag. |
| |
| CBOR tags are 64-bit numbers that are attached to generic CBOR types to |
| provide further semantic meaning. QCborTag may be constructed from an |
| enumeration found in QCborKnownTags or directly by providing the numeric |
| representation. |
| |
| For example, the following creates a QCborValue containing a byte array |
| tagged with a tag 2. |
| |
| \snippet code/src_corelib_serialization_qcborstream.cpp 0 |
| |
| \sa QCborKnownTags, QCborStreamWriter::append(QCborTag), |
| QCborStreamReader::isTag(), QCborStreamReader::toTag(), |
| QCborValue::isTag(), QCborValue::tag() |
| */ |
| |
| Q_CORE_EXPORT const char *qt_cbor_tag_id(QCborTag tag) |
| { |
| // Casting to QCborKnownTags's underlying type will make the comparison |
| // below fail if the tag value is out of range. |
| auto n = std::underlying_type<QCborKnownTags>::type(tag); |
| if (QCborTag(n) == tag) { |
| switch (QCborKnownTags(n)) { |
| case QCborKnownTags::DateTimeString: |
| return "DateTimeString"; |
| case QCborKnownTags::UnixTime_t: |
| return "UnixTime_t"; |
| case QCborKnownTags::PositiveBignum: |
| return "PositiveBignum"; |
| case QCborKnownTags::NegativeBignum: |
| return "NegativeBignum"; |
| case QCborKnownTags::Decimal: |
| return "Decimal"; |
| case QCborKnownTags::Bigfloat: |
| return "Bigfloat"; |
| case QCborKnownTags::COSE_Encrypt0: |
| return "COSE_Encrypt0"; |
| case QCborKnownTags::COSE_Mac0: |
| return "COSE_Mac0"; |
| case QCborKnownTags::COSE_Sign1: |
| return "COSE_Sign1"; |
| case QCborKnownTags::ExpectedBase64url: |
| return "ExpectedBase64url"; |
| case QCborKnownTags::ExpectedBase64: |
| return "ExpectedBase64"; |
| case QCborKnownTags::ExpectedBase16: |
| return "ExpectedBase16"; |
| case QCborKnownTags::EncodedCbor: |
| return "EncodedCbor"; |
| case QCborKnownTags::Url: |
| return "Url"; |
| case QCborKnownTags::Base64url: |
| return "Base64url"; |
| case QCborKnownTags::Base64: |
| return "Base64"; |
| case QCborKnownTags::RegularExpression: |
| return "RegularExpression"; |
| case QCborKnownTags::MimeMessage: |
| return "MimeMessage"; |
| case QCborKnownTags::Uuid: |
| return "Uuid"; |
| case QCborKnownTags::COSE_Encrypt: |
| return "COSE_Encrypt"; |
| case QCborKnownTags::COSE_Mac: |
| return "COSE_Mac"; |
| case QCborKnownTags::COSE_Sign: |
| return "COSE_Sign"; |
| case QCborKnownTags::Signature: |
| return "Signature"; |
| } |
| } |
| return nullptr; |
| } |
| |
| #if !defined(QT_NO_DEBUG_STREAM) |
| QDebug operator<<(QDebug dbg, QCborTag tag) |
| { |
| QDebugStateSaver saver(dbg); |
| const char *id = qt_cbor_tag_id(tag); |
| dbg.nospace() << "QCborTag("; |
| if (id) |
| dbg.nospace() << "QCborKnownTags::" << id; |
| else |
| dbg.nospace() << quint64(tag); |
| |
| return dbg << ')'; |
| } |
| #endif |
| |
| /*! |
| \enum QCborKnownTags |
| \relates <QtCborCommon> |
| |
| This enum contains a list of CBOR tags, known at the time of the Qt |
| implementation. This list is not meant to be complete and contains only |
| tags that are either backed by an RFC or specifically used by the Qt |
| implementation. |
| |
| The authoritative list is maintained by IANA in the |
| \l{https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml}{CBOR tag |
| registry}. |
| |
| \value DateTimeString A date and time string, formatted according to RFC 3339, as refined |
| by RFC 4287. It is the same format as Qt::ISODate and |
| Qt::ISODateWithMs. |
| \value UnixTime_t A numerical representation of seconds elapsed since |
| 1970-01-01T00:00Z. |
| \value PositiveBignum A positive number of arbitrary length, encoded as a byte array in |
| network byte order. For example, the number 2\sup{64} is represented by |
| a byte array containing the byte value 0x01 followed by 8 zero bytes. |
| \value NegativeBignum A negative number of arbirary length, encoded as the absolute value |
| of that number, minus one. For example, a byte array containing |
| byte value 0x02 followed by 8 zero bytes represents the number |
| -2\sup{65} - 1. |
| \value Decimal A decimal fraction, encoded as an array of two integers: the first |
| is the exponent of the power of 10, the second the integral |
| mantissa. The value 273.15 would be encoded as array \c{[-2, 27315]}. |
| \value Bigfloat Similar to Decimal, but the exponent is a power of 2 instead. |
| \value COSE_Encrypt0 An \c Encrypt0 map as specified by \l{https://tools.ietf.org/html/rfc8152}{RFC 8152} |
| (CBOR Object Signing and Encryption). |
| \value COSE_Mac0 A \c Mac0 map as specified by \l{https://tools.ietf.org/html/rfc8152}{RFC 8152} |
| (CBOR Object Signing and Encryption). |
| \value COSE_Sign1 A \c Sign1 map as specified by \l{https://tools.ietf.org/html/rfc8152}{RFC 8152} |
| (CBOR Object Signing and Encryption). |
| \value ExpectedBase64url Indicates that the byte array should be encoded using Base64url |
| if the stream is converted to JSON. |
| \value ExpectedBase64 Indicates that the byte array should be encoded using Base64 |
| if the stream is converted to JSON. |
| \value ExpectedBase16 Indicates that the byte array should be encoded using Base16 (hex) |
| if the stream is converted to JSON. |
| \value EncodedCbor Indicates that the byte array contains a CBOR stream. |
| \value Url Indicates that the string contains a URL. |
| \value Base64url Indicates that the string contains data encoded using Base64url. |
| \value Base64 Indicates that the string contains data encoded using Base64. |
| \value RegularExpression Indicates that the string contains a Perl-Compatible Regular |
| Expression pattern. |
| \value MimeMessage Indicates that the string contains a MIME message (according to |
| \l{https://tools.ietf.org/html/rfc2045}){RFC 2045}. |
| \value Uuid Indicates that the byte array contains a UUID. |
| \value COSE_Encrypt An \c Encrypt map as specified by \l{https://tools.ietf.org/html/rfc8152}{RFC 8152} |
| (CBOR Object Signing and Encryption). |
| \value COSE_Mac A \c Mac map as specified by \l{https://tools.ietf.org/html/rfc8152}{RFC 8152} |
| (CBOR Object Signing and Encryption). |
| \value COSE_Sign A \c Sign map as specified by \l{https://tools.ietf.org/html/rfc8152}{RFC 8152} |
| (CBOR Object Signing and Encryption). |
| \value Signature No change in interpretation; this tag can be used as the outermost |
| tag in a CBOR stream as the file header. |
| |
| The following tags are interpreted by QCborValue during decoding and will |
| produce objects with extended Qt types, and it will use those tags when |
| encoding the same extended types. |
| |
| \value DateTimeString \l QDateTime |
| \value UnixTime_t \l QDateTime (only in decoding) |
| \value Url \l QUrl |
| \value Uuid \l QUuid |
| |
| Additionally, if a QCborValue containing a QByteArray is tagged using one of |
| \c ExpectedBase64url, \c ExpectedBase64 or \c ExpectedBase16, QCborValue |
| will use the expected encoding when converting to JSON (see |
| QCborValue::toJsonValue). |
| |
| \sa QCborTag, QCborStreamWriter::append(QCborTag), |
| QCborStreamReader::isTag(), QCborStreamReader::toTag(), |
| QCborValue::isTag(), QCborValue::tag() |
| */ |
| |
| #if !defined(QT_NO_DEBUG_STREAM) |
| QDebug operator<<(QDebug dbg, QCborKnownTags tag) |
| { |
| QDebugStateSaver saver(dbg); |
| const char *id = qt_cbor_tag_id(QCborTag(int(tag))); |
| if (id) |
| return dbg.nospace() << "QCborKnownTags::" << id; |
| |
| return dbg.nospace() << "QCborKnownTags(" << int(tag) << ')'; |
| } |
| #endif |
| |
| /*! |
| \class QCborError |
| \inmodule QtCore |
| \relates <QtCborCommon> |
| \reentrant |
| \since 5.12 |
| |
| \brief The QCborError class holds the error condition found while parsing or |
| validating a CBOR stream. |
| |
| \sa QCborStreamReader, QCborValue, QCborParserError |
| */ |
| |
| /*! |
| \enum QCborError::Code |
| |
| This enum contains the possible error condition codes. |
| |
| \value NoError No error was detected. |
| \value UnknownError An unknown error occurred and no further details are available. |
| \value AdvancePastEnd QCborStreamReader::next() was called but there are no more elements in |
| the current context. |
| \value InputOutputError An I/O error with the QIODevice occurred. |
| \value GarbageAtEnd Data was found in the input stream after the last element. |
| \value EndOfFile The end of the input stream was unexpectedly reached while processing an |
| element. |
| \value UnexpectedBreak The CBOR stream contains a Break where it is not allowed (data is |
| corrupt and the error is not recoverable). |
| \value UnknownType The CBOR stream contains an unknown/unparseable Type (data is corrupt |
| and the and the error is not recoverable). |
| \value IllegalType The CBOR stream contains a known type in a position it is not allowed |
| to exist (data is corrupt and the error is not recoverable). |
| \value IllegalNumber The CBOR stream appears to be encoding a number larger than 64-bit |
| (data is corrupt and the error is not recoverable). |
| \value IllegalSimpleType The CBOR stream contains a Simple Type encoded incorrectly (data is |
| corrupt and the error is not recoverable). |
| \value InvalidUtf8String The CBOR stream contains a text string that does not decode properly |
| as UTF-8 (data is corrupt and the error is not recoverable). |
| \value DataTooLarge CBOR string, map or array is too big and cannot be parsed by Qt |
| (internal limitation, but the error is not recoverable). |
| \value NestingTooDeep Too many levels of arrays or maps encountered while processing the |
| input (internal limitation, but the error is not recoverable). |
| \value UnsupportedType The CBOR stream contains a known type that the implementation does not |
| support (internal limitation, but the error is not recoverable). |
| */ |
| |
| // Convert from CborError to QCborError. |
| // |
| // Centralized in a function in case we need to make more adjustments in the |
| // future. |
| static QCborError fromCborError(CborError err) |
| { |
| return { QCborError::Code(int(err)) }; |
| } |
| |
| // Convert to CborError from QCborError. |
| // |
| // Centralized in a function in case we need to make more adjustments in the |
| // future. |
| static CborError toCborError(QCborError c) |
| { |
| return CborError(int(c.c)); |
| } |
| |
| /*! |
| \variable QCborError::c |
| \internal |
| */ |
| |
| /*! |
| \fn QCborError::operator Code() const |
| |
| Returns the error code that this QCborError object stores. |
| */ |
| |
| /*! |
| Returns a text string that matches the error code in this QCborError object. |
| |
| Note: the string is not translated. Applications whose interface allow users |
| to parse CBOR streams need to provide their own, translated strings. |
| |
| \sa QCborError::Code |
| */ |
| QString QCborError::toString() const |
| { |
| switch (c) { |
| case NoError: |
| Q_STATIC_ASSERT(int(NoError) == int(CborNoError)); |
| return QString(); |
| |
| case UnknownError: |
| Q_STATIC_ASSERT(int(UnknownError) == int(CborUnknownError)); |
| return QStringLiteral("Unknown error"); |
| case AdvancePastEnd: |
| Q_STATIC_ASSERT(int(AdvancePastEnd) == int(CborErrorAdvancePastEOF)); |
| return QStringLiteral("Read past end of buffer (more bytes needed)"); |
| case InputOutputError: |
| Q_STATIC_ASSERT(int(InputOutputError) == int(CborErrorIO)); |
| return QStringLiteral("Input/Output error"); |
| case GarbageAtEnd: |
| Q_STATIC_ASSERT(int(GarbageAtEnd) == int(CborErrorGarbageAtEnd)); |
| return QStringLiteral("Data found after the end of the stream"); |
| case EndOfFile: |
| Q_STATIC_ASSERT(int(EndOfFile) == int(CborErrorUnexpectedEOF)); |
| return QStringLiteral("Unexpected end of input data (more bytes needed)"); |
| case UnexpectedBreak: |
| Q_STATIC_ASSERT(int(UnexpectedBreak) == int(CborErrorUnexpectedBreak)); |
| return QStringLiteral("Invalid CBOR stream: unexpected 'break' byte"); |
| case UnknownType: |
| Q_STATIC_ASSERT(int(UnknownType) == int(CborErrorUnknownType)); |
| return QStringLiteral("Invalid CBOR stream: unknown type"); |
| case IllegalType: |
| Q_STATIC_ASSERT(int(IllegalType) == int(CborErrorIllegalType)); |
| return QStringLiteral("Invalid CBOR stream: illegal type found"); |
| case IllegalNumber: |
| Q_STATIC_ASSERT(int(IllegalNumber) == int(CborErrorIllegalNumber)); |
| return QStringLiteral("Invalid CBOR stream: illegal number encoding (future extension)"); |
| case IllegalSimpleType: |
| Q_STATIC_ASSERT(int(IllegalSimpleType) == int(CborErrorIllegalSimpleType)); |
| return QStringLiteral("Invalid CBOR stream: illegal simple type"); |
| case InvalidUtf8String: |
| Q_STATIC_ASSERT(int(InvalidUtf8String) == int(CborErrorInvalidUtf8TextString)); |
| return QStringLiteral("Invalid CBOR stream: invalid UTF-8 text string"); |
| case DataTooLarge: |
| Q_STATIC_ASSERT(int(DataTooLarge) == int(CborErrorDataTooLarge)); |
| return QStringLiteral("Internal limitation: data set too large"); |
| case NestingTooDeep: |
| Q_STATIC_ASSERT(int(NestingTooDeep) == int(CborErrorNestingTooDeep)); |
| return QStringLiteral("Internal limitation: data nesting too deep"); |
| case UnsupportedType: |
| Q_STATIC_ASSERT(int(UnsupportedType) == int(CborErrorUnsupportedType)); |
| return QStringLiteral("Internal limitation: unsupported type"); |
| } |
| |
| // get the error string from TinyCBOR |
| CborError err = toCborError(*this); |
| return QString::fromLatin1(cbor_error_string(err)); |
| } |
| |
| /*! |
| \class QCborStreamWriter |
| \inmodule QtCore |
| \ingroup cbor |
| \reentrant |
| \since 5.12 |
| |
| \brief The QCborStreamWriter class is a simple CBOR encoder operating on a |
| one-way stream. |
| |
| This class can be used to quickly encode a stream of CBOR content directly |
| to either a QByteArray or QIODevice. CBOR is the Concise Binary Object |
| Representation, a very compact form of binary data encoding that is |
| compatible with JSON. It was created by the IETF Constrained RESTful |
| Environments (CoRE) WG, which has used it in many new RFCs. It is meant to |
| be used alongside the \l{https://tools.ietf.org/html/rfc7252}{CoAP |
| protocol}. |
| |
| QCborStreamWriter provides a StAX-like API, similar to that of |
| \l{QXmlStreamWriter}. It is rather low-level and requires a bit of knowledge |
| of CBOR encoding. For a simpler API, see \l{QCborValue} and especially the |
| encoding function QCborValue::toCbor(). |
| |
| The typical use of QCborStreamWriter is to create the object on the target |
| QByteArray or QIODevice, then call one of the append() overloads with the |
| desired type to be encoded. To create arrays and maps, QCborStreamWriter |
| provides startArray() and startMap() overloads, which must be terminated by |
| the corresponding endArray() and endMap() functions. |
| |
| The following example encodes the equivalent of this JSON content: |
| |
| \div{class="pre"} |
| { |
| "label": "journald", |
| "autoDetect": false, |
| "condition": "libs.journald", |
| "output": [ "privateFeature" ] |
| } |
| \enddiv |
| |
| \snippet code/src_corelib_serialization_qcborstream.cpp 1 |
| |
| \section1 CBOR support |
| |
| QCborStreamWriter supports all CBOR features required to create canonical |
| and strict streams. It implements almost all of the features specified in |
| \l {https://tools.ietf.org/html/rfc7049}{RFC 7049}. |
| |
| The following table lists the CBOR features that QCborStreamWriter supports. |
| |
| \table |
| \header \li Feature \li Support |
| \row \li Unsigned numbers \li Yes (full range) |
| \row \li Negative numbers \li Yes (full range) |
| \row \li Byte strings \li Yes |
| \row \li Text strings \li Yes |
| \row \li Chunked strings \li No |
| \row \li Tags \li Yes (arbitrary) |
| \row \li Booleans \li Yes |
| \row \li Null \li Yes |
| \row \li Undefined \li Yes |
| \row \li Arbitrary simple values \li Yes |
| \row \li Half-precision float (16-bit) \li Yes |
| \row \li Single-precision float (32-bit) \li Yes |
| \row \li Double-precision float (64-bit) \li Yes |
| \row \li Infinities and NaN floating point \li Yes |
| \row \li Determinate-length arrays and maps \li Yes |
| \row \li Indeterminate-length arrays and maps \li Yes |
| \row \li Map key types other than strings and integers \li Yes (arbitrary) |
| \endtable |
| |
| \section2 Canonical CBOR encoding |
| |
| Canonical CBOR encoding is defined by |
| \l{https://tools.ietf.org/html/rfc7049#section-3.9}{Section 3.9 of RFC |
| 7049}. Canonical encoding is not a requirement for Qt's CBOR decoding |
| functionality, but it may be required for some protocols. In particular, |
| protocols that require the ability to reproduce the same stream identically |
| may require this. |
| |
| In order to be considered "canonical", a CBOR stream must meet the |
| following requirements: |
| |
| \list |
| \li Integers must be as small as possible. QCborStreamWriter always |
| does this (no user action is required and it is not possible |
| to write overlong integers). |
| \li Array, map and string lengths must be as short as possible. As |
| above, QCborStreamWriter automatically does this. |
| \li Arrays, maps and strings must use explicit length. QCborStreamWriter |
| always does this for strings; for arrays and maps, be sure to call |
| startArray() and startMap() overloads with explicit length. |
| \li Keys in every map must be sorted in ascending order. QCborStreamWriter |
| offers no help in this item: the developer must ensure that before |
| calling append() for the map pairs. |
| \li Floating point values should be as small as possible. QCborStreamWriter |
| will not convert floating point values; it is up to the developer |
| to perform this check prior to calling append() (see those functions' |
| examples). |
| \endlist |
| |
| \section2 Strict CBOR mode |
| |
| Strict mode is defined by |
| \l{https://tools.ietf.org/html/rfc7049#section-3.10}{Section 3.10 of RFC |
| 7049}. As for Canonical encoding above, QCborStreamWriter makes it possible |
| to create strict CBOR streams, but does not require them or validate that |
| the output is so. |
| |
| \list |
| \li Keys in a map must be unique. QCborStreamWriter performs no validation |
| of map keys. |
| \li Tags may be required to be paired only with the correct types, |
| according to their specification. QCborStreamWriter performs no |
| validation of tag usage. |
| \li Text Strings must be properly-encoded UTF-8. QCborStreamWriter always |
| writes proper UTF-8 for strings added with append(), but performs no |
| validation for strings added with appendTextString(). |
| \endlist |
| |
| \section2 Invalid CBOR stream |
| |
| It is also possible to misuse QCborStreamWriter and produce invalid CBOR |
| streams that will fail to be decoded by a receiver. The following actions |
| will produce invalid streams: |
| |
| \list |
| \li Append a tag and not append the corresponding tagged value |
| (QCborStreamWriter produces no diagnostic). |
| \li Append too many or too few items to an array or map with explicit |
| length (endMap() and endArray() will return false and |
| QCborStreamWriter will log with qWarning()). |
| \endlist |
| |
| \sa QCborStreamReader, QCborValue, QXmlStreamWriter |
| */ |
| |
| class QCborStreamWriterPrivate |
| { |
| public: |
| static Q_CONSTEXPR quint64 IndefiniteLength = (std::numeric_limits<quint64>::max)(); |
| |
| QIODevice *device; |
| CborEncoder encoder; |
| QStack<CborEncoder> containerStack; |
| bool deleteDevice = false; |
| |
| QCborStreamWriterPrivate(QIODevice *device) |
| : device(device) |
| { |
| cbor_encoder_init_writer(&encoder, qt_cbor_encoder_write_callback, this); |
| } |
| |
| ~QCborStreamWriterPrivate() |
| { |
| if (deleteDevice) |
| delete device; |
| } |
| |
| template <typename... Args> void executeAppend(CborError (*f)(CborEncoder *, Args...), Args... args) |
| { |
| f(&encoder, std::forward<Args>(args)...); |
| } |
| |
| void createContainer(CborError (*f)(CborEncoder *, CborEncoder *, size_t), quint64 len = IndefiniteLength) |
| { |
| Q_STATIC_ASSERT(size_t(IndefiniteLength) == CborIndefiniteLength); |
| if (sizeof(len) != sizeof(size_t) && len != IndefiniteLength) { |
| if (Q_UNLIKELY(len >= CborIndefiniteLength)) { |
| // TinyCBOR can't do this in 32-bit mode |
| qWarning("QCborStreamWriter: container of size %llu is too big for a 32-bit build; " |
| "will use indeterminate length instead", len); |
| len = CborIndefiniteLength; |
| } |
| } |
| |
| containerStack.push(encoder); |
| f(&containerStack.top(), &encoder, len); |
| } |
| |
| bool closeContainer() |
| { |
| if (containerStack.isEmpty()) { |
| qWarning("QCborStreamWriter: closing map or array that wasn't open"); |
| return false; |
| } |
| |
| CborEncoder container = containerStack.pop(); |
| CborError err = cbor_encoder_close_container(&container, &encoder); |
| encoder = container; |
| |
| if (Q_UNLIKELY(err)) { |
| if (err == CborErrorTooFewItems) |
| qWarning("QCborStreamWriter: not enough items added to array or map"); |
| else if (err == CborErrorTooManyItems) |
| qWarning("QCborStreamWriter: too many items added to array or map"); |
| return false; |
| } |
| |
| return true; |
| } |
| }; |
| |
| static CborError qt_cbor_encoder_write_callback(void *self, const void *data, size_t len, CborEncoderAppendType) |
| { |
| auto that = static_cast<QCborStreamWriterPrivate *>(self); |
| if (!that->device) |
| return CborNoError; |
| qint64 written = that->device->write(static_cast<const char *>(data), len); |
| return (written == qsizetype(len) ? CborNoError : CborErrorIO); |
| } |
| |
| /*! |
| Creates a QCborStreamWriter object that will write the stream to \a device. |
| The device must be opened before the first append() call is made. This |
| constructor can be used with any class that derives from QIODevice, such as |
| QFile, QProcess or QTcpSocket. |
| |
| QCborStreamWriter has no buffering, so every append() call will result in |
| one or more calls to the device's \l {QIODevice::}{write()} method. |
| |
| The following example writes an empty map to a file: |
| |
| \snippet code/src_corelib_serialization_qcborstream.cpp 2 |
| |
| QCborStreamWriter does not take ownership of \a device. |
| |
| \sa device(), setDevice() |
| */ |
| QCborStreamWriter::QCborStreamWriter(QIODevice *device) |
| : d(new QCborStreamWriterPrivate(device)) |
| { |
| } |
| |
| /*! |
| Creates a QCborStreamWriter object that will append the stream to \a data. |
| All streaming is done immediately to the byte array, without the need for |
| flushing any buffers. |
| |
| The following example writes a number to a byte array then returns |
| it. |
| |
| \snippet code/src_corelib_serialization_qcborstream.cpp 3 |
| |
| QCborStreamWriter does not take ownership of \a data. |
| */ |
| QCborStreamWriter::QCborStreamWriter(QByteArray *data) |
| : d(new QCborStreamWriterPrivate(new QBuffer(data))) |
| { |
| d->deleteDevice = true; |
| d->device->open(QIODevice::WriteOnly | QIODevice::Unbuffered); |
| } |
| |
| /*! |
| Destroys this QCborStreamWriter object and frees any resources associated. |
| |
| QCborStreamWriter does not perform error checking to see if all required |
| items were written to the stream prior to the object being destroyed. It is |
| the programmer's responsibility to ensure that it was done. |
| */ |
| QCborStreamWriter::~QCborStreamWriter() |
| { |
| } |
| |
| /*! |
| Replaces the device or byte array that this QCborStreamWriter object is |
| writing to with \a device. |
| |
| \sa device() |
| */ |
| void QCborStreamWriter::setDevice(QIODevice *device) |
| { |
| if (d->deleteDevice) |
| delete d->device; |
| d->device = device; |
| d->deleteDevice = false; |
| } |
| |
| /*! |
| Returns the QIODevice that this QCborStreamWriter object is writing to. The |
| device must have previously been set with either the constructor or with |
| setDevice(). |
| |
| If this object was created by writing to a QByteArray, this function will |
| return an internal instance of QBuffer, which is owned by QCborStreamWriter. |
| |
| \sa setDevice() |
| */ |
| QIODevice *QCborStreamWriter::device() const |
| { |
| return d->device; |
| } |
| |
| /*! |
| \overload |
| |
| Appends the 64-bit unsigned value \a u to the CBOR stream, creating a CBOR |
| Unsigned Integer value. In the following example, we write the values 0, |
| 2\sup{32} and \c UINT64_MAX: |
| |
| \snippet code/src_corelib_serialization_qcborstream.cpp 4 |
| |
| \sa QCborStreamReader::isUnsignedInteger(), QCborStreamReader::toUnsignedInteger() |
| */ |
| void QCborStreamWriter::append(quint64 u) |
| { |
| d->executeAppend(cbor_encode_uint, uint64_t(u)); |
| } |
| |
| /*! |
| \overload |
| |
| Appends the 64-bit signed value \a i to the CBOR stream. This will create |
| either a CBOR Unsigned Integer or CBOR NegativeInteger value based on the |
| sign of the parameter. In the following example, we write the values 0, -1, |
| 2\sup{32} and \c INT64_MAX: |
| |
| \snippet code/src_corelib_serialization_qcborstream.cpp 5 |
| |
| \sa QCborStreamReader::isInteger(), QCborStreamReader::toInteger() |
| */ |
| void QCborStreamWriter::append(qint64 i) |
| { |
| d->executeAppend(cbor_encode_int, int64_t(i)); |
| } |
| |
| /*! |
| \overload |
| |
| Appends the 64-bit negative value \a n to the CBOR stream. |
| QCborNegativeInteger is a 64-bit enum that holds the absolute value of the |
| negative number we want to write. If n is zero, the value written will be |
| equivalent to 2\sup{64} (that is, -18,446,744,073,709,551,616). |
| |
| In the following example, we write the values -1, -2\sup{32} and INT64_MIN: |
| \snippet code/src_corelib_serialization_qcborstream.cpp 6 |
| |
| Note how this function can be used to encode numbers that cannot fit a |
| standard computer's 64-bit signed integer like \l qint64. That is, if \a n |
| is larger than \c{std::numeric_limits<qint64>::max()} or is 0, this will |
| represent a negative number smaller than |
| \c{std::numeric_limits<qint64>::min()}. |
| |
| \sa QCborStreamReader::isNegativeInteger(), QCborStreamReader::toNegativeInteger() |
| */ |
| void QCborStreamWriter::append(QCborNegativeInteger n) |
| { |
| d->executeAppend(cbor_encode_negative_int, uint64_t(n)); |
| } |
| |
| /*! |
| \fn void QCborStreamWriter::append(const QByteArray &ba) |
| \overload |
| |
| Appends the byte array \a ba to the stream, creating a CBOR Byte String |
| value. QCborStreamWriter will attempt to write the entire string in one |
| chunk. |
| |
| The following example will load and append the contents of a file to the |
| stream: |
| |
| \snippet code/src_corelib_serialization_qcborstream.cpp 7 |
| |
| As the example shows, unlike JSON, CBOR requires no escaping for binary |
| content. |
| |
| \sa appendByteString(), QCborStreamReader::isByteArray(), |
| QCborStreamReader::readByteArray() |
| */ |
| |
| /*! |
| \overload |
| |
| Appends the text string \a str to the stream, creating a CBOR Text String |
| value. QCborStreamWriter will attempt to write the entire string in one |
| chunk. |
| |
| The following example appends a simple string to the stream: |
| |
| \snippet code/src_corelib_serialization_qcborstream.cpp 8 |
| |
| \b{Performance note}: CBOR requires that all Text Strings be encoded in |
| UTF-8, so this function will iterate over the characters in the string to |
| determine whether the contents are US-ASCII or not. If the string is found |
| to contain characters outside of US-ASCII, it will allocate memory and |
| convert to UTF-8. If this check is unnecessary, use appendTextString() |
| instead. |
| |
| \sa QCborStreamReader::isString(), QCborStreamReader::readString() |
| */ |
| void QCborStreamWriter::append(QLatin1String str) |
| { |
| // We've got Latin-1 but CBOR wants UTF-8, so check if the string is the |
| // common subset (US-ASCII). |
| if (QtPrivate::isAscii(str)) { |
| // it is plain US-ASCII |
| appendTextString(str.latin1(), str.size()); |
| } else { |
| // non-ASCII, so we need a pass-through UTF-16 |
| append(QString(str)); |
| } |
| } |
| |
| /*! |
| \overload |
| |
| Appends the text string \a str to the stream, creating a CBOR Text String |
| value. QCborStreamWriter will attempt to write the entire string in one |
| chunk. |
| |
| The following example writes an arbitrary QString to the stream: |
| |
| \snippet code/src_corelib_serialization_qcborstream.cpp 9 |
| |
| \sa QCborStreamReader::isString(), QCborStreamReader::readString() |
| */ |
| void QCborStreamWriter::append(QStringView str) |
| { |
| QByteArray utf8 = str.toUtf8(); |
| appendTextString(utf8.constData(), utf8.size()); |
| } |
| |
| /*! |
| \overload |
| |
| Appends the CBOR tag \a tag to the stream, creating a CBOR Tag value. All |
| tags must be followed by another type which they provide meaning for. |
| |
| In the following example, we append a CBOR Tag 36 (Regular Expression) and a |
| QRegularExpression's pattern to the stream: |
| |
| \snippet code/src_corelib_serialization_qcborstream.cpp 10 |
| |
| \sa QCborStreamReader::isTag(), QCborStreamReader::toTag() |
| */ |
| void QCborStreamWriter::append(QCborTag tag) |
| { |
| d->executeAppend(cbor_encode_tag, CborTag(tag)); |
| } |
| |
| /*! |
| \fn void QCborStreamWriter::append(QCborKnownTags tag) |
| \overload |
| |
| Appends the CBOR tag \a tag to the stream, creating a CBOR Tag value. All |
| tags must be followed by another type which they provide meaning for. |
| |
| In the following example, we append a CBOR Tag 1 (Unix \c time_t) and an |
| integer representing the current time to the stream, obtained using the \c |
| time() function: |
| |
| \snippet code/src_corelib_serialization_qcborstream.cpp 11 |
| |
| \sa QCborStreamReader::isTag(), QCborStreamReader::toTag() |
| */ |
| |
| /*! |
| \overload |
| |
| Appends the CBOR simple type \a st to the stream, creating a CBOR Simple |
| Type value. In the following example, we write the simple type for Null as |
| well as for type 32, which Qt has no support for. |
| |
| \snippet code/src_corelib_serialization_qcborstream.cpp 12 |
| |
| \note Using Simple Types for which there is no specification can lead to |
| validation errors by the remote receiver. In addition, simple type values 24 |
| through 31 (inclusive) are reserved and must not be used. |
| |
| \sa QCborStreamReader::isSimpleType(), QCborStreamReader::toSimpleType() |
| */ |
| void QCborStreamWriter::append(QCborSimpleType st) |
| { |
| d->executeAppend(cbor_encode_simple_value, uint8_t(st)); |
| } |
| |
| /*! |
| \overload |
| |
| Appends the floating point number \a f to the stream, creating a CBOR 16-bit |
| Half-Precision Floating Point value. The following code can be used to convert |
| a C++ \tt float to \c qfloat16 if there's no loss of precision and append it, or |
| instead append the \tt float. |
| |
| \snippet code/src_corelib_serialization_qcborstream.cpp 13 |
| |
| \sa QCborStreamReader::isFloat16(), QCborStreamReader::toFloat16() |
| */ |
| void QCborStreamWriter::append(qfloat16 f) |
| { |
| d->executeAppend(cbor_encode_half_float, static_cast<const void *>(&f)); |
| } |
| |
| /*! |
| \overload |
| |
| Appends the floating point number \a f to the stream, creating a CBOR 32-bit |
| Single-Precision Floating Point value. The following code can be used to convert |
| a C++ \tt double to \tt float if there's no loss of precision and append it, or |
| instead append the \tt double. |
| |
| \snippet code/src_corelib_serialization_qcborstream.cpp 14 |
| |
| \sa QCborStreamReader::isFloat(), QCborStreamReader::toFloat() |
| */ |
| void QCborStreamWriter::append(float f) |
| { |
| d->executeAppend(cbor_encode_float, f); |
| } |
| |
| /*! |
| \overload |
| |
| Appends the floating point number \a d to the stream, creating a CBOR 64-bit |
| Double-Precision Floating Point value. QCborStreamWriter always appends the |
| number as-is, performing no check for whether the number is the canonical |
| form for NaN, an infinite, whether it is denormal or if it could be written |
| with a shorter format. |
| |
| The following code performs all those checks, except for the denormal one, |
| which is expected to be taken into account by the system FPU or floating |
| point emulation directly. |
| |
| \snippet code/src_corelib_serialization_qcborstream.cpp 15 |
| |
| Determining if a double can be converted to an integral with no loss of |
| precision is left as an exercise to the reader. |
| |
| \sa QCborStreamReader::isDouble(), QCborStreamReader::toDouble() |
| */ |
| void QCborStreamWriter::append(double d) |
| { |
| this->d->executeAppend(cbor_encode_double, d); |
| } |
| |
| /*! |
| Appends \a len bytes of data starting from \a data to the stream, creating a |
| CBOR Byte String value. QCborStreamWriter will attempt to write the entire |
| string in one chunk. |
| |
| Unlike the QByteArray overload of append(), this function is not limited by |
| QByteArray's size limits. However, note that neither |
| QCborStreamReader::readByteArray() nor QCborValue support reading CBOR |
| streams with byte arrays larger than 2 GB. |
| |
| \sa append(), appendTextString(), |
| QCborStreamReader::isByteArray(), QCborStreamReader::readByteArray() |
| */ |
| void QCborStreamWriter::appendByteString(const char *data, qsizetype len) |
| { |
| d->executeAppend(cbor_encode_byte_string, reinterpret_cast<const uint8_t *>(data), size_t(len)); |
| } |
| |
| /*! |
| Appends \a len bytes of text starting from \a utf8 to the stream, creating a |
| CBOR Text String value. QCborStreamWriter will attempt to write the entire |
| string in one chunk. |
| |
| The string pointed to by \a utf8 is expected to be properly encoded UTF-8. |
| QCborStreamWriter performs no validation that this is the case. |
| |
| Unlike the QLatin1String overload of append(), this function is not limited |
| to 2 GB. However, note that neither QCborStreamReader::readString() nor |
| QCborValue support reading CBOR streams with text strings larger than 2 GB. |
| |
| \sa append(QLatin1String), append(QStringView), |
| QCborStreamReader::isString(), QCborStreamReader::readString() |
| */ |
| void QCborStreamWriter::appendTextString(const char *utf8, qsizetype len) |
| { |
| d->executeAppend(cbor_encode_text_string, utf8, size_t(len)); |
| } |
| |
| /*! |
| \fn void QCborStreamWriter::append(const char *str, qsizetype size) |
| \overload |
| |
| Appends \a size bytes of text starting from \a str to the stream, creating a |
| CBOR Text String value. QCborStreamWriter will attempt to write the entire |
| string in one chunk. If \a size is -1, this function will write \c strlen(\a |
| str) bytes. |
| |
| The string pointed to by \a str is expected to be properly encoded UTF-8. |
| QCborStreamWriter performs no validation that this is the case. |
| |
| Unlike the QLatin1String overload of append(), this function is not limited |
| to 2 GB. However, note that neither QCborStreamReader nor QCborValue support |
| reading CBOR streams with text strings larger than 2 GB. |
| |
| \sa append(QLatin1String), append(QStringView), |
| QCborStreamReader::isString(), QCborStreamReader::readString() |
| */ |
| |
| /*! |
| \fn void QCborStreamWriter::append(bool b) |
| \overload |
| |
| Appends the boolean value \a b to the stream, creating either a CBOR False |
| value or a CBOR True value. This function is equivalent to (and implemented |
| as): |
| |
| \snippet code/src_corelib_serialization_qcborstream.cpp 16 |
| |
| \sa appendNull(), appendUndefined(), |
| QCborStreamReader::isBool(), QCborStreamReader::toBool() |
| */ |
| |
| /*! |
| \fn void QCborStreamWriter::append(std::nullptr_t) |
| \overload |
| |
| Appends a CBOR Null value to the stream. This function is equivalent to (and |
| implemented as): The parameter is ignored. |
| |
| \snippet code/src_corelib_serialization_qcborstream.cpp 17 |
| |
| \sa appendNull(), append(QCborSimpleType), QCborStreamReader::isNull() |
| */ |
| |
| /*! |
| \fn void QCborStreamWriter::appendNull() |
| |
| Appends a CBOR Null value to the stream. This function is equivalent to (and |
| implemented as): |
| |
| \snippet code/src_corelib_serialization_qcborstream.cpp 18 |
| |
| \sa append(std::nullptr_t), append(QCborSimpleType), QCborStreamReader::isNull() |
| */ |
| |
| /*! |
| \fn void QCborStreamWriter::appendUndefined() |
| |
| Appends a CBOR Undefined value to the stream. This function is equivalent to (and |
| implemented as): |
| |
| \snippet code/src_corelib_serialization_qcborstream.cpp 19 |
| |
| \sa append(QCborSimpleType), QCborStreamReader::isUndefined() |
| */ |
| |
| /*! |
| Starts a CBOR Array with indeterminate length in the CBOR stream. Each |
| startArray() call must be paired with one endArray() call and the current |
| CBOR element extends until the end of the array. |
| |
| The array created by this function has no explicit length. Instead, its |
| length is implied by the elements contained in it. Note, however, that use |
| of indeterminate-length arrays is not compliant with canonical CBOR encoding. |
| |
| The following example appends elements from the linked list of strings |
| passed as input: |
| |
| \snippet code/src_corelib_serialization_qcborstream.cpp 20 |
| |
| \sa startArray(quint64), endArray(), startMap(), QCborStreamReader::isArray(), |
| QCborStreamReader::isLengthKnown() |
| */ |
| void QCborStreamWriter::startArray() |
| { |
| d->createContainer(cbor_encoder_create_array); |
| } |
| |
| /*! |
| \overload |
| |
| Starts a CBOR Array with explicit length of \a count items in the CBOR |
| stream. Each startArray call must be paired with one endArray() call and the |
| current CBOR element extends until the end of the array. |
| |
| The array created by this function has an explicit length and therefore |
| exactly \a count items must be added to the CBOR stream. Adding fewer or |
| more items will result in failure during endArray() and the CBOR stream will |
| be corrupt. However, explicit-length arrays are required by canonical CBOR |
| encoding. |
| |
| The following example appends all strings found in the \l QStringList passed as input: |
| |
| \snippet code/src_corelib_serialization_qcborstream.cpp 21 |
| |
| \b{Size limitations}: The parameter to this function is quint64, which would |
| seem to allow up to 2\sup{64}-1 elements in the array. However, both |
| QCborStreamWriter and QCborStreamReader are currently limited to 2\sup{32}-2 |
| items on 32-bit systems and 2\sup{64}-2 items on 64-bit ones. Also note that |
| QCborArray is currently limited to 2\sup{27} elements in any platform. |
| |
| \sa startArray(), endArray(), startMap(), QCborStreamReader::isArray(), |
| QCborStreamReader::isLengthKnown() |
| */ |
| void QCborStreamWriter::startArray(quint64 count) |
| { |
| d->createContainer(cbor_encoder_create_array, count); |
| } |
| |
| /*! |
| Terminates the array started by either overload of startArray() and returns |
| true if the correct number of elements was added to the array. This function |
| must be called for every startArray() used. |
| |
| A return of false indicates error in the application and an unrecoverable |
| error in this stream. QCborStreamWriter also writes a warning using |
| qWarning() if that happens. |
| |
| Calling this function when the current container is not an array is also an |
| error, though QCborStreamWriter cannot currently detect this condition. |
| |
| \sa startArray(), startArray(quint64), endMap() |
| */ |
| bool QCborStreamWriter::endArray() |
| { |
| return d->closeContainer(); |
| } |
| |
| /*! |
| Starts a CBOR Map with indeterminate length in the CBOR stream. Each |
| startMap() call must be paired with one endMap() call and the current CBOR |
| element extends until the end of the map. |
| |
| The map created by this function has no explicit length. Instead, its length |
| is implied by the elements contained in it. Note, however, that use of |
| indeterminate-length maps is not compliant with canonical CBOR encoding |
| (canonical encoding also requires keys to be unique and in sorted order). |
| |
| The following example appends elements from the linked list of int and |
| string pairs passed as input: |
| |
| \snippet code/src_corelib_serialization_qcborstream.cpp 22 |
| |
| \sa startMap(quint64), endMap(), startArray(), QCborStreamReader::isMap(), |
| QCborStreamReader::isLengthKnown() |
| */ |
| void QCborStreamWriter::startMap() |
| { |
| d->createContainer(cbor_encoder_create_map); |
| } |
| |
| /*! |
| \overload |
| |
| Starts a CBOR Map with explicit length of \a count items in the CBOR |
| stream. Each startMap call must be paired with one endMap() call and the |
| current CBOR element extends until the end of the map. |
| |
| The map created by this function has an explicit length and therefore |
| exactly \a count pairs of items must be added to the CBOR stream. Adding |
| fewer or more items will result in failure during endMap() and the CBOR |
| stream will be corrupt. However, explicit-length map are required by |
| canonical CBOR encoding. |
| |
| The following example appends all strings found in the \l QMap passed as input: |
| |
| \snippet code/src_corelib_serialization_qcborstream.cpp 23 |
| |
| \b{Size limitations}: The parameter to this function is quint64, which would |
| seem to allow up to 2\sup{64}-1 pairs in the map. However, both |
| QCborStreamWriter and QCborStreamReader are currently limited to 2\sup{31}-1 |
| items on 32-bit systems and 2\sup{63}-1 items on 64-bit ones. Also note that |
| QCborMap is currently limited to 2\sup{26} elements in any platform. |
| |
| \sa startMap(), endMap(), startArray(), QCborStreamReader::isMap(), |
| QCborStreamReader::isLengthKnown() |
| */ |
| void QCborStreamWriter::startMap(quint64 count) |
| { |
| d->createContainer(cbor_encoder_create_map, count); |
| } |
| |
| /*! |
| Terminates the map started by either overload of startMap() and returns |
| true if the correct number of elements was added to the array. This function |
| must be called for every startMap() used. |
| |
| A return of false indicates error in the application and an unrecoverable |
| error in this stream. QCborStreamWriter also writes a warning using |
| qWarning() if that happens. |
| |
| Calling this function when the current container is not a map is also an |
| error, though QCborStreamWriter cannot currently detect this condition. |
| |
| \sa startMap(), startMap(quint64), endArray() |
| */ |
| bool QCborStreamWriter::endMap() |
| { |
| return d->closeContainer(); |
| } |
| |
| /*! |
| \class QCborStreamReader |
| \inmodule QtCore |
| \ingroup cbor |
| \reentrant |
| \since 5.12 |
| |
| \brief The QCborStreamReader class is a simple CBOR stream decoder, operating |
| on either a QByteArray or QIODevice. |
| |
| This class can be used to decode a stream of CBOR content directly from |
| either a QByteArray or a QIODevice. CBOR is the Concise Binary Object |
| Representation, a very compact form of binary data encoding that is |
| compatible with JSON. It was created by the IETF Constrained RESTful |
| Environments (CoRE) WG, which has used it in many new RFCs. It is meant to |
| be used alongside the \l{https://tools.ietf.org/html/rfc7252}{CoAP |
| protocol}. |
| |
| QCborStreamReader provides a StAX-like API, similar to that of |
| \l{QXmlStreamReader}. Using it requires a bit of knowledge of CBOR encoding. |
| For a simpler API, see \l{QCborValue} and especially the decoding function |
| QCborValue::fromCbor(). |
| |
| Typically, one creates a QCborStreamReader by passing the source QByteArray |
| or QIODevice as a parameter to the constructor, then pop elements off the |
| stream if there were no errors in decoding. There are three kinds of CBOR |
| types: |
| |
| \table |
| \header \li Kind \li Types \li Behavior |
| \row \li Fixed-width \li Integers, Tags, Simple types, Floating point |
| \li Value is pre-parsed by QCborStreamReader, so accessor functions |
| are \c const. Must call next() to advance. |
| \row \li Strings \li Byte arrays, Text strings |
| \li Length (if known) is pre-parsed, but the string itself is not. |
| The accessor functions are not const and may allocate memory. |
| Once called, the accessor functions automatically advance to |
| the next element. |
| \row \li Containers \li Arrays, Maps |
| \li Length (if known) is pre-parsed. To access the elements, you |
| must call enterContainer(), read all elements, then call |
| leaveContainer(). That function advances to the next element. |
| \endtable |
| |
| So a processor function typically looks like this: |
| |
| \snippet code/src_corelib_serialization_qcborstream.cpp 24 |
| |
| \section1 CBOR support |
| |
| The following table lists the CBOR features that QCborStreamReader supports. |
| |
| \table |
| \header \li Feature \li Support |
| \row \li Unsigned numbers \li Yes (full range) |
| \row \li Negative numbers \li Yes (full range) |
| \row \li Byte strings \li Yes |
| \row \li Text strings \li Yes |
| \row \li Chunked strings \li Yes |
| \row \li Tags \li Yes (arbitrary) |
| \row \li Booleans \li Yes |
| \row \li Null \li Yes |
| \row \li Undefined \li Yes |
| \row \li Arbitrary simple values \li Yes |
| \row \li Half-precision float (16-bit) \li Yes |
| \row \li Single-precision float (32-bit) \li Yes |
| \row \li Double-precision float (64-bit) \li Yes |
| \row \li Infinities and NaN floating point \li Yes |
| \row \li Determinate-length arrays and maps \li Yes |
| \row \li Indeterminate-length arrays and maps \li Yes |
| \row \li Map key types other than strings and integers \li Yes (arbitrary) |
| \endtable |
| |
| \section1 Dealing with invalid or incomplete CBOR streams |
| |
| QCborStreamReader is capable of detecting corrupt input on its own. The |
| library it uses has been extensively tested against invalid input of any |
| kind and is quite able to report errors. If any is detected, |
| QCborStreamReader will set lastError() to a value besides |
| QCborError::NoError, indicating which situation was detected. |
| |
| Most errors detected by QCborStreamReader during normal item parsing are not |
| recoverable. The code using QCborStreamReader may opt to handle the data |
| that was properly decoded or it can opt to discard the entire data. |
| |
| The only recoverable error is QCborError::EndOfFile, which indicates that |
| more data is required in order to complete the parsing. This situation is |
| useful when data is being read from an asynchronous source, such as a pipe |
| (QProcess) or a socket (QTcpSocket, QUdpSocket, QNetworkReply, etc.). When |
| more data arrives, the surrounding code needs to call either addData(), if |
| parsing from a QByteArray, or reparse(), if it is instead reading directly |
| a the QIDOevice that now has more data available (see setDevice()). |
| |
| \sa QCborStreamWriter, QCborValue, QXmlStreamReader |
| */ |
| |
| /*! |
| \enum QCborStreamReader::Type |
| |
| This enumeration contains all possible CBOR types as decoded by |
| QCborStreamReader. CBOR has 7 major types, plus a number of simple types |
| carrying no value, and floating point values. |
| |
| \value UnsignedInteger (Major type 0) Ranges from 0 to 2\sup{64} - 1 |
| (18,446,744,073,709,551,616) |
| \value NegativeInteger (Major type 1) Ranges from -1 to -2\sup{64} |
| (-18,446,744,073,709,551,616) |
| \value ByteArray (Major type 2) Arbitrary binary data. |
| \value ByteString An alias to ByteArray. |
| \value String (Major type 3) Unicode text, possibly containing NULs. |
| \value TextString An alias to String |
| \value Array (Major type 4) Array of heterogeneous items. |
| \value Map (Major type 5) Map/dictionary of heterogeneous items. |
| \value Tag (Major type 6) Numbers giving further semantic value |
| to generic CBOR items. See \l QCborTag for more information. |
| \value SimpleType (Major type 7) Types carrying no further value. Includes |
| booleans (true and false), null, undefined. |
| \value Float16 IEEE 754 half-precision floating point (\c qfloat16). |
| \value HalfFloat An alias to Float16. |
| \value Float IEEE 754 single-precision floating point (\tt float). |
| \value Double IEEE 754 double-precision floating point (\tt double). |
| \value Invalid Not a valid type, either due to parsing error or due to |
| reaching the end of an array or map. |
| */ |
| |
| /*! |
| \enum QCborStreamReader::StringResultCode |
| |
| This enum is returned by readString() and readByteArray() and is used to |
| indicate what the status of the parsing is. |
| |
| \value EndOfString The parsing for the string is complete, with no error. |
| \value Ok The function returned data; there was no error. |
| \value Error Parsing failed with an error. |
| */ |
| |
| /*! |
| \class QCborStreamReader::StringResult |
| \inmodule QtCore |
| |
| This class is returned by readString() and readByteArray(), with either the |
| contents of the string that was read or an indication that the parsing is |
| done or found an error. |
| |
| The contents of \l data are valid only if \l status is |
| \l{StringResultCode}{Ok}. Otherwise, it should be null. |
| */ |
| |
| /*! |
| \variable QCborStreamReader::StringResult::data |
| |
| Contains the actual data from the string if \l status is \c Ok. |
| */ |
| |
| /*! |
| \variable QCborStreamReader::StringResult::status |
| |
| Contains the status of the attempt of reading the string from the stream. |
| */ |
| |
| /*! |
| \fn QCborStreamReader::Type QCborStreamReader::type() const |
| |
| Returns the type of the current element. It is one of the valid types or |
| Invalid. |
| |
| \sa isValid(), isUnsignedInteger(), isNegativeInteger(), isInteger(), |
| isByteArray(), isString(), isArray(), isMap(), isTag(), isSimpleType(), |
| isBool(), isFalse(), isTrue(), isNull(), isUndefined(), isFloat16(), |
| isFloat(), isDouble() |
| */ |
| |
| /*! |
| \fn bool QCborStreamReader::isValid() const |
| |
| Returns true if the current element is valid, false otherwise. The current |
| element may be invalid if there was a decoding error or we've just parsed |
| the last element in an array or map. |
| |
| \note This function is not the opposite of isNull(). Null is a normal CBOR |
| type that must be handled by the application. |
| |
| \sa type(), isInvalid() |
| */ |
| |
| /*! |
| \fn bool QCborStreamReader::isInvalid() const |
| |
| Returns true if the current element is invalid, false otherwise. The current |
| element may be invalid if there was a decoding error or we've just parsed |
| the last element in an array or map. |
| |
| \note This function is not to be confused with isNull(). Null is a normal |
| CBOR type that must be handled by the application. |
| |
| \sa type(), isValid() |
| */ |
| |
| /*! |
| \fn bool QCborStreamReader::isUnsignedInteger() const |
| |
| Returns true if the type of the current element is an unsigned integer (that |
| is if type() returns QCborStreamReader::UnsignedInteger). If this function |
| returns true, you may call toUnsignedInteger() or toInteger() to read that value. |
| |
| \sa type(), toUnsignedInteger(), toInteger(), isInteger(), isNegativeInteger() |
| */ |
| |
| /*! |
| \fn bool QCborStreamReader::isNegativeInteger() const |
| |
| Returns true if the type of the current element is a negative integer (that |
| is if type() returns QCborStreamReader::NegativeInteger). If this function |
| returns true, you may call toNegativeInteger() or toInteger() to read that value. |
| |
| \sa type(), toNegativeInteger(), toInteger(), isInteger(), isUnsignedInteger() |
| */ |
| |
| /*! |
| \fn bool QCborStreamReader::isInteger() const |
| |
| Returns true if the type of the current element is either an unsigned |
| integer or a negative one (that is, if type() returns |
| QCborStreamReader::UnsignedInteger or QCborStreamReader::NegativeInteger). |
| If this function returns true, you may call toInteger() to read that |
| value. |
| |
| \sa type(), toInteger(), toUnsignedInteger(), toNegativeInteger(), |
| isUnsignedInteger(), isNegativeInteger() |
| */ |
| |
| /*! |
| \fn bool QCborStreamReader::isByteArray() const |
| |
| Returns true if the type of the current element is a byte array (that is, |
| if type() returns QCborStreamReader::ByteArray). If this function returns |
| true, you may call readByteArray() to read that data. |
| |
| \sa type(), readByteArray(), isString() |
| */ |
| |
| /*! |
| \fn bool QCborStreamReader::isString() const |
| |
| Returns true if the type of the current element is a text string (that is, |
| if type() returns QCborStreamReader::String). If this function returns |
| true, you may call readString() to read that data. |
| |
| \sa type(), readString(), isByteArray() |
| */ |
| |
| /*! |
| \fn bool QCborStreamReader::isArray() const |
| |
| Returns true if the type of the current element is an array (that is, |
| if type() returns QCborStreamReader::Array). If this function returns |
| true, you may call enterContainer() to begin parsing that container. |
| |
| When the current element is an array, you may also call isLengthKnown() to |
| find out if the array's size is explicit in the CBOR stream. If it is, that |
| size can be obtained by calling length(). |
| |
| The following example pre-allocates a QVariantList given the array's size |
| for more efficient decoding: |
| |
| \snippet code/src_corelib_serialization_qcborstream.cpp 25 |
| |
| \note The code above does not validate that the length is a sensible value. |
| If the input stream reports that the length is 1 billion elements, the above |
| function will try to allocate some 16 GB or more of RAM, which can lead to a |
| crash. |
| |
| \sa type(), isMap(), isLengthKnown(), length(), enterContainer(), leaveContainer() |
| */ |
| |
| /*! |
| \fn bool QCborStreamReader::isMap() const |
| |
| Returns true if the type of the current element is a map (that is, if type() |
| returns QCborStreamReader::Map). If this function returns true, you may call |
| enterContainer() to begin parsing that container. |
| |
| When the current element is a map, you may also call isLengthKnown() to |
| find out if the map's size is explicit in the CBOR stream. If it is, that |
| size can be obtained by calling length(). |
| |
| The following example pre-allocates a QVariantMap given the map's size |
| for more efficient decoding: |
| |
| \snippet code/src_corelib_serialization_qcborstream.cpp 26 |
| |
| The example above uses a function called \c readElementAsString to read the |
| map's keys and obtain a string. That is because CBOR maps may contain any |
| type as keys, not just strings. User code needs to either perform this |
| conversion, reject non-string keys, or instead use a different container |
| besides \l QVariantMap and \l QVariantHash. For example, if the map is |
| expected to contain integer keys, which is recommended as it reduces stream |
| size and parsing, the correct container would be \c{\l{QMap}<int, QVariant>} |
| or \c{\l{QHash}<int, QVariant>}. |
| |
| \note The code above does not validate that the length is a sensible value. |
| If the input stream reports that the length is 1 billion elements, the above |
| function will try to allocate some 24 GB or more of RAM, which can lead to a |
| crash. |
| |
| \sa type(), isArray(), isLengthKnown(), length(), enterContainer(), leaveContainer() |
| */ |
| |
| /*! |
| \fn bool QCborStreamReader::isTag() const |
| |
| Returns true if the type of the current element is a CBOR tag (that is, |
| if type() returns QCborStreamReader::Tag). If this function returns |
| true, you may call toTag() to read that data. |
| |
| \sa type(), toTag() |
| */ |
| |
| /*! |
| \fn bool QCborStreamReader::isFloat16() const |
| |
| Returns true if the type of the current element is an IEEE 754 |
| half-precision floating point (that is, if type() returns |
| QCborStreamReader::Float16). If this function returns true, you may call |
| toFloat16() to read that data. |
| |
| \sa type(), toFloat16(), isFloat(), isDouble() |
| */ |
| |
| /*! |
| \fn bool QCborStreamReader::isFloat() const |
| |
| Returns true if the type of the current element is an IEEE 754 |
| single-precision floating point (that is, if type() returns |
| QCborStreamReader::Float). If this function returns true, you may call |
| toFloat() to read that data. |
| |
| \sa type(), toFloat(), isFloat16(), isDouble() |
| */ |
| |
| /*! |
| \fn bool QCborStreamReader::isDouble() const |
| |
| Returns true if the type of the current element is an IEEE 754 |
| double-precision floating point (that is, if type() returns |
| QCborStreamReader::Double). If this function returns true, you may call |
| toDouble() to read that data. |
| |
| \sa type(), toDouble(), isFloat16(), isFloat() |
| */ |
| |
| /*! |
| \fn bool QCborStreamReader::isSimpleType() const |
| |
| Returns true if the type of the current element is any CBOR simple type, |
| including a boolean value (true and false) as well as null and undefined. To |
| find out which simple type this is, call toSimpleType(). Alternatively, to |
| test for one specific simple type, call the overload that takes a |
| QCborSimpleType parameter. |
| |
| CBOR simple types are types that do not carry extra value. There are 255 |
| possibilities, but there are currently only four values that have defined |
| meaning. Code is not expected to cope with unknown simple types and may |
| simply discard the stream as invalid if it finds an unknown one. |
| |
| \sa QCborSimpleType, type(), isSimpleType(QCborSimpleType), toSimpleType() |
| */ |
| |
| /*! |
| \fn bool QCborStreamReader::isSimpleType(QCborSimpleType st) const |
| |
| Returns true if the type of the current element is the simple type \a st, |
| false otherwise. If this function returns true, then toSimpleType() will |
| return \a st. |
| |
| CBOR simple types are types that do not carry extra value. There are 255 |
| possibilities, but there are currently only four values that have defined |
| meaning. Code is not expected to cope with unknown simple types and may |
| simply discard the stream as invalid if it finds an unknown one. |
| |
| \sa QCborSimpleType, type(), isSimpleType(), toSimpleType() |
| */ |
| |
| /*! |
| \fn bool QCborStreamReader::isFalse() const |
| |
| Returns true if the current element is the \c false value, false if it is |
| anything else. |
| |
| \sa type(), isTrue(), isBool(), toBool(), isSimpleType(), toSimpleType() |
| */ |
| |
| /*! |
| \fn bool QCborStreamReader::isTrue() const |
| |
| Returns true if the current element is the \c true value, false if it is |
| anything else. |
| |
| \sa type(), isFalse(), isBool(), toBool(), isSimpleType(), toSimpleType() |
| */ |
| |
| /*! |
| \fn bool QCborStreamReader::isBool() const |
| |
| Returns true if the current element is a boolean value (\c true or \c |
| false), false if it is anything else. If this function returns true, you may |
| call toBool() to retrieve the value of the boolean. You may also call |
| toSimpleType() and compare to either QCborSimpleValue::True or |
| QCborSimpleValue::False. |
| |
| \sa type(), isFalse(), isTrue(), toBool(), isSimpleType(), toSimpleType() |
| */ |
| |
| /*! |
| \fn bool QCborStreamReader::isNull() const |
| |
| Returns true if the current element is the \c null value, false if it is |
| anything else. Null values may be used to indicate the absence of some |
| optional data. |
| |
| \note This function is not the opposite of isValid(). A Null value is a |
| valid CBOR value. |
| |
| \sa type(), isSimpleType(), toSimpleType() |
| */ |
| |
| /*! |
| \fn bool QCborStreamReader::isUndefined() const |
| |
| Returns true if the current element is the \c undefined value, false if it |
| is anything else. Undefined values may be encoded to indicate that some |
| conversion failed or was not possible when creating the stream. |
| QCborStreamReader never performs any replacement and this function will only |
| return true if the stream contains an explicit undefined value. |
| |
| \sa type(), isSimpleType(), toSimpleType() |
| */ |
| |
| /*! |
| \fn bool QCborStreamReader::isContainer() const |
| |
| Returns true if the current element is a container (that is, an array or a |
| map), false if it is anything else. If the current element is a container, |
| the isLengthKnown() function may be used to find out if the container's size |
| is explicit in the stream and, if so, length() can be used to get that size. |
| |
| More importantly, for a container, the enterContainer() function is |
| available to begin iterating through the elements contained therein. |
| |
| \sa type(), isArray(), isMap(), isLengthKnown(), length(), enterContainer(), |
| leaveContainer(), containerDepth() |
| */ |
| |
| class QCborStreamReaderPrivate |
| { |
| public: |
| enum { |
| // 9 bytes is the maximum size for any integer, floating point or |
| // length in CBOR. |
| MaxCborIndividualSize = 9, |
| IdealIoBufferSize = 256 |
| }; |
| |
| QIODevice *device; |
| QByteArray buffer; |
| QStack<CborValue> containerStack; |
| |
| CborParser parser; |
| CborValue currentElement; |
| QCborError lastError = {}; |
| |
| QByteArray::size_type bufferStart; |
| bool corrupt = false; |
| |
| QCborStreamReaderPrivate(const QByteArray &data) |
| : device(nullptr), buffer(data) |
| { |
| initDecoder(); |
| } |
| |
| QCborStreamReaderPrivate(QIODevice *device) |
| { |
| setDevice(device); |
| } |
| |
| ~QCborStreamReaderPrivate() |
| { |
| } |
| |
| void setDevice(QIODevice *dev) |
| { |
| buffer.clear(); |
| device = dev; |
| initDecoder(); |
| } |
| |
| void initDecoder() |
| { |
| containerStack.clear(); |
| bufferStart = 0; |
| if (device) { |
| buffer.clear(); |
| buffer.reserve(IdealIoBufferSize); // sets the CapacityReserved flag |
| } |
| |
| preread(); |
| if (CborError err = cbor_parser_init_reader(nullptr, &parser, ¤tElement, this)) |
| handleError(err); |
| else |
| lastError = { QCborError::NoError }; |
| } |
| |
| char *bufferPtr() |
| { |
| Q_ASSERT(buffer.isDetached()); |
| return const_cast<char *>(buffer.constData()) + bufferStart; |
| } |
| |
| void preread() |
| { |
| if (device && buffer.size() - bufferStart < MaxCborIndividualSize) { |
| // load more, but only if there's more to be read |
| qint64 avail = device->bytesAvailable(); |
| Q_ASSERT(avail >= buffer.size()); |
| if (avail == buffer.size()) |
| return; |
| |
| if (bufferStart) |
| device->skip(bufferStart); // skip what we've already parsed |
| |
| if (buffer.size() != IdealIoBufferSize) |
| buffer.resize(IdealIoBufferSize); |
| |
| bufferStart = 0; |
| qint64 read = device->peek(bufferPtr(), IdealIoBufferSize); |
| if (read < 0) |
| buffer.clear(); |
| else if (read != IdealIoBufferSize) |
| buffer.truncate(read); |
| } |
| } |
| |
| void handleError(CborError err) noexcept |
| { |
| Q_ASSERT(err); |
| |
| // is the error fatal? |
| if (err != CborErrorUnexpectedEOF) |
| corrupt = true; |
| |
| lastError = fromCborError(err); |
| } |
| |
| void updateBufferAfterString(qsizetype offset, qsizetype size) |
| { |
| Q_ASSERT(device); |
| |
| bufferStart += offset; |
| qsizetype newStart = bufferStart + size; |
| qsizetype remainingInBuffer = buffer.size() - newStart; |
| |
| if (remainingInBuffer <= 0) { |
| // We've read from the QIODevice more than what was in the buffer. |
| buffer.truncate(0); |
| } else { |
| // There's still data buffered, but we need to move it around. |
| char *ptr = buffer.data(); |
| memmove(ptr, ptr + newStart, remainingInBuffer); |
| buffer.truncate(remainingInBuffer); |
| } |
| |
| bufferStart = 0; |
| } |
| |
| bool ensureStringIteration(); |
| }; |
| |
| void qt_cbor_stream_set_error(QCborStreamReaderPrivate *d, QCborError error) |
| { |
| d->handleError(CborError(error.c)); |
| } |
| |
| static inline bool qt_cbor_decoder_can_read(void *token, size_t len) |
| { |
| Q_ASSERT(len <= QCborStreamReaderPrivate::MaxCborIndividualSize); |
| auto self = static_cast<QCborStreamReaderPrivate *>(token); |
| |
| qint64 avail = self->buffer.size() - self->bufferStart; |
| return len <= quint64(avail); |
| } |
| |
| static void qt_cbor_decoder_advance(void *token, size_t len) |
| { |
| Q_ASSERT(len <= QCborStreamReaderPrivate::MaxCborIndividualSize); |
| auto self = static_cast<QCborStreamReaderPrivate *>(token); |
| Q_ASSERT(len <= size_t(self->buffer.size() - self->bufferStart)); |
| |
| self->bufferStart += int(len); |
| self->preread(); |
| } |
| |
| static void *qt_cbor_decoder_read(void *token, void *userptr, size_t offset, size_t len) |
| { |
| Q_ASSERT(len == 1 || len == 2 || len == 4 || len == 8); |
| Q_ASSERT(offset == 0 || offset == 1); |
| auto self = static_cast<const QCborStreamReaderPrivate *>(token); |
| |
| // we must have pre-read the data |
| Q_ASSERT(len + offset <= size_t(self->buffer.size() - self->bufferStart)); |
| return memcpy(userptr, self->buffer.constData() + self->bufferStart + offset, len); |
| } |
| |
| static CborError qt_cbor_decoder_transfer_string(void *token, const void **userptr, size_t offset, size_t len) |
| { |
| auto self = static_cast<QCborStreamReaderPrivate *>(token); |
| Q_ASSERT(offset <= size_t(self->buffer.size())); |
| Q_STATIC_ASSERT(sizeof(size_t) >= sizeof(QByteArray::size_type)); |
| Q_STATIC_ASSERT(sizeof(size_t) == sizeof(qsizetype)); |
| |
| // check that we will have enough data from the QIODevice before we advance |
| // (otherwise, we'd lose the length information) |
| qsizetype total; |
| if (len > size_t(std::numeric_limits<QByteArray::size_type>::max()) |
| || add_overflow<qsizetype>(offset, len, &total)) |
| return CborErrorDataTooLarge; |
| |
| // our string transfer is just saving the offset to the userptr |
| *userptr = reinterpret_cast<void *>(offset); |
| |
| qint64 avail = (self->device ? self->device->bytesAvailable() : self->buffer.size()) - |
| self->bufferStart; |
| return total > avail ? CborErrorUnexpectedEOF : CborNoError; |
| } |
| |
| bool QCborStreamReaderPrivate::ensureStringIteration() |
| { |
| if (currentElement.flags & CborIteratorFlag_IteratingStringChunks) |
| return true; |
| |
| CborError err = cbor_value_begin_string_iteration(¤tElement); |
| if (!err) |
| return true; |
| handleError(err); |
| return false; |
| } |
| |
| /*! |
| \internal |
| */ |
| inline void QCborStreamReader::preparse() |
| { |
| if (lastError() == QCborError::NoError) { |
| type_ = cbor_value_get_type(&d->currentElement); |
| |
| if (type_ == CborInvalidType) { |
| // We may have reached the end. |
| if (d->device && d->containerStack.isEmpty()) { |
| d->buffer.clear(); |
| if (d->bufferStart) |
| d->device->skip(d->bufferStart); |
| d->bufferStart = 0; |
| } |
| } else { |
| d->lastError = {}; |
| // Undo the type mapping that TinyCBOR does (we have an explicit type |
| // for negative integer and we don't have separate types for Boolean, |
| // Null and Undefined). |
| if (type_ == CborBooleanType || type_ == CborNullType || type_ == CborUndefinedType) { |
| type_ = CborSimpleType; |
| value64 = quint8(d->buffer.at(d->bufferStart)) - CborSimpleType; |
| } else { |
| // Using internal TinyCBOR API! |
| value64 = _cbor_value_extract_int64_helper(&d->currentElement); |
| |
| if (cbor_value_is_negative_integer(&d->currentElement)) |
| type_ = quint8(QCborStreamReader::NegativeInteger); |
| } |
| } |
| } else { |
| type_ = Invalid; |
| } |
| } |
| |
| /*! |
| Creates a QCborStreamReader object with no source data. After construction, |
| QCborStreamReader will report an error parsing. |
| |
| You can add more data by calling addData() or by setting a different source |
| device using setDevice(). |
| |
| \sa addData(), isValid() |
| */ |
| QCborStreamReader::QCborStreamReader() |
| : QCborStreamReader(QByteArray()) |
| { |
| } |
| |
| /*! |
| \overload |
| |
| Creates a QCborStreamReader object with \a len bytes of data starting at \a |
| data. The pointer must remain valid until QCborStreamReader is destroyed. |
| */ |
| QCborStreamReader::QCborStreamReader(const char *data, qsizetype len) |
| : QCborStreamReader(QByteArray::fromRawData(data, len)) |
| { |
| } |
| |
| /*! |
| \overload |
| |
| Creates a QCborStreamReader object with \a len bytes of data starting at \a |
| data. The pointer must remain valid until QCborStreamReader is destroyed. |
| */ |
| QCborStreamReader::QCborStreamReader(const quint8 *data, qsizetype len) |
| : QCborStreamReader(QByteArray::fromRawData(reinterpret_cast<const char *>(data), len)) |
| { |
| } |
| |
| /*! |
| \overload |
| |
| Creates a QCborStreamReader object that will parse the CBOR stream found in |
| \a data. |
| */ |
| QCborStreamReader::QCborStreamReader(const QByteArray &data) |
| : d(new QCborStreamReaderPrivate(data)) |
| { |
| preparse(); |
| } |
| |
| /*! |
| \overload |
| |
| Creates a QCborStreamReader object that will parse the CBOR stream found by |
| reading from \a device. QCborStreamReader does not take ownership of \a |
| device, so it must remain valid until this oject is destroyed. |
| */ |
| QCborStreamReader::QCborStreamReader(QIODevice *device) |
| : d(new QCborStreamReaderPrivate(device)) |
| { |
| preparse(); |
| } |
| |
| /*! |
| Destroys this QCborStreamReader object and frees any associated resources. |
| */ |
| QCborStreamReader::~QCborStreamReader() |
| { |
| } |
| |
| /*! |
| Sets the source of data to \a device, resetting the decoder to its initial |
| state. |
| */ |
| void QCborStreamReader::setDevice(QIODevice *device) |
| { |
| d->setDevice(device); |
| preparse(); |
| } |
| |
| /*! |
| Returns the QIODevice that was set with either setDevice() or the |
| QCborStreamReader constructor. If this object was reading from a QByteArray, |
| this function returns nullptr instead. |
| */ |
| QIODevice *QCborStreamReader::device() const |
| { |
| return d->device; |
| } |
| |
| /*! |
| Adds \a data to the CBOR stream and reparses the current element. This |
| function is useful if the end of the data was previously reached while |
| processing the stream, but now more data is available. |
| */ |
| void QCborStreamReader::addData(const QByteArray &data) |
| { |
| addData(data.constData(), data.size()); |
| } |
| |
| /*! |
| \fn void QCborStreamReader::addData(const quint8 *data, qsizetype len) |
| \overload |
| |
| Adds \a len bytes of data starting at \a data to the CBOR stream and |
| reparses the current element. This function is useful if the end of the data |
| was previously reached while processing the stream, but now more data is |
| available. |
| */ |
| |
| /*! |
| \overload |
| |
| Adds \a len bytes of data starting at \a data to the CBOR stream and |
| reparses the current element. This function is useful if the end of the data |
| was previously reached while processing the stream, but now more data is |
| available. |
| */ |
| void QCborStreamReader::addData(const char *data, qsizetype len) |
| { |
| if (!d->device) { |
| if (len > 0) |
| d->buffer.append(data, len); |
| reparse(); |
| } else { |
| qWarning("QCborStreamReader: addData() with device()"); |
| } |
| } |
| |
| /*! |
| Reparses the current element. This function must be called when more data |
| becomes available in the source QIODevice after parsing failed due to |
| reaching the end of the input data before the end of the CBOR stream. |
| |
| When reading from QByteArray(), the addData() function automatically calls |
| this function. Calling it when the reading had not failed is a no-op. |
| */ |
| void QCborStreamReader::reparse() |
| { |
| d->lastError = {}; |
| d->preread(); |
| if (CborError err = cbor_value_reparse(&d->currentElement)) |
| d->handleError(err); |
| else |
| preparse(); |
| } |
| |
| /*! |
| Clears the decoder state and resets the input source data to an empty byte |
| array. After this function is called, QCborStreamReader will be indicating |
| an error parsing. |
| |
| Call addData() to add more data to be parsed. |
| |
| \sa reset(), setDevice() |
| */ |
| void QCborStreamReader::clear() |
| { |
| setDevice(nullptr); |
| } |
| |
| /*! |
| Resets the source back to the beginning and clears the decoder state. If the |
| source data was a QByteArray, QCborStreamReader will restart from the |
| beginning of the array. |
| |
| If the source data is a QIODevice, this function will call |
| QIODevice::reset(), which will seek to byte position 0. If the CBOR stream |
| is not found at the beginning of the device (e.g., beginning of a file), |
| then this function will likely do the wrong thing. Instead, position the |
| QIODevice to the right offset and call setDevice(). |
| |
| \sa clear(), setDevice() |
| */ |
| void QCborStreamReader::reset() |
| { |
| if (d->device) |
| d->device->reset(); |
| d->lastError = {}; |
| d->initDecoder(); |
| preparse(); |
| } |
| |
| /*! |
| Returns the last error in decoding the stream, if any. If no error |
| was encountered, this returns an QCborError::NoError. |
| |
| \sa isValid() |
| */ |
| QCborError QCborStreamReader::lastError() |
| { |
| return d->lastError; |
| } |
| |
| /*! |
| Returns the offset in the input stream of the item currently being decoded. |
| The current offset is the number of decoded bytes so far only if the source |
| data is a QByteArray or it is a QIODevice that was positioned at its |
| beginning when decoding started. |
| |
| \sa reset(), clear(), device() |
| */ |
| qint64 QCborStreamReader::currentOffset() const |
| { |
| return (d->device ? d->device->pos() : 0) + d->bufferStart; |
| } |
| |
| /*! |
| Returns the number of containers that this stream has entered with |
| enterContainer() but not yet left. |
| |
| \sa enterContainer(), leaveContainer() |
| */ |
| int QCborStreamReader::containerDepth() const |
| { |
| return d->containerStack.size(); |
| } |
| |
| /*! |
| Returns either QCborStreamReader::Array or QCborStreamReader::Map, |
| indicating whether the container that contains the current item was an array |
| or map, respectively. If we're currently parsing the root element, this |
| function returns QCborStreamReader::Invalid. |
| |
| \sa containerDepth(), enterContainer() |
| */ |
| QCborStreamReader::Type QCborStreamReader::parentContainerType() const |
| { |
| if (d->containerStack.isEmpty()) |
| return Invalid; |
| return Type(cbor_value_get_type(&qAsConst(d->containerStack).top())); |
| } |
| |
| /*! |
| Returns true if there are more items to be decoded in the current container |
| or false of we've reached its end. If we're parsing the root element, |
| hasNext() returning false indicates the parsing is complete; otherwise, if |
| the container depth is non-zero, then the outer code needs to call |
| leaveContainer(). |
| |
| \sa parentContainerType(), containerDepth(), leaveContainer() |
| */ |
| bool QCborStreamReader::hasNext() const noexcept |
| { |
| return cbor_value_is_valid(&d->currentElement) && |
| !cbor_value_at_end(&d->currentElement); |
| } |
| |
| /*! |
| Advance the CBOR stream decoding one element. You should usually call this |
| function when parsing fixed-width basic elements (that is, integers, simple |
| values, tags and floating point values). But this function can be called |
| when the current item is a string, array or map too and it will skip over |
| that entire element, including all contained elements. |
| |
| This function returns true if advancing was successful, false otherwise. It |
| may fail if the stream is corrupt, incomplete or if the nesting level of |
| arrays and maps exceeds \a maxRecursion. Calling this function when |
| hasNext() has returned false is also an error. If this function returns |
| false, lastError() will return the error code detailing what the failure |
| was. |
| |
| \sa lastError(), isValid(), hasNext() |
| */ |
| bool QCborStreamReader::next(int maxRecursion) |
| { |
| if (lastError() != QCborError::NoError) |
| return false; |
| |
| if (!hasNext()) { |
| d->handleError(CborErrorAdvancePastEOF); |
| } else if (maxRecursion < 0) { |
| d->handleError(CborErrorNestingTooDeep); |
| } else if (isContainer()) { |
| // iterate over each element |
| enterContainer(); |
| while (lastError() == QCborError::NoError && hasNext()) |
| next(maxRecursion - 1); |
| if (lastError() == QCborError::NoError) |
| leaveContainer(); |
| } else if (isString() || isByteArray()) { |
| auto r = _readByteArray_helper(); |
| while (r.status == Ok) { |
| if (isString() && !QUtf8::isValidUtf8(r.data, r.data.size()).isValidUtf8) { |
| d->handleError(CborErrorInvalidUtf8TextString); |
| break; |
| } |
| r = _readByteArray_helper(); |
| } |
| } else { |
| // fixed types |
| CborError err = cbor_value_advance_fixed(&d->currentElement); |
| if (err) |
| d->handleError(err); |
| } |
| |
| preparse(); |
| return d->lastError == QCborError::NoError; |
| } |
| |
| /*! |
| Returns true if the length of the current array, map, byte array or string |
| is known (explicit in the CBOR stream), false otherwise. This function |
| should only be called if the element is one of those. |
| |
| If the length is known, it may be obtained by calling length(). |
| |
| If the length of a map or an array is not known, it is implied by the number |
| of elements present in the stream. QCborStreamReader has no API to calculate |
| the length in that condition. |
| |
| Strings and byte arrays may also have indeterminate length (that is, they |
| may be transmitted in multiple chunks). Those cannot currently be created |
| with QCborStreamWriter, but they could be with other encoders, so |
| QCborStreamReader supports them. |
| |
| \sa length(), QCborStreamWriter::startArray(), QCborStreamWriter::startMap() |
| */ |
| bool QCborStreamReader::isLengthKnown() const noexcept |
| { |
| return cbor_value_is_length_known(&d->currentElement); |
| } |
| |
| /*! |
| Returns the length of the string or byte array, or the number of items in an |
| array or the number, of item pairs in a map, if known. This function must |
| not be called if the length is unknown (that is, if isLengthKnown() returned |
| false). It is an error to do that and it will cause QCborStreamReader to |
| stop parsing the input stream. |
| |
| \sa isLengthKnown(), QCborStreamWriter::startArray(), QCborStreamWriter::startMap() |
| */ |
| quint64 QCborStreamReader::length() const |
| { |
| CborError err; |
| switch (type()) { |
| case String: |
| case ByteArray: |
| case Map: |
| case Array: |
| if (isLengthKnown()) |
| return value64; |
| err = CborErrorUnknownLength; |
| break; |
| |
| default: |
| err = CborErrorIllegalType; |
| break; |
| } |
| |
| d->handleError(err); |
| return quint64(-1); |
| } |
| |
| /*! |
| \fn bool QCborStreamReader::enterContainer() |
| |
| Enters the array or map that is the current item and prepares for iterating |
| the elements contained in the container. Returns true if entering the |
| container succeeded, false otherwise (usually, a parsing error). Each call |
| to enterContainer() must be paired with a call to leaveContainer(). |
| |
| This function may only be called if the current item is an array or a map |
| (that is, if isArray(), isMap() or isContainer() is true). Calling it in any |
| other condition is an error. |
| |
| \sa leaveContainer(), isContainer(), isArray(), isMap() |
| */ |
| bool QCborStreamReader::_enterContainer_helper() |
| { |
| d->containerStack.push(d->currentElement); |
| CborError err = cbor_value_enter_container(&d->containerStack.top(), &d->currentElement); |
| if (!err) { |
| preparse(); |
| return true; |
| } |
| d->handleError(err); |
| return false; |
| } |
| |
| /*! |
| Leaves the array or map whose items were being processed and positions the |
| decoder at the next item after the end of the container. Returns true if |
| leaving the container succeeded, false otherwise (usually, a parsing error). |
| Each call to enterContainer() must be paired with a call to |
| leaveContainer(). |
| |
| This function may only be called if hasNext() has returned false and |
| containerDepth() is not zero. Calling it in any other condition is an error. |
| |
| \sa enterContainer(), parentContainerType(), containerDepth() |
| */ |
| bool QCborStreamReader::leaveContainer() |
| { |
| if (d->containerStack.isEmpty()) { |
| qWarning("QCborStreamReader::leaveContainer: trying to leave top-level element"); |
| return false; |
| } |
| if (d->corrupt) |
| return false; |
| |
| CborValue container = d->containerStack.pop(); |
| CborError err = cbor_value_leave_container(&container, &d->currentElement); |
| d->currentElement = container; |
| if (err) { |
| d->handleError(err); |
| return false; |
| } |
| |
| preparse(); |
| return true; |
| } |
| |
| /*! |
| \fn bool QCborStreamReader::toBool() const |
| |
| Returns the boolean value of the current element. |
| |
| This function does not perform any type conversions, including from integer. |
| Therefore, it may only be called if isTrue(), isFalse() or isBool() returned |
| true; calling it in any other condition is an error. |
| |
| \sa isBool(), isTrue(), isFalse(), toInteger() |
| */ |
| |
| /*! |
| \fn QCborTag QCborStreamReader::toTag() const |
| |
| Returns the tag value of the current element. |
| |
| This function does not perform any type conversions, including from integer. |
| Therefore, it may only be called if isTag() is true; calling it in any other |
| condition is an error. |
| |
| Tags are 64-bit numbers attached to generic CBOR types that give them |
| further meaning. For a list of known tags, see the \l QCborKnownTags |
| enumeration. |
| |
| \sa isTag(), toInteger(), QCborKnownTags |
| */ |
| |
| /*! |
| \fn quint64 QCborStreamReader::toUnsignedInteger() const |
| |
| Returns the unsigned integer value of the current element. |
| |
| This function does not perform any type conversions, including from boolean |
| or CBOR tag. Therefore, it may only be called if isUnsignedInteger() is |
| true; calling it in any other condition is an error. |
| |
| This function may be used to obtain numbers beyond the range of the return |
| type of toInteger(). |
| |
| \sa type(), toInteger(), isUnsignedInteger(), isNegativeInteger() |
| */ |
| |
| /*! |
| \fn QCborNegativeValue QCborStreamReader::toNegativeInteger() const |
| |
| Returns the negative integer value of the current element. |
| QCborNegativeValue is a 64-bit unsigned integer containing the absolute |
| value of the negative number that was stored in the CBOR stream. |
| Additionally, QCborNegativeValue(0) represents the number -2\sup{64}. |
| |
| This function does not perform any type conversions, including from boolean |
| or CBOR tag. Therefore, it may only be called if isNegativeInteger() is |
| true; calling it in any other condition is an error. |
| |
| This function may be used to obtain numbers beyond the range of the return |
| type of toInteger(). However, use of negative numbers smaller than -2\sup{63} |
| is extremely discouraged. |
| |
| \sa type(), toInteger(), isNegativeInteger(), isUnsignedInteger() |
| */ |
| |
| /*! |
| \fn qint64 QCborStreamReader::toInteger() const |
| |
| Returns the integer value of the current element, be it negative, positive |
| or zero. If the value is larger than 2\sup{63} - 1 or smaller than |
| -2\sup{63}, the returned value will overflow and will have an incorrect |
| sign. If handling those values is required, use toUnsignedInteger() or |
| toNegativeInteger() instead. |
| |
| This function does not perform any type conversions, including from boolean |
| or CBOR tag. Therefore, it may only be called if isInteger() is true; |
| calling it in any other condition is an error. |
| |
| \sa isInteger(), toUnsignedInteger(), toNegativeInteger() |
| */ |
| |
| /*! |
| \fn QCborSimpleType QCborStreamReader::toSimpleType() const |
| |
| Returns value of the current simple type. |
| |
| This function does not perform any type conversions, including from integer. |
| Therefore, it may only be called if isSimpleType() is true; calling it in |
| any other condition is an error. |
| |
| \sa isSimpleType(), isTrue(), isFalse(), isBool(), isNull(), isUndefined() |
| */ |
| |
| /*! |
| \fn qfloat16 QCborStreamReader::toFloat16() const |
| |
| Returns the 16-bit half-precision floating point value of the current element. |
| |
| This function does not perform any type conversions, including from other |
| floating point types or from integer values. Therefore, it may only be |
| called if isFloat16() is true; calling it in any other condition is an |
| error. |
| |
| \sa isFloat16(), toFloat(), toDouble() |
| */ |
| |
| /*! |
| \fn float QCborStreamReader::toFloat() const |
| |
| Returns the 32-bit single-precision floating point value of the current |
| element. |
| |
| This function does not perform any type conversions, including from other |
| floating point types or from integer values. Therefore, it may only be |
| called if isFloat() is true; calling it in any other condition is an error. |
| |
| \sa isFloat(), toFloat16(), toDouble() |
| */ |
| |
| /*! |
| \fn double QCborStreamReader::toDouble() const |
| |
| Returns the 64-bit double-precision floating point value of the current |
| element. |
| |
| This function does not perform any type conversions, including from other |
| floating point types or from integer values. Therefore, it may only be |
| called if isDouble() is true; calling it in any other condition is an error. |
| |
| \sa isDouble(), toFloat16(), toFloat() |
| */ |
| |
| /*! |
| \fn QCborStreamReader::StringResult<QString> QCborStreamReader::readString() |
| |
| Decodes one string chunk from the CBOR string and returns it. This function |
| is used for both regular and chunked string contents, so the caller must |
| always loop around calling this function, even if isLengthKnown() has |
| is true. The typical use of this function is as follows: |
| |
| \snippet code/src_corelib_serialization_qcborstream.cpp 27 |
| |
| This function does not perform any type conversions, including from integers |
| or from byte arrays. Therefore, it may only be called if isString() returned |
| true; calling it in any other condition is an error. |
| |
| \sa readByteArray(), isString(), readStringChunk() |
| */ |
| QCborStreamReader::StringResult<QString> QCborStreamReader::_readString_helper() |
| { |
| auto r = _readByteArray_helper(); |
| QCborStreamReader::StringResult<QString> result; |
| result.status = r.status; |
| |
| if (r.status == Ok) { |
| QTextCodec::ConverterState cs; |
| result.data = QUtf8::convertToUnicode(r.data, r.data.size(), &cs); |
| if (cs.invalidChars == 0 && cs.remainingChars == 0) |
| return result; |
| |
| d->handleError(CborErrorInvalidUtf8TextString); |
| result.data.clear(); |
| result.status = Error; |
| return result; |
| } |
| return result; |
| } |
| |
| /*! |
| \fn QCborStreamReader::StringResult<QString> QCborStreamReader::readByteArray() |
| |
| Decodes one byte array chunk from the CBOR string and returns it. This |
| function is used for both regular and chunked contents, so the caller must |
| always loop around calling this function, even if isLengthKnown() has |
| is true. The typical use of this function is as follows: |
| |
| \snippet code/src_corelib_serialization_qcborstream.cpp 28 |
| |
| This function does not perform any type conversions, including from integers |
| or from strings. Therefore, it may only be called if isByteArray() is true; |
| calling it in any other condition is an error. |
| |
| \sa readString(), isByteArray(), readStringChunk() |
| */ |
| QCborStreamReader::StringResult<QByteArray> QCborStreamReader::_readByteArray_helper() |
| { |
| QCborStreamReader::StringResult<QByteArray> result; |
| result.status = Error; |
| qsizetype len = _currentStringChunkSize(); |
| if (len < 0) |
| return result; |
| |
| result.data.resize(len); |
| auto r = readStringChunk(result.data.data(), len); |
| Q_ASSERT(r.status != Ok || r.data == len); |
| result.status = r.status; |
| return result; |
| } |
| |
| /*! |
| \fn qsizetype QCborStreamReader::currentStringChunkSize() const |
| |
| Returns the size of the current text or byte string chunk. If the CBOR |
| stream contains a non-chunked string (that is, if isLengthKnown() returns |
| \c true), this function returns the size of the entire string, the same as |
| length(). |
| |
| This function is useful to pre-allocate the buffer whose pointer can be passed |
| to readStringChunk() later. |
| |
| \sa readString(), readByteArray(), readStringChunk() |
| */ |
| qsizetype QCborStreamReader::_currentStringChunkSize() const |
| { |
| if (!d->ensureStringIteration()) |
| return -1; |
| |
| size_t len; |
| CborError err = cbor_value_get_string_chunk_size(&d->currentElement, &len); |
| if (err == CborErrorNoMoreStringChunks) |
| return 0; // not a real error |
| else if (err) |
| d->handleError(err); |
| else if (qsizetype(len) < 0) |
| d->handleError(CborErrorDataTooLarge); |
| else |
| return qsizetype(len); |
| return -1; |
| } |
| |
| /*! |
| Reads the current string chunk into the buffer pointed to by \a ptr, whose |
| size is \a maxlen. This function returns a \l StringResult object, with the |
| number of bytes copied into \a ptr saved in the \c \l StringResult::data |
| member. The \c \l StringResult::status member indicates whether there was |
| an error reading the string, whether data was copied or whether this was |
| the last chunk. |
| |
| This function can be called for both \l String and \l ByteArray types. |
| For the latter, this function will read the same data that readByteArray() |
| would have returned. For strings, it returns the UTF-8 equivalent of the \l |
| QString that would have been returned. |
| |
| This function is usually used alongside currentStringChunkSize() in a loop. |
| For example: |
| |
| \snippet code/src_corelib_serialization_qcborstream.cpp 29 |
| |
| Unlike readByteArray() and readString(), this function is not limited by |
| implementation limits of QByteArray and QString. |
| |
| \note This function does not perform verification that the UTF-8 contents |
| are properly formatted. That means this function does not produce the |
| QCborError::InvalidUtf8String error, even when readString() does. |
| |
| \sa currentStringChunkSize(), readString(), readByteArray(), |
| isString(), isByteArray() |
| */ |
| QCborStreamReader::StringResult<qsizetype> |
| QCborStreamReader::readStringChunk(char *ptr, qsizetype maxlen) |
| { |
| CborError err; |
| size_t len; |
| const void *content = nullptr; |
| QCborStreamReader::StringResult<qsizetype> result; |
| result.data = 0; |
| result.status = Error; |
| |
| d->lastError = {}; |
| if (!d->ensureStringIteration()) |
| return result; |
| |
| #if 1 |
| // Using internal TinyCBOR API! |
| err = _cbor_value_get_string_chunk(&d->currentElement, &content, &len, &d->currentElement); |
| #else |
| // the above is effectively the same as: |
| if (cbor_value_is_byte_string(¤tElement)) |
| err = cbor_value_get_byte_string_chunk(&d->currentElement, reinterpret_cast<const uint8_t **>(&content), |
| &len, &d->currentElement); |
| else |
| err = cbor_value_get_text_string_chunk(&d->currentElement, reinterpret_cast<const char **>(&content), |
| &len, &d->currentElement); |
| #endif |
| |
| // Range check: using implementation-defined behavior in converting an |
| // unsigned value out of range of the destination signed type (same as |
| // "len > size_t(std::numeric_limits<qsizetype>::max())", but generates |
| // better code with ICC and MSVC). |
| if (!err && qsizetype(len) < 0) |
| err = CborErrorDataTooLarge; |
| |
| if (err) { |
| if (err == CborErrorNoMoreStringChunks) { |
| d->preread(); |
| err = cbor_value_finish_string_iteration(&d->currentElement); |
| result.status = EndOfString; |
| } |
| if (err) |
| d->handleError(err); |
| else |
| preparse(); |
| return result; |
| } |
| |
| // Read the chunk into the user's buffer. |
| qint64 actuallyRead; |
| qptrdiff offset = qptrdiff(content); |
| qsizetype toRead = qsizetype(len); |
| qsizetype left = toRead - maxlen; |
| if (left < 0) |
| left = 0; // buffer bigger than string |
| else |
| toRead = maxlen; // buffer smaller than string |
| |
| if (d->device) { |
| // This first skip can't fail because we've already read this many bytes. |
| d->device->skip(d->bufferStart + qptrdiff(content)); |
| actuallyRead = d->device->read(ptr, toRead); |
| |
| if (actuallyRead != toRead) { |
| actuallyRead = -1; |
| } else if (left) { |
| qint64 skipped = d->device->skip(left); |
| if (skipped != left) |
| actuallyRead = -1; |
| } |
| |
| if (actuallyRead < 0) { |
| d->handleError(CborErrorIO); |
| return result; |
| } |
| |
| d->updateBufferAfterString(offset, len); |
| } else { |
| actuallyRead = toRead; |
| memcpy(ptr, d->buffer.constData() + d->bufferStart + offset, toRead); |
| d->bufferStart += QByteArray::size_type(offset + len); |
| } |
| |
| d->preread(); |
| result.data = actuallyRead; |
| result.status = Ok; |
| return result; |
| } |
| |
| QT_END_NAMESPACE |
| |
| #include "moc_qcborcommon.cpp" |
| #include "moc_qcborstream.cpp" |