diff options
Diffstat (limited to 'src/corelib/serialization')
20 files changed, 11626 insertions, 27 deletions
diff --git a/src/corelib/serialization/qcborarray.cpp b/src/corelib/serialization/qcborarray.cpp new file mode 100644 index 0000000000..506fb052be --- /dev/null +++ b/src/corelib/serialization/qcborarray.cpp @@ -0,0 +1,1198 @@ +/**************************************************************************** +** +** 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 "qcborarray.h" +#include "qcborvalue_p.h" + +QT_BEGIN_NAMESPACE + +using namespace QtCbor; + +/*! + \class QCborArray + \inmodule QtCore + \ingroup cbor + \reentrant + \since 5.12 + + \brief The QCborArray class is used to hold an array of CBOR elements. + + This class can be used to hold one sequential container in CBOR (an array). + CBOR is the Concise Binary Object Representation, a very compact form of + binary data encoding that is a superset of 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}. + + QCborArray is very similar to \l QVariantList and \l QJsonArray and its API + is almost identical to those two classes. It can also be converted to and + from those two, though there may be loss of information in some + conversions. + + \sa QCborValue, QCborMap, QJsonArray, QList, QVector + */ + +/*! + \typedef QCborArray::size_type + + A typedef to qsizetype. + */ + +/*! + \typedef QCborArray::difference_type + + A typedef to qsizetype. + */ + +/*! + \typedef QCborArray::value_type + + The type of values that can be held in a QCborArray: that is, QCborValue. + */ + +/*! + \typedef QCborArray::pointer + + A typedef to \c{QCborValue *}, for compatibility with generic algorithms. + */ + +/*! + \typedef QCborArray::const_pointer + + A typedef to \c{const QCborValue *}, for compatibility with generic algorithms. + */ + +/*! + \typedef QCborArray::reference + + A typedef to \c{QCborValue &}, for compatibility with generic algorithms. + */ + +/*! + \typedef QCborArray::const_reference + + A typedef to \c{const QCborValue &}, for compatibility with generic algorithms. + */ + +/*! + Constructs an empty QCborArray. + */ +QCborArray::QCborArray() noexcept + : d(nullptr) +{ +} + +/*! + Copies the contents of \a other into this object. + */ +QCborArray::QCborArray(const QCborArray &other) noexcept + : d(other.d) +{ +} + +/*! + \fn QCborArray::QCborArray(std::initializer_list<QCborValue> args) + + Initializes this QCborArray from the C++ brace-enclosed list found in \a + args, as in the following example: + + \code + QCborArray a = { null, 0, 1, 1.5, 2, "Hello", QByteArray("World") }; + \endcode + */ + +/*! + Destroys this QCborArray and frees any associated resources. + */ +QCborArray::~QCborArray() +{ +} + +/*! + Replaces the contents of this array with that found in \a other, then + returns a reference to this object. + */ +QCborArray &QCborArray::operator=(const QCborArray &other) noexcept +{ + d = other.d; + return *this; +} + +/*! + \fn void QCborArray::swap(QCborArray &other) + + Swaps the contents of this object and \a other. + */ + +/*! + \fn QCborValue QCborArray::toCborValue() const + + Explicitly construcuts a \l QCborValue object that represents this array. + This function is usually not necessary since QCborValue has a constructor + for QCborArray, so the conversion is implicit. + + Converting QCborArray to QCborValue allows it to be used in any context + where QCborValues can be used, including as items in QCborArrays and as keys + and mapped types in QCborMap. Converting an array to QCborValue allows + access to QCborValue::toCbor(). + + \sa QCborValue::QCborValue(const QCborArray &) + */ + +/*! + Returns the size of this array. + + \sa isEmpty() + */ +qsizetype QCborArray::size() const noexcept +{ + return d ? d->elements.size() : 0; +} + +/*! + \fn bool QCborArray::isEmpty() const + + Returns true if this QCborArray is empty (that is if size() is 0). + + \sa size() + */ + +/*! + Returns the QCborValue element at position \a i in the array. + + If the array is smaller than \a i elements, this function returns a + QCborValue containing an undefined value. For that reason, it is not + possible with this function to tell apart the situation where the array is + not large enough from the case where the array starts with an undefined + value. + + \sa operator[](), first(), last(), insert(), prepend(), append(), + removeAt(), takeAt() + */ +QCborValue QCborArray::at(qsizetype i) const +{ + if (!d || size_t(i) >= size_t(size())) + return QCborValue(); + return d->valueAt(i); +} + +/*! + \fn QCborValue QCborArray::first() const + + Returns the first QCborValue of this array. + + If the array is empty, this function returns a QCborValue containing an + undefined value. For that reason, it is not possible with this function to + tell apart the situation where the array is not large enough from the case + where the array ends with an undefined value. + + \sa operator[](), at(), last(), insert(), prepend(), append(), + removeAt(), takeAt() + */ + +/*! + \fn QCborValue QCborArray::last() const + + Returns the last QCborValue of this array. + + If the array is empty, this function returns a QCborValue containing an + undefined value. For that reason, it is not possible with this function to + tell apart the situation where the array is not large enough from the case + where the array ends with an undefined value. + + \sa operator[](), at(), first(), insert(), prepend(), append(), + removeAt(), takeAt() + */ + +/*! + \fn QCborValue QCborArray::operator[](qsizetype i) const + + Returns the QCborValue element at position \a i in the array. + + If the array is smaller than \a i elements, this function returns a + QCborValue containing an undefined value. For that reason, it is not + possible with this function to tell apart the situation where the array is + not large enough from the case where the array contains an undefined value + at position \a i. + + \sa at(), first(), last(), insert(), prepend(), append(), + removeAt(), takeAt() + */ + +/*! + \fn QCborValueRef QCborArray::first() + + Returns a reference to the first QCborValue of this array. The array must + not be empty. + + QCborValueRef has the exact same API as \l QCborValue, with one important + difference: if you assign new values to it, this map will be updated with + that new value. + + \sa operator[](), at(), last(), insert(), prepend(), append(), + removeAt(), takeAt() + */ + +/*! + \fn QCborValueRef QCborArray::last() + + Returns a reference to the last QCborValue of this array. The array must + not be empty. + + QCborValueRef has the exact same API as \l QCborValue, with one important + difference: if you assign new values to it, this map will be updated with + that new value. + + \sa operator[](), at(), first(), insert(), prepend(), append(), + removeAt(), takeAt() + */ + +/*! + \fn QCborValueRef QCborArray::operator[](qsizetype i) + + Returns a reference to the QCborValue element at position \a i in the + array. The array must have at least \a i elements. + + QCborValueRef has the exact same API as \l QCborValue, with one important + difference: if you assign new values to it, this map will be updated with + that new value. + + \sa at(), first(), last(), insert(), prepend(), append(), + removeAt(), takeAt() + */ + +/*! + \fn void QCborArray::insert(qsizetype i, const QCborValue &value) + \fn void QCborArray::insert(qsizetype i, QCborValue &&value) + + Inserts \a value into the array at position \a i in this array. The array + must have at least \a i elements before the insertion. + + \sa at(), operator[](), first(), last(), prepend(), append(), + removeAt(), takeAt(), extract() + */ +void QCborArray::insert(qsizetype i, const QCborValue &value) +{ + Q_ASSERT(size_t(i) <= size_t(size()) || i == -1); + if (i < 0) + i = size(); + detach(qMax(i + 1, size())); + d->insertAt(i, value); +} + +void QCborArray::insert(qsizetype i, QCborValue &&value) +{ + Q_ASSERT(size_t(i) <= size_t(size()) || i == -1); + if (i < 0) + i = size(); + detach(qMax(i + 1, size())); + d->insertAt(i, value, QCborContainerPrivate::MoveContainer); + QCborContainerPrivate::resetValue(value); +} + +/*! + \fn QCborValue QCborArray::extract(Iterator it) + \fn QCborValue QCborArray::extract(ConstIterator it) + + Extracts a value from the array at the position indicated by iterator \a it + and returns the value so extracted. + + \sa insert(), erase(), takeAt(), removeAt() + */ +QCborValue QCborArray::extract(iterator it) +{ + detach(); + + QCborValue v = d->extractAt(it.item.i); + d->removeAt(it.item.i); + return v; +} + +/*! + \fn void QCborArray::prepend(const QCborValue &value) + \fn void QCborArray::prepend(QCborValue &&value) + + Prepends \a value into the array before any other elements it may already + contain. + + \sa at(), operator[](), first(), last(), insert(), append(), + removeAt(), takeAt() + */ + +/*! + \fn void QCborArray::append(const QCborValue &value) + \fn void QCborArray::append(QCborValue &&value) + + Appends \a value into the array after all other elements it may already + contain. + + \sa at(), operator[](), first(), last(), insert(), prepend(), + removeAt(), takeAt() + */ + +/*! + Removes the item at position \a i from the array. The array must have more + than \a i elements before the removal. + + \sa takeAt(), removeFirst(), removeLast(), at(), operator[](), insert(), + prepend(), append() + */ +void QCborArray::removeAt(qsizetype i) +{ + detach(size()); + d->removeAt(i); +} + +/*! + \fn QCborValue QCborArray::takeAt(qsizetype i) + + Removes the item at position \a i from the array and returns it. The array + must have more than \a i elements before the removal. + + \sa removeAt(), removeFirst(), removeLast(), at(), operator[](), insert(), + prepend(), append() + */ + +/*! + \fn void QCborArray::removeFirst() + + Removes the first item in the array, making the second element become the + first. The array must not be empty before this call. + + \sa removeAt(), takeFirst(), removeLast(), at(), operator[](), insert(), + prepend(), append() + */ + +/*! + \fn void QCborArray::removeLast() + + Removes the last item in the array. The array must not be empty before this + call. + + \sa removeAt(), takeLast(), removeFirst(), at(), operator[](), insert(), + prepend(), append() + */ + +/*! + \fn void QCborArray::takeFirst() + + Removes the first item in the array and returns it, making the second + element become the first. The array must not be empty before this call. + + \sa takeAt(), removeFirst(), removeLast(), at(), operator[](), insert(), + prepend(), append() + */ + +/*! + \fn void QCborArray::takeLast() + + Removes the last item in the array and returns it. The array must not be + empty before this call. + + \sa takeAt(), removeLast(), removeFirst(), at(), operator[](), insert(), + prepend(), append() + */ + +/*! + Returns true if this array contains an element that is equal to \a value. + */ +bool QCborArray::contains(const QCborValue &value) const +{ + for (qsizetype i = 0; i < size(); ++i) { + int cmp = d->compareElement(i, value); + if (cmp == 0) + return true; + } + return false; +} + +/*! + \fn int QCborArray::compare(const QCborArray &other) const + + Compares this array and \a other, comparing each element in sequence, and + returns an integer that indicates whether this array should be sorted + before (if the result is negative) or after \a other (if the result is + positive). If this function returns 0, the two arrays are equal and contain + the same elements. + + For more information on CBOR sorting order, see QCborValue::compare(). + + \sa QCborValue::compare(), QCborMap::compare(), operator==() + */ + +/*! + \fn bool QCborArray::operator==(const QCborArray &other) const + + Compares this array and \a other, comparing each element in sequence, and + returns true if both arrays contains the same elements, false otherwise. + + For more information on CBOR equality in Qt, see, QCborValue::compare(). + + \sa compare(), QCborValue::operator==(), QCborMap::operator==(), + operator!=(), operator<() + */ + +/*! + \fn bool QCborArray::operator!=(const QCborArray &other) const + + Compares this array and \a other, comparing each element in sequence, and + returns true if the two arrays' contents are different, false otherwise. + + For more information on CBOR equality in Qt, see, QCborValue::compare(). + + \sa compare(), QCborValue::operator==(), QCborMap::operator==(), + operator==(), operator<() + */ + +/*! + \fn bool QCborArray::operator<(const QCborArray &other) const + + Compares this array and \a other, comparing each element in sequence, and + returns true if this array should be sorted before \a other, false + otherwise. + + For more information on CBOR sorting order, see QCborValue::compare(). + + \sa compare(), QCborValue::operator==(), QCborMap::operator==(), + operator==(), operator!=() + */ + +/*! + \typedef QCborArray::iterator + + A synonym to QCborArray::Iterator. + */ + +/*! + \typedef QCborArray::const_iterator + + A synonym to QCborArray::ConstIterator. + */ + +/*! + \fn QCborArray::iterator QCborArray::begin() + + Returns an array iterator pointing to the first item in this array. If the + array is empty, then this function returns the same as end(). + + \sa constBegin(), end() + */ + +/*! + \fn QCborArray::const_iterator QCborArray::begin() const + + Returns an array iterator pointing to the first item in this array. If the + array is empty, then this function returns the same as end(). + + \sa constBegin(), constEnd() + */ + +/*! + \fn QCborArray::const_iterator QCborArray::cbegin() const + + Returns an array iterator pointing to the first item in this array. If the + array is empty, then this function returns the same as end(). + + \sa constBegin(), constEnd() + */ + +/*! + \fn QCborArray::const_iterator QCborArray::constBegin() const + + Returns an array iterator pointing to the first item in this array. If the + array is empty, then this function returns the same as end(). + + \sa begin(), constEnd() + */ + +/*! + \fn QCborArray::iterator QCborArray::end() + + Returns an array iterator pointing to just after the last element in this + array. + + \sa begin(), constEnd() + */ + +/*! + \fn QCborArray::const_iterator QCborArray::end() const + + Returns an array iterator pointing to just after the last element in this + array. + + \sa constBegin(), constEnd() + */ + +/*! + \fn QCborArray::const_iterator QCborArray::cend() const + + Returns an array iterator pointing to just after the last element in this + array. + + \sa constBegin(), constEnd() + */ + +/*! + \fn QCborArray::const_iterator QCborArray::constEnd() const + + Returns an array iterator pointing to just after the last element in this + array. + + \sa constBegin(), end() + */ + +/*! + \fn QCborArray::iterator QCborArray::insert(iterator before, const QCborValue &value) + \fn QCborArray::iterator QCborArray::insert(const_iterator before, const QCborValue &value) + \overload + + Inserts \a value into this array before element \a before and returns an + array iterator pointing to the just-inserted element. + + \sa erase(), removeAt(), prepend(), append() + */ + +/*! + \fn QCborArray::iterator QCborArray::erase(iterator it) + \fn QCborArray::iterator QCborArray::erase(const_iterator it) + + Removes the element pointed to by the array iterator \a it from this array, + then returns an iterator to the next element (the one that took the same + position in the array that \a it used to occupy). + + \sa insert(), removeAt(), takeAt(), takeFirst(), takeLast() + */ + +/*! + \fn void QCborArray::push_back(const QCborValue &t) + + Synonym for append(). This function is provided for compatibility with + generic code that uses the Standard Library API. + + Appends the element \a t to this array. + + \sa append(), push_front(), pop_back(), prepend(), insert() + */ + +/*! + \fn void QCborArray::push_front(const QCborValue &t) + + Synonym for prepend(). This function is provided for compatibility with + generic code that uses the Standard Library API. + + Prepends the element \a t to this array. + + \sa prepend(), push_back(), pop_front(), append(), insert() + */ + +/*! + \fn void QCborArray::pop_front() + + Synonym for removeFirst(). This function is provided for compatibility with + generic code that uses the Standard Library API. + + Removes the first element of this array. The array must not be empty before + the removal + + \sa removeFirst(), takeFirst(), pop_back(), push_front(), prepend(), insert() + */ + +/*! + \fn void QCborArray::pop_back() + + Synonym for removeLast(). This function is provided for compatibility with + generic code that uses the Standard Library API. + + Removes the last element of this array. The array must not be empty before + the removal + + \sa removeLast(), takeLast(), pop_front(), push_back(), append(), insert() + */ + +/*! + \fn bool QCborArray::empty() const + + Synonym for isEmpty(). This function is provided for compatibility with + generic code that uses the Standard Library API. + + Returns true if this array is empty (size() == 0). + + \sa isEmpty(), size() + */ + +/*! + \fn QCborArray QCborArray::operator+(const QCborValue &v) const + + Returns a new QCborArray containing the same elements as this array, plus + \a v appended as the last element. + + \sa operator+=(), operator<<(), append() + */ + +/*! + \fn QCborArray &QCborArray::operator+=(const QCborValue &v) + + Appends \a v to this array and returns a reference to this array. + + \sa append(), insert(), operator+(), operator<<() + */ + +/*! + \fn QCborArray &QCborArray::operator<<(const QCborValue &v) + + Appends \a v to this array and returns a reference to this array. + + \sa append(), insert(), operator+(), operator+=() + */ + +void QCborArray::detach(qsizetype reserved) +{ + d = QCborContainerPrivate::detach(d.data(), reserved ? reserved : size()); +} + +/*! + \class QCborArray::Iterator + \inmodule QtCore + \ingroup cbor + \since 5.12 + + \brief The QCborArray::Iterator class provides an STL-style non-const iterator for QCborArray. + + QCborArray::Iterator allows you to iterate over a QCborArray and to modify + the array item associated with the iterator. If you want to iterate over a + const QCborArray, use QCborArray::ConstIterator instead. It is generally a + good practice to use QCborArray::ConstIterator on a non-const QCborArray as + well, unless you need to change the QCborArray through the iterator. Const + iterators are slightly faster and improve code readability. + + Iterators are initialized by using a QCborArray function like + QCborArray::begin(), QCborArray::end(), or QCborArray::insert(). Iteration + is only possible after that. + + Most QCborArray functions accept an integer index rather than an iterator. + For that reason, iterators are rarely useful in connection with QCborArray. + One place where STL-style iterators do make sense is as arguments to + \l{generic algorithms}. + + Multiple iterators can be used on the same array. However, be aware that + any non-const function call performed on the QCborArray will render all + existing iterators undefined. + + \sa QCborArray::ConstIterator +*/ + +/*! + \typedef QCborArray::Iterator::iterator_category + + A synonym for \e {std::random_access_iterator_tag} indicating this iterator + is a random access iterator. + */ + +/*! + \typedef QCborArray::Iterator::difference_type + \internal +*/ + +/*! + \typedef QCborArray::Iterator::value_type + \internal +*/ + +/*! + \typedef QCborArray::Iterator::reference + \internal +*/ + +/*! + \typedef QCborArray::Iterator::pointer + \internal +*/ + +/*! + \fn QCborArray::Iterator::Iterator() + + Constructs an uninitialized iterator. + + Functions like operator*() and operator++() should not be called on an + uninitialized iterator. Use operator=() to assign a value to it before + using it. + + \sa QCborArray::begin(), QCborArray::end() +*/ + +/*! + \fn QCborArray::Iterator::Iterator(const Iterator &other) + + Makes a copy of \a other. + */ + +/*! + \fn QCborArray::Iterator &QCborArray::Iterator::operator=(const Iterator &other) + + Makes this iterator a copy of \a other and returns a reference to this iterator. + */ + +/*! + \fn QCborValueRef QCborArray::Iterator::operator*() const + + Returns a modifiable reference to the current item. + + You can change the value of an item by using operator*() on the left side + of an assignment. + + The return value is of type QCborValueRef, a helper class for QCborArray + and QCborMap. When you get an object of type QCborValueRef, you can use it + as if it were a reference to a QCborValue. If you assign to it, the + assignment will apply to the element in the QCborArray or QCborMap from + which you got the reference. +*/ + +/*! + \fn QCborValueRef *QCborArray::Iterator::operator->() const + + Returns a pointer to a modifiable reference to the current item. +*/ + +/*! + \fn QCborValueRef QCborArray::Iterator::operator[](qsizetype j) + + Returns a modifiable reference to the item at a position \a j steps forward + from the item pointed to by this iterator. + + This function is provided to make QCborArray iterators behave like C++ + pointers. + + The return value is of type QCborValueRef, a helper class for QCborArray + and QCborMap. When you get an object of type QCborValueRef, you can use it + as if it were a reference to a QCborValue. If you assign to it, the + assignment will apply to the element in the QCborArray or QCborMap from + which you got the reference. + + \sa operator+() +*/ + +/*! + \fn bool QCborArray::Iterator::operator==(const Iterator &other) const + \fn bool QCborArray::Iterator::operator==(const ConstIterator &other) const + + Returns \c true if \a other points to the same entry in the array as this + iterator; otherwise returns \c false. + + \sa operator!=() +*/ + +/*! + \fn bool QCborArray::Iterator::operator!=(const Iterator &other) const + \fn bool QCborArray::Iterator::operator!=(const ConstIterator &other) const + + Returns \c true if \a other points to a different entry in the array than + this iterator; otherwise returns \c false. + + \sa operator==() +*/ + +/*! + \fn bool QCborArray::Iterator::operator<(const Iterator& other) const + \fn bool QCborArray::Iterator::operator<(const ConstIterator& other) const + + Returns \c true if the entry in the array pointed to by this iterator + occurs before the entry pointed to by the \a other iterator. +*/ + +/*! + \fn bool QCborArray::Iterator::operator<=(const Iterator& other) const + \fn bool QCborArray::Iterator::operator<=(const ConstIterator& other) const + + Returns \c true if the entry in the array pointed to by this iterator + occurs before or is the same entry as is pointed to by the \a other + iterator. +*/ + +/*! + \fn bool QCborArray::Iterator::operator>(const Iterator& other) const + \fn bool QCborArray::Iterator::operator>(const ConstIterator& other) const + + Returns \c true if the entry in the array pointed to by this iterator + occurs after the entry pointed to by the \a other iterator. + */ + +/*! + \fn bool QCborArray::Iterator::operator>=(const Iterator& other) const + \fn bool QCborArray::Iterator::operator>=(const ConstIterator& other) const + + Returns \c true if the entry in the array pointed to by this iterator + occurs after or is the same entry as is pointed to by the \a other + iterator. +*/ + +/*! + \fn QCborArray::Iterator &QCborArray::Iterator::operator++() + + The prefix ++ operator, \c{++it}, advances the iterator to the next item in + the array and returns this iterator. + + Calling this function on QCborArray::end() leads to undefined results. + + \sa operator--() +*/ + +/*! + \fn QCborArray::Iterator QCborArray::Iterator::operator++(int) + \overload + + The postfix ++ operator, \c{it++}, advances the iterator to the next item + in the array and returns an iterator to the previously current item. +*/ + +/*! + \fn QCborArray::Iterator &QCborArray::Iterator::operator--() + + The prefix -- operator, \c{--it}, makes the preceding item current and + returns this iterator. + + Calling this function on QCborArray::begin() leads to undefined results. + + \sa operator++() +*/ + +/*! + \fn QCborArray::Iterator QCborArray::Iterator::operator--(int) + \overload + + The postfix -- operator, \c{it--}, makes the preceding item current and + returns an iterator to the previously current item. +*/ + +/*! + \fn QCborArray::Iterator &QCborArray::Iterator::operator+=(qsizetype j) + + Advances the iterator by \a j positions. If \a j is negative, the iterator + goes backward. Returns a reference to this iterator. + + \sa operator-=(), operator+() +*/ + +/*! + \fn QCborArray::Iterator &QCborArray::Iterator::operator-=(qsizetype j) + + Makes the iterator go back by \a j positions. If \a j is negative, the + iterator goes forward. Returns a reference to this iterator. + + \sa operator+=(), operator-() +*/ + +/*! + \fn QCborArray::Iterator QCborArray::Iterator::operator+(qsizetype j) const + + Returns an iterator to the item at position \a j steps forward from this + iterator. If \a j is negative, the iterator goes backward. + + \sa operator-(), operator+=() +*/ + +/*! + \fn QCborArray::Iterator QCborArray::Iterator::operator-(qsizetype j) const + + Returns an iterator to the item at position \a j steps backward from this + iterator. If \a j is negative, the iterator goes forward. + + \sa operator+(), operator-=() +*/ + +/*! + \fn qsizetype QCborArray::Iterator::operator-(Iterator other) const + + Returns the offset of this iterator relative to \a other. +*/ + +/*! + \class QCborArray::ConstIterator + \inmodule QtCore + \ingroup cbor + \since 5.12 + + \brief The QCborArray::ConstIterator class provides an STL-style const iterator for QCborArray. + + QCborArray::ConstIterator allows you to iterate over a QCborArray. If you + want to modify the QCborArray as you iterate over it, use + QCborArray::Iterator instead. It is generally good practice to use + QCborArray::ConstIterator, even on a non-const QCborArray, when you don't + need to change the QCborArray through the iterator. Const iterators are + slightly faster and improves code readability. + + Iterators are initialized by using a QCborArray function like + QCborArray::begin() or QCborArray::end(). Iteration is only possible after + that. + + Most QCborArray functions accept an integer index rather than an iterator. + For that reason, iterators are rarely useful in connection with QCborArray. + One place where STL-style iterators do make sense is as arguments to + \l{generic algorithms}. + + Multiple iterators can be used on the same array. However, be aware that + any non-const function call performed on the QCborArray will render all + existing iterators undefined. + + \sa QCborArray::Iterator +*/ + +/*! + \fn QCborArray::ConstIterator::ConstIterator() + + Constructs an uninitialized iterator. + + Functions like operator*() and operator++() should not be called on an + uninitialized iterator. Use operator=() to assign a value to it before + using it. + + \sa QCborArray::constBegin(), QCborArray::constEnd() +*/ + +/*! + \fn QCborArray::ConstIterator &QCborArray::ConstIterator::operator=(const ConstIterator &other) + + Makes this iterator a copy of \a other and returns a reference to this iterator. +*/ + +/*! + \typedef QCborArray::ConstIterator::iterator_category + + A synonym for \e {std::random_access_iterator_tag} indicating this iterator + is a random access iterator. +*/ + +/*! + \typedef QCborArray::ConstIterator::difference_type + \internal +*/ + +/*! + \typedef QCborArray::ConstIterator::value_type + \internal +*/ + +/*! + \typedef QCborArray::ConstIterator::reference + \internal +*/ + +/*! + \typedef QCborArray::ConstIterator::pointer + \internal +*/ + +/*! + \fn QCborArray::ConstIterator::ConstIterator(const ConstIterator &other) + + Constructs a copy of \a other. +*/ + +/*! + \fn QCborValue QCborArray::ConstIterator::operator*() const + + Returns the current item. +*/ + +/*! + \fn const QCborValue *QCborArray::ConstIterator::operator->() const + + Returns a pointer to the current item. +*/ + +/*! + \fn const QCborValueRef QCborArray::ConstIterator::operator[](qsizetype j) + + Returns the item at a position \a j steps forward from the item pointed to + by this iterator. + + This function is provided to make QCborArray iterators behave like C++ + pointers. + + \sa operator+() +*/ + +/*! + \fn bool QCborArray::ConstIterator::operator==(const Iterator &other) const + \fn bool QCborArray::ConstIterator::operator==(const ConstIterator &other) const + + Returns \c true if \a other points to the same entry in the array as this + iterator; otherwise returns \c false. + + \sa operator!=() +*/ + +/*! + \fn bool QCborArray::ConstIterator::operator!=(const Iterator &o) const + \fn bool QCborArray::ConstIterator::operator!=(const ConstIterator &o) const + + Returns \c true if \a o points to a different entry in the array than + this iterator; otherwise returns \c false. + + \sa operator==() +*/ + +/*! + \fn bool QCborArray::ConstIterator::operator<(const Iterator &other) const + \fn bool QCborArray::ConstIterator::operator<(const ConstIterator &other) const + + Returns \c true if the entry in the array pointed to by this iterator + occurs before the entry pointed to by the \a other iterator. +*/ + +/*! + \fn bool QCborArray::ConstIterator::operator<=(const Iterator &other) const + \fn bool QCborArray::ConstIterator::operator<=(const ConstIterator &other) const + + Returns \c true if the entry in the array pointed to by this iterator + occurs before or is the same entry as is pointed to by the \a other + iterator. +*/ + +/*! + \fn bool QCborArray::ConstIterator::operator>(const Iterator &other) const + \fn bool QCborArray::ConstIterator::operator>(const ConstIterator &other) const + + Returns \c true if the entry in the array pointed to by this iterator + occurs after the entry pointed to by the \a other iterator. +*/ + +/*! + \fn bool QCborArray::ConstIterator::operator>=(const Iterator &other) const + \fn bool QCborArray::ConstIterator::operator>=(const ConstIterator &other) const + + Returns \c true if the entry in the array pointed to by this iterator + occurs after or is the same entry as is pointed to by the \a other + iterator. +*/ + +/*! + \fn QCborArray::ConstIterator &QCborArray::ConstIterator::operator++() + + The prefix ++ operator, \c{++it}, advances the iterator to the next item in + the array and returns this iterator. + + Calling this function on QCborArray::end() leads to undefined results. + + \sa operator--() +*/ + +/*! + \fn QCborArray::ConstIterator QCborArray::ConstIterator::operator++(int) + \overload + + The postfix ++ operator, \c{it++}, advances the iterator to the next item + in the array and returns an iterator to the previously current item. +*/ + +/*! + \fn QCborArray::ConstIterator &QCborArray::ConstIterator::operator--() + + The prefix -- operator, \c{--it}, makes the preceding item current and + returns this iterator. + + Calling this function on QCborArray::begin() leads to undefined results. + + \sa operator++() +*/ + +/*! + \fn QCborArray::ConstIterator QCborArray::ConstIterator::operator--(int) + \overload + + The postfix -- operator, \c{it--}, makes the preceding item current and + returns an iterator to the previously current item. +*/ + +/*! + \fn QCborArray::ConstIterator &QCborArray::ConstIterator::operator+=(qsizetype j) + + Advances the iterator by \a j positions. If \a j is negative, the iterator + goes backward. Returns a reference to this iterator. + + \sa operator-=(), operator+() +*/ + +/*! + \fn QCborArray::ConstIterator &QCborArray::ConstIterator::operator-=(qsizetype j) + + Makes the iterator go back by \a j positions. If \a j is negative, the + iterator goes forward. Returns a reference to this iterator. + + \sa operator+=(), operator-() +*/ + +/*! + \fn QCborArray::ConstIterator QCborArray::ConstIterator::operator+(qsizetype j) const + + Returns an iterator to the item at a position \a j steps forward from this + iterator. If \a j is negative, the iterator goes backward. + + \sa operator-(), operator+=() +*/ + +/*! + \fn QCborArray::ConstIterator QCborArray::ConstIterator::operator-(qsizetype j) const + + Returns an iterator to the item at a position \a j steps backward from this + iterator. If \a j is negative, the iterator goes forward. + + \sa operator+(), operator-=() +*/ + +/*! + \fn qsizetype QCborArray::ConstIterator::operator-(ConstIterator other) const + + Returns the offset of this iterator relative to \a other. +*/ + +#if !defined(QT_NO_DEBUG_STREAM) +QDebug operator<<(QDebug dbg, const QCborArray &a) +{ + QDebugStateSaver saver(dbg); + dbg.nospace() << "QCborArray{"; + const char *comma = ""; + for (auto v : a) { + dbg << comma << v; + comma = ", "; + } + return dbg << '}'; +} +#endif + +QT_END_NAMESPACE diff --git a/src/corelib/serialization/qcborarray.h b/src/corelib/serialization/qcborarray.h new file mode 100644 index 0000000000..4e9f7cf9b5 --- /dev/null +++ b/src/corelib/serialization/qcborarray.h @@ -0,0 +1,295 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QCBORARRAY_H +#define QCBORARRAY_H + +#include <QtCore/qcborvalue.h> + +#include <initializer_list> + +QT_BEGIN_NAMESPACE + +class QJsonArray; + +class QCborContainerPrivate; +class Q_CORE_EXPORT QCborArray +{ +public: + class ConstIterator; + class Iterator { + mutable QCborValueRef item; + friend class ConstIterator; + friend class QCborArray; + Iterator(QCborContainerPrivate *dd, qsizetype ii) : item(dd, ii) {} + public: + typedef std::random_access_iterator_tag iterator_category; + typedef qsizetype difference_type; + typedef QCborValue value_type; + typedef QCborValueRef reference; + typedef QCborValueRef *pointer; + + Q_DECL_CONSTEXPR Iterator() = default; + Q_DECL_CONSTEXPR Iterator(const Iterator &) = default; + Iterator &operator=(const Iterator &other) + { + // rebind the reference + item.d = other.item.d; + item.i = other.item.i; + return *this; + } + + QCborValueRef operator*() const { return item; } + QCborValueRef *operator->() const { return &item; } + QCborValueRef operator[](qsizetype j) { return { item.d, item.i + j }; } + + bool operator==(const Iterator &o) const { return item.d == o.item.d && item.i == o.item.i; } + bool operator!=(const Iterator &o) const { return !(*this == o); } + bool operator<(const Iterator& other) const { Q_ASSERT(item.d == other.item.d); return item.i < other.item.i; } + bool operator<=(const Iterator& other) const { Q_ASSERT(item.d == other.item.d); return item.i <= other.item.i; } + bool operator>(const Iterator& other) const { Q_ASSERT(item.d == other.item.d); return item.i > other.item.i; } + bool operator>=(const Iterator& other) const { Q_ASSERT(item.d == other.item.d); return item.i >= other.item.i; } + bool operator==(const ConstIterator &o) const { return item.d == o.item.d && item.i == o.item.i; } + bool operator!=(const ConstIterator &o) const { return !(*this == o); } + bool operator<(const ConstIterator& other) const { Q_ASSERT(item.d == other.item.d); return item.i < other.item.i; } + bool operator<=(const ConstIterator& other) const { Q_ASSERT(item.d == other.item.d); return item.i <= other.item.i; } + bool operator>(const ConstIterator& other) const { Q_ASSERT(item.d == other.item.d); return item.i > other.item.i; } + bool operator>=(const ConstIterator& other) const { Q_ASSERT(item.d == other.item.d); return item.i >= other.item.i; } + Iterator &operator++() { ++item.i; return *this; } + Iterator operator++(int) { Iterator n = *this; ++item.i; return n; } + Iterator &operator--() { item.i--; return *this; } + Iterator operator--(int) { Iterator n = *this; item.i--; return n; } + Iterator &operator+=(qsizetype j) { item.i += j; return *this; } + Iterator &operator-=(qsizetype j) { item.i -= j; return *this; } + Iterator operator+(qsizetype j) const { return Iterator({ item.d, item.i + j }); } + Iterator operator-(qsizetype j) const { return Iterator({ item.d, item.i - j }); } + qsizetype operator-(Iterator j) const { return item.i - j.item.i; } + }; + + class ConstIterator { + QCborValueRef item; + friend class Iterator; + friend class QCborArray; + ConstIterator(QCborContainerPrivate *dd, qsizetype ii) : item(dd, ii) {} + public: + typedef std::random_access_iterator_tag iterator_category; + typedef qsizetype difference_type; + typedef const QCborValue value_type; + typedef const QCborValueRef reference; + typedef const QCborValueRef *pointer; + + Q_DECL_CONSTEXPR ConstIterator() = default; + Q_DECL_CONSTEXPR ConstIterator(const ConstIterator &) = default; + ConstIterator &operator=(const ConstIterator &other) + { + // rebind the reference + item.d = other.item.d; + item.i = other.item.i; + return *this; + } + + const QCborValueRef operator*() const { return item; } + const QCborValueRef *operator->() const { return &item; } + const QCborValueRef operator[](qsizetype j) { return { item.d, item.i + j }; } + + bool operator==(const Iterator &o) const { return item.d == o.item.d && item.i == o.item.i; } + bool operator!=(const Iterator &o) const { return !(*this == o); } + bool operator<(const Iterator& other) const { Q_ASSERT(item.d == other.item.d); return item.i < other.item.i; } + bool operator<=(const Iterator& other) const { Q_ASSERT(item.d == other.item.d); return item.i <= other.item.i; } + bool operator>(const Iterator& other) const { Q_ASSERT(item.d == other.item.d); return item.i > other.item.i; } + bool operator>=(const Iterator& other) const { Q_ASSERT(item.d == other.item.d); return item.i >= other.item.i; } + bool operator==(const ConstIterator &o) const { return item.d == o.item.d && item.i == o.item.i; } + bool operator!=(const ConstIterator &o) const { return !(*this == o); } + bool operator<(const ConstIterator& other) const { Q_ASSERT(item.d == other.item.d); return item.i < other.item.i; } + bool operator<=(const ConstIterator& other) const { Q_ASSERT(item.d == other.item.d); return item.i <= other.item.i; } + bool operator>(const ConstIterator& other) const { Q_ASSERT(item.d == other.item.d); return item.i > other.item.i; } + bool operator>=(const ConstIterator& other) const { Q_ASSERT(item.d == other.item.d); return item.i >= other.item.i; } + ConstIterator &operator++() { ++item.i; return *this; } + ConstIterator operator++(int) { ConstIterator n = *this; ++item.i; return n; } + ConstIterator &operator--() { item.i--; return *this; } + ConstIterator operator--(int) { ConstIterator n = *this; item.i--; return n; } + ConstIterator &operator+=(qsizetype j) { item.i += j; return *this; } + ConstIterator &operator-=(qsizetype j) { item.i -= j; return *this; } + ConstIterator operator+(qsizetype j) const { return ConstIterator({ item.d, item.i + j }); } + ConstIterator operator-(qsizetype j) const { return ConstIterator({ item.d, item.i - j }); } + qsizetype operator-(ConstIterator j) const { return item.i - j.item.i; } + }; + + typedef qsizetype size_type; + typedef QCborValue value_type; + typedef value_type *pointer; + typedef const value_type *const_pointer; + typedef QCborValue &reference; + typedef const QCborValue &const_reference; + typedef qsizetype difference_type; + + QCborArray() noexcept; + QCborArray(const QCborArray &other) noexcept; + QCborArray &operator=(const QCborArray &other) noexcept; + QCborArray(std::initializer_list<QCborValue> args) + : QCborArray() + { + detach(qsizetype(args.size())); + for (const QCborValue &v : args) + append(v); + } + ~QCborArray(); + + void swap(QCborArray &other) noexcept + { + qSwap(d, other.d); + } + + QCborValue toCborValue() const { return *this; } + + qsizetype size() const noexcept; + bool isEmpty() const { return size() == 0; } + + QCborValue at(qsizetype i) const; + QCborValue first() const { return at(0); } + QCborValue last() const { return at(size() - 1); } + QCborValue operator[](qsizetype i) const { return at(i); } + QCborValueRef first() { Q_ASSERT(!isEmpty()); return begin()[0]; } + QCborValueRef last() { Q_ASSERT(!isEmpty()); return begin()[size() - 1]; } + QCborValueRef operator[](qsizetype i) { Q_ASSERT(i < size()); return begin()[i]; } + + void insert(qsizetype i, const QCborValue &value); + void insert(qsizetype i, QCborValue &&value); + void prepend(const QCborValue &value) { insert(0, value); } + void prepend(QCborValue &&value) { insert(0, std::move(value)); } + void append(const QCborValue &value) { insert(-1, value); } + void append(QCborValue &&value) { insert(-1, std::move(value)); } + QCborValue extract(ConstIterator it) { return extract(Iterator{ it.item.d, it.item.i }); } + QCborValue extract(Iterator it); + void removeAt(qsizetype i); + QCborValue takeAt(qsizetype i) { Q_ASSERT(i < size()); return extract(begin() + i); } + void removeFirst() { removeAt(0); } + void removeLast() { removeAt(size() - 1); } + QCborValue takeFirst() { return takeAt(0); } + QCborValue takeLast() { return takeAt(size() - 1); } + + bool contains(const QCborValue &value) const; + + int compare(const QCborArray &other) const noexcept Q_DECL_PURE_FUNCTION; +#if 0 && QT_HAS_INCLUDE(<compare>) + std::strong_ordering operator<=>(const QCborArray &other) const + { + int c = compare(other); + if (c > 0) return std::strong_ordering::greater; + if (c == 0) return std::strong_ordering::equivalent; + return std::strong_ordering::less; + } +#else + bool operator==(const QCborArray &other) const noexcept + { return compare(other) == 0; } + bool operator!=(const QCborArray &other) const noexcept + { return !(*this == other); } + bool operator<(const QCborArray &other) const + { return compare(other) < 0; } +#endif + + typedef Iterator iterator; + typedef ConstIterator const_iterator; + iterator begin() { detach(); return iterator{d.data(), 0}; } + const_iterator constBegin() const { return const_iterator{d.data(), 0}; } + const_iterator begin() const { return constBegin(); } + const_iterator cbegin() const { return constBegin(); } + iterator end() { detach(); return iterator{d.data(), size()}; } + const_iterator constEnd() const { return const_iterator{d.data(), size()}; } + const_iterator end() const { return constEnd(); } + const_iterator cend() const { return constEnd(); } + iterator insert(iterator before, const QCborValue &value) + { insert(before.item.i, value); return iterator{d.data(), before.item.i}; } + iterator insert(const_iterator before, const QCborValue &value) + { insert(before.item.i, value); return iterator{d.data(), before.item.i}; } + iterator erase(iterator it) { removeAt(it.item.i); return iterator{d.data(), it.item.i}; } + iterator erase(const_iterator it) { removeAt(it.item.i); return iterator{d.data(), it.item.i}; } + + void push_back(const QCborValue &t) { append(t); } + void push_front(const QCborValue &t) { prepend(t); } + void pop_front() { removeFirst(); } + void pop_back() { removeLast(); } + bool empty() const { return isEmpty(); } + + // convenience + QCborArray operator+(const QCborValue &v) const + { QCborArray n = *this; n += v; return n; } + QCborArray &operator+=(const QCborValue &v) + { append(v); return *this; } + QCborArray &operator<<(const QCborValue &v) + { append(v); return *this; } + + static QCborArray fromStringList(const QStringList &list); + static QCborArray fromVariantList(const QVariantList &list); + static QCborArray fromJsonArray(const QJsonArray &array); + QVariantList toVariantList() const; + QJsonArray toJsonArray() const; + +private: + void detach(qsizetype reserve = 0); + + friend QCborValue; + explicit QCborArray(QCborContainerPrivate &dd) noexcept; + QExplicitlySharedDataPointer<QCborContainerPrivate> d; +}; + +Q_DECLARE_SHARED(QCborArray) + +inline QCborValue::QCborValue(QCborArray &&a) + : n(-1), container(a.d.take()), t(Array) +{ +} + +inline QCborArray QCborValueRef::toArray() const +{ + return concrete().toArray(); +} + +inline QCborArray QCborValueRef::toArray(const QCborArray &a) const +{ + return concrete().toArray(a); +} + +#if !defined(QT_NO_DEBUG_STREAM) +Q_CORE_EXPORT QDebug operator<<(QDebug, const QCborArray &a); +#endif + +QT_END_NAMESPACE + +#endif // QCBORARRAY_H diff --git a/src/corelib/serialization/qcborcommon.h b/src/corelib/serialization/qcborcommon.h new file mode 100644 index 0000000000..9661cd70bb --- /dev/null +++ b/src/corelib/serialization/qcborcommon.h @@ -0,0 +1,147 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QCBORCOMMON_H +#define QCBORCOMMON_H + +#include <QtCore/qobjectdefs.h> +#include <QtCore/qmetatype.h> +#include <QtCore/qdebug.h> + +#if 0 +#pragma qt_class(QtCborCommon) +#endif + +/* X11 headers use these values too, but as defines */ +#if defined(False) && defined(True) +# define QT_X11_DEFINES_FOUND 1 +# undef True +# undef False +#endif + +QT_BEGIN_NAMESPACE + +enum class QCborSimpleType : quint8 { + False = 20, + True = 21, + Null = 22, + Undefined = 23 +}; + +enum class QCborTag : quint64 {}; +enum class QCborKnownTags { + DateTimeString = 0, + UnixTime_t = 1, + PositiveBignum = 2, + NegativeBignum = 3, + Decimal = 4, + Bigfloat = 5, + COSE_Encrypt0 = 16, + COSE_Mac0 = 17, + COSE_Sign1 = 18, + ExpectedBase64url = 21, + ExpectedBase64 = 22, + ExpectedBase16 = 23, + EncodedCbor = 24, + Url = 32, + Base64url = 33, + Base64 = 34, + RegularExpression = 35, + MimeMessage = 36, + Uuid = 37, + COSE_Encrypt = 96, + COSE_Mac = 97, + COSE_Sign = 98, + Signature = 55799 +}; + +inline bool operator==(QCborTag t, QCborKnownTags kt) { return quint64(t) == quint64(kt); } +inline bool operator==(QCborKnownTags kt, QCborTag t) { return quint64(t) == quint64(kt); } +inline bool operator!=(QCborTag t, QCborKnownTags kt) { return quint64(t) != quint64(kt); } +inline bool operator!=(QCborKnownTags kt, QCborTag t) { return quint64(t) != quint64(kt); } + +struct Q_CORE_EXPORT QCborError +{ + Q_GADGET +public: + enum Code : int { + UnknownError = 1, + AdvancePastEnd = 3, + InputOutputError = 4, + GarbageAtEnd = 256, + EndOfFile, + UnexpectedBreak, + UnknownType, + IllegalType, + IllegalNumber, + IllegalSimpleType, + + InvalidUtf8String = 516, + + DataTooLarge = 1024, + NestingTooDeep, + UnsupportedType, + + NoError = 0 + }; + Q_ENUM(Code) + + Code c; + operator Code() const { return c; } + QString toString() const; +}; + +#if !defined(QT_NO_DEBUG_STREAM) +Q_CORE_EXPORT QDebug operator<<(QDebug, QCborSimpleType st); +Q_CORE_EXPORT QDebug operator<<(QDebug, QCborKnownTags tg); +Q_CORE_EXPORT QDebug operator<<(QDebug, QCborTag tg); +#endif + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QCborTag) + +// To avoid changing namespace we need to reinstate defines, even though our .cpp +// will then have to remove them again. +#if defined(QT_X11_DEFINES_FOUND) +# define True 1 +# define False 0 +#endif + +#endif // QCBORSTREAM_H diff --git a/src/corelib/serialization/qcbordiagnostic.cpp b/src/corelib/serialization/qcbordiagnostic.cpp new file mode 100644 index 0000000000..75feaded17 --- /dev/null +++ b/src/corelib/serialization/qcbordiagnostic.cpp @@ -0,0 +1,347 @@ +/**************************************************************************** +** +** 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 "qcborvalue.h" +#include "qcborvalue_p.h" + +#include "qcborarray.h" +#include "qcbormap.h" + +#include <private/qnumeric_p.h> +#include <qstack.h> +#include <private/qtools_p.h> + +QT_BEGIN_NAMESPACE + +namespace { +class DiagnosticNotation +{ +public: + static QString create(const QCborValue &v, QCborValue::DiagnosticNotationOptions opts) + { + DiagnosticNotation dn(opts); + dn.appendValue(v); + return dn.result; + } + +private: + QStack<int> byteArrayFormatStack; + QString separator; + QString result; + QCborValue::DiagnosticNotationOptions opts; + int nestingLevel = 0; + + struct Nest { + enum { IndentationWidth = 4 }; + DiagnosticNotation *dn; + Nest(DiagnosticNotation *that) : dn(that) + { + ++dn->nestingLevel; + static const char indent[IndentationWidth + 1] = " "; + if (dn->opts & QCborValue::LineWrapped) + dn->separator += QLatin1String(indent, IndentationWidth); + } + ~Nest() + { + --dn->nestingLevel; + if (dn->opts & QCborValue::LineWrapped) + dn->separator.chop(IndentationWidth); + } + }; + + DiagnosticNotation(QCborValue::DiagnosticNotationOptions opts_) + : separator(QLatin1String(opts_ & QCborValue::LineWrapped ? "\n" : "")), opts(opts_) + { + byteArrayFormatStack.push(int(QCborKnownTags::ExpectedBase16)); + } + + void appendString(const QString &s); + void appendArray(const QCborArray &a); + void appendMap(const QCborMap &m); + void appendValue(const QCborValue &v); +}; +} + +static QString makeFpString(double d) +{ + QString s; + quint64 v; + if (qt_is_inf(d)) { + s = (d < 0) ? QStringLiteral("-inf") : QStringLiteral("inf"); + } else if (qt_is_nan(d)) { + s = QStringLiteral("nan"); + } else if (convertDoubleTo(d, &v)) { + s = QString::fromLatin1("%1.0").arg(v); + if (d < 0) + s.prepend(QLatin1Char('-')); + } else { + s = QString::number(d, 'g', QLocale::FloatingPointShortest); + if (!s.contains(QLatin1Char('.')) && !s.contains('e')) + s += QLatin1Char('.'); + } + return s; +} + +static bool isByteArrayEncodingTag(QCborTag tag) +{ + switch (quint64(tag)) { + case quint64(QCborKnownTags::ExpectedBase16): + case quint64(QCborKnownTags::ExpectedBase64): + case quint64(QCborKnownTags::ExpectedBase64url): + return true; + } + return false; +} + +void DiagnosticNotation::appendString(const QString &s) +{ + result += QLatin1Char('"'); + + const QChar *begin = s.begin(); + const QChar *end = s.end(); + while (begin < end) { + // find the longest span comprising only non-escaped characters + const QChar *ptr = begin; + for ( ; ptr < end; ++ptr) { + ushort uc = ptr->unicode(); + if (uc == '\\' || uc == '"' || uc < ' ' || uc >= 0x7f) + break; + } + + if (ptr != begin) + result.append(begin, ptr - begin); + + if (ptr == end) + break; + + // there's an escaped character + static const char escapeMap[16] = { + // The C escape characters \a \b \t \n \v \f and \r indexed by + // their ASCII values + 0, 0, 0, 0, + 0, 0, 0, 'a', + 'b', 't', 'n', 'v', + 'f', 'r', 0, 0 + }; + int buflen = 2; + QChar buf[10]; + buf[0] = QLatin1Char('\\'); + buf[1] = QChar::Null; + char16_t uc = ptr->unicode(); + + if (uc < sizeof(escapeMap)) + buf[1] = QLatin1Char(escapeMap[uc]); + else if (uc == '"' || uc == '\\') + buf[1] = QChar(uc); + + if (buf[1] == QChar::Null) { + using QtMiscUtils::toHexUpper; + if (ptr->isHighSurrogate() && (ptr + 1) != end && ptr[1].isLowSurrogate()) { + // properly-paired surrogates + ++ptr; + char32_t ucs4 = QChar::surrogateToUcs4(uc, ptr->unicode()); + buf[1] = 'U'; + buf[2] = '0'; // toHexUpper(ucs4 >> 28); + buf[3] = '0'; // toHexUpper(ucs4 >> 24); + buf[4] = toHexUpper(ucs4 >> 20); + buf[5] = toHexUpper(ucs4 >> 16); + buf[6] = toHexUpper(ucs4 >> 12); + buf[7] = toHexUpper(ucs4 >> 8); + buf[8] = toHexUpper(ucs4 >> 4); + buf[9] = toHexUpper(ucs4); + buflen = 10; + } else { + buf[1] = 'u'; + buf[2] = toHexUpper(uc >> 12); + buf[3] = toHexUpper(uc >> 8); + buf[4] = toHexUpper(uc >> 4); + buf[5] = toHexUpper(uc); + buflen = 6; + } + } + + result.append(buf, buflen); + begin = ptr + 1; + } + + result += QLatin1Char('"'); +} + +void DiagnosticNotation::appendArray(const QCborArray &a) +{ + result += QLatin1Char('['); + + // length 2 (including the space) when not line wrapping + QLatin1String commaValue(", ", opts & QCborValue::LineWrapped ? 1 : 2); + { + Nest n(this); + QLatin1String comma; + for (auto v : a) { + result += comma + separator; + comma = commaValue; + appendValue(v); + } + } + + result += separator + QLatin1Char(']'); +} + +void DiagnosticNotation::appendMap(const QCborMap &m) +{ + result += QLatin1Char('{'); + + // length 2 (including the space) when not line wrapping + QLatin1String commaValue(", ", opts & QCborValue::LineWrapped ? 1 : 2); + { + Nest n(this); + QLatin1String comma; + for (auto v : m) { + result += comma + separator; + comma = commaValue; + appendValue(v.first); + result += QLatin1String(": "); + appendValue(v.second); + } + } + + result += separator + QLatin1Char('}'); +}; + +void DiagnosticNotation::appendValue(const QCborValue &v) +{ + switch (v.type()) { + case QCborValue::Integer: + result += QString::number(v.toInteger()); + return; + case QCborValue::ByteArray: + switch (byteArrayFormatStack.top()) { + case int(QCborKnownTags::ExpectedBase16): + result += QString::fromLatin1("h'" + + v.toByteArray().toHex(opts & QCborValue::ExtendedFormat ? ' ' : '\0') + + '\''); + return; + case int(QCborKnownTags::ExpectedBase64): + result += QString::fromLatin1("b64'" + v.toByteArray().toBase64() + '\''); + return; + default: + case int(QCborKnownTags::ExpectedBase64url): + result += QString::fromLatin1("b64'" + + v.toByteArray().toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals) + + '\''); + return; + } + case QCborValue::String: + return appendString(v.toString()); + case QCborValue::Array: + return appendArray(v.toArray()); + case QCborValue::Map: + return appendMap(v.toMap()); + case QCborValue::False: + result += QLatin1String("false"); + return; + case QCborValue::True: + result += QLatin1String("true"); + return; + case QCborValue::Null: + result += QLatin1String("null"); + return; + case QCborValue::Undefined: + result += QLatin1String("undefined"); + return; + case QCborValue::Double: + result += makeFpString(v.toDouble()); + return; + case QCborValue::Invalid: + result += QStringLiteral("<invalid>"); + return; + + default: + // Only tags, extended types, and simple types remain; see below. + break; + } + + if (v.isTag()) { + // We handle all extended types as regular tags, so it won't matter + // whether we understand that tag or not. + bool byteArrayFormat = opts & QCborValue::ExtendedFormat && isByteArrayEncodingTag(v.tag()); + if (byteArrayFormat) + byteArrayFormatStack.push(int(v.tag())); + result += QString::number(quint64(v.tag())) + QLatin1Char('('); + appendValue(v.taggedValue()); + result += QLatin1Char(')'); + if (byteArrayFormat) + byteArrayFormatStack.pop(); + } else { + // must be a simple type + result += QString::fromLatin1("simple(%1)").arg(quint8(v.toSimpleType())); + } +} + +/*! + Creates the diagnostic notation equivalent of this CBOR object and returns + it. The \a opts parameter controls the dialect of the notation. Diagnostic + notation is useful in debugging, to aid the developer in understanding what + value is stored in the QCborValue or in a CBOR stream. For that reason, the + Qt API provides no support for parsing the diagnostic back into the + in-memory format or CBOR stream, though the representation is unique and it + would be possible. + + CBOR diagnostic notation is specified by + \l{https://tools.ietf.org/html/rfc7049#section-6}{section 6} of RFC 7049. + It is a text representation of the CBOR stream and it is very similar to + JSON, but it supports the CBOR types not found in JSON. The extended format + enabled by the \l{DiagnosticNotationOption}{ExtendedFormat} flag is + currently in some IETF drafts and its format is subject to change. + + This function produces the equivalent representation of the stream that + toCbor() would produce, without any transformation option provided there. + This also implies this function may not produce a representation of the + stream that was used to create the object, if it was created using + fromCbor(), as that function may have applied transformations. For a + high-fidelity notation of a stream, without transformation, see the \c + cbordump example. + + \sa toCbor(), QJsonDocument::toJson() + */ +QString QCborValue::toDiagnosticNotation(DiagnosticNotationOptions opts) const +{ + return DiagnosticNotation::create(*this, opts); +} + +QT_END_NAMESPACE diff --git a/src/corelib/serialization/qcbormap.cpp b/src/corelib/serialization/qcbormap.cpp new file mode 100644 index 0000000000..f9b000393c --- /dev/null +++ b/src/corelib/serialization/qcbormap.cpp @@ -0,0 +1,1751 @@ +/**************************************************************************** +** +** 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 "qcbormap.h" +#include "qcborvalue_p.h" + +QT_BEGIN_NAMESPACE + +using namespace QtCbor; + +/*! + \class QCborMap + \inmodule QtCore + \ingroup cbor + \reentrant + \since 5.12 + + \brief The QCborMap class is used to hold an associative container representable in CBOR. + + This class can be used to hold an associative container in CBOR, a map + between a key and a value type. CBOR is the Concise Binary Object + Representation, a very compact form of binary data encoding that is a + superset of 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}. + + Unlike JSON and \l QVariantMap, CBOR map keys can be of any type, not just + strings. For that reason, QCborMap is effectively a map between QCborValue + keys to QCborValue value elements. + + However, for all member functions that take a key parameter, QCborMap + provides overloads that will work efficiently with integers and strings. In + fact, the use of integer keys is encouraged, since they occupy fewer bytes + to transmit and are simpler to encode and decode. Newer protocols designed + by the IETF CoRE WG to work specifically with CBOR are known to use them. + + QCborMap is not sorted, because of that, searching for keys has linear + complexity (O(n)). QCborMap actually keeps the elements in the order that + they were inserted, which means that it is possible to make sorted + QCborMaps by carefully inserting elements in sorted order. CBOR does not + require sorting, but recommends it. + + QCborMap can also be converted to and from QVariantMap and QJsonObject. + However, when performing the conversion, any non-string keys will be + stringified using a one-way method that the conversion back to QCborMap + will not undo. + + \sa QCborArray, QCborValue, QJsonDocument, QVariantMap + */ + +/*! + \typedef QCborMap::value_type + + The value that is stored in this container: a pair of QCborValues + */ + +/*! + \typedef QCborMap::key_type + + The key type for this map. Since QCborMap keys can be any CBOR type, this + is a QCborValue. + */ + +/*! + \typedef QCborMap::mapped_type + + The type that is mapped to (the value), that is, a QCborValue. + */ + +/*! + \typedef QCborMap::size_type + + The type that QCborMap uses for sizes. + */ + +/*! + \typedef QCborMap::iterator + + A synonym for QCborMap::Iterator. + */ + +/*! + \typedef QCborMap::const_iterator + + A synonym for QCborMap::ConstIterator + */ + +/*! + \fn QCborMap::iterator QCborMap::begin() + + Returns a map iterator pointing to the first key-value pair of this map. If + this map is empty, the returned iterator will be the same as end(). + + \sa constBegin(), end() + */ + +/*! + \fn QCborMap::const_iterator QCborMap::constBegin() const + + Returns a map iterator pointing to the first key-value pair of this map. If + this map is empty, the returned iterator will be the same as constEnd(). + + \sa begin(), constEnd() + */ + +/*! + \fn QCborMap::const_iterator QCborMap::begin() const + + Returns a map iterator pointing to the first key-value pair of this map. If + this map is empty, the returned iterator will be the same as constEnd(). + + \sa begin(), constEnd() + */ + +/*! + \fn QCborMap::const_iterator QCborMap::cbegin() const + + Returns a map iterator pointing to the first key-value pair of this map. If + this map is empty, the returned iterator will be the same as constEnd(). + + \sa begin(), constEnd() + */ + +/*! + \fn QCborMap::iterator QCborMap::end() + + Returns a map iterator representing an element just past the last element + in the map. + + \sa begin(), constBegin(), find(), constFind() + */ + +/*! + \fn QCborMap::iterator QCborMap::constEnd() const + + Returns a map iterator representing an element just past the last element + in the map. + + \sa begin(), constBegin(), find(), constFind() + */ + +/*! + \fn QCborMap::iterator QCborMap::end() const + + Returns a map iterator representing an element just past the last element + in the map. + + \sa begin(), constBegin(), find(), constFind() + */ + +/*! + \fn QCborMap::iterator QCborMap::cend() const + + Returns a map iterator representing an element just past the last element + in the map. + + \sa begin(), constBegin(), find(), constFind() + */ + +/*! + Constructs an empty CBOR Map object. + + \sa isEmpty() + */ +QCborMap::QCborMap() noexcept + : d(nullptr) +{ +} + +/*! + Creates a QCborMap object that is a copy of \a other. + */ +QCborMap::QCborMap(const QCborMap &other) noexcept + : d(other.d) +{ +} + +/*! + \fn QCborMap::QCborMap(std::initializer_list<value_type> args) + + Constructs a QCborMap with items from a brace-initialization list found in + \a args, as in the following example: + + \code + QCborMap map = { + {0, "Hello"}, + {1, "World"}, + {"foo", nullptr}, + {"bar", QCborArray{0, 1, 2, 3, 4}} + }; + \endcode + */ + +/*! + Destroys this QCborMap object and frees any associated resources it owns. + */ +QCborMap::~QCborMap() +{ +} + +/*! + Replaces the contents of this object with a copy of \a other, then returns + a reference to this object. + */ +QCborMap &QCborMap::operator=(const QCborMap &other) noexcept +{ + d = other.d; + return *this; +} + +/*! + \fn void QCborMap::swap(QCborMap &other) + + Swaps the contents of this map and \a other. + */ + +/*! + \fn QCborValue QCborMap::toCborValue() const + + Explicitly constructs a \l QCborValue object that represents this map. + This function is usually not necessary since QCborValue has a constructor + for QCborMap, so the conversion is implicit. + + Converting QCborMap to QCborValue allows it to be used in any context where + QCborValues can be used, including as keys and mapped types in QCborMap, as + well as QCborValue::toCbor(). + + \sa QCborValue::QCborValue(const QCborMap &) + */ + +/*! + \fn bool QCborMap::isEmpty() const + + Returns true if this map is empty (that is, size() is 0). + + \sa size() + */ + +/*! + Returns the number of elements in this map. + + \sa isEmpty() + */ +qsizetype QCborMap::size() const noexcept +{ + return d ? d->elements.size() / 2 : 0; +} + +/*! + Returns a list of all keys in this map. + + \sa QMap::keys(), QHash::keys() + */ +QVector<QCborValue> QCborMap::keys() const +{ + QVector<QCborValue> result; + if (d) { + result.reserve(size()); + for (qsizetype i = 0; i < d->elements.size(); i += 2) + result << d->valueAt(i); + } + return result; +} + +/*! + \fn QCborValue QCborMap::value(qint64 key) const + + Returns the QCborValue element in this map that corresponds to key \a key, + if there is one. CBOR recommends using integer keys, since they occupy less + space and are simpler to encode and decode. + + If the map does not contain key \a key, this function returns a QCborValue + containing an undefined value. For that reason, it is not possible with + this function to tell apart the situation where the key was not present + from the situation where the key was mapped to an undefined value. + + If the map contains more than one key equal to \a key, it is undefined + which one the return from function will reference. QCborMap does not allow + inserting duplicate keys, but it is possible to create such a map by + decoding a CBOR stream with them. They are usually not permitted and having + duplicate keys is usually an indication of a problem in the sender. + + \sa operator[](qint64), find(qint64), constFind(qint64), remove(qint64), contains(qint64) + value(QLatin1String), value(const QString &), value(const QCborValue &) + */ + +/*! + \fn QCborValue QCborMap::operator[](qint64 key) const + + Returns the QCborValue element in this map that corresponds to key \a key, + if there is one. CBOR recommends using integer keys, since they occupy less + space and are simpler to encode and decode. + + If the map does not contain key \a key, this function returns a QCborValue + containing an undefined value. For that reason, it is not possible with + this function to tell apart the situation where the key was not present + from the situation where the key was mapped to an undefined value. + + If the map contains more than one key equal to \a key, it is undefined + which one this function will return. QCborMap does not allow inserting + duplicate keys, but it is possible to create such a map by decoding a CBOR + stream with them. They are usually not permitted and having duplicate keys + is usually an indication of a problem in the sender. + + \sa value(qint64), find(qint64), constFind(qint64), remove(qint64), contains(qint64) + operator[](QLatin1String), operator[](const QString &), operator[](const QCborOperator[] &) + */ + +/*! + \fn QCborValue QCborMap::take(qint64 key) + + Removes the key \a key and the corresponding value from the map and returns + the value, if it is found. If the map contains no such key, this function does nothing. + + If the map contains more than one key equal to \a key, it is undefined + which one this function will remove. QCborMap does not allow inserting + duplicate keys, but it is possible to create such a map by decoding a CBOR + stream with them. They are usually not permitted and having duplicate keys + is usually an indication of a problem in the sender. + + \sa value(qint64), operator[](qint64), find(qint64), contains(qint64), + take(QLatin1String), take(const QString &), take(const QCborValue &), insert() + */ + +/*! + \fn void QCborMap::remove(qint64 key) + + Removes the key \a key and the corresponding value from the map, if it is + found. If the map contains no such key, this function does nothing. + + If the map contains more than one key equal to \a key, it is undefined + which one this function will remove. QCborMap does not allow inserting + duplicate keys, but it is possible to create such a map by decoding a CBOR + stream with them. They are usually not permitted and having duplicate keys + is usually an indication of a problem in the sender. + + \sa value(qint64), operator[](qint64), find(qint64), contains(qint64) + remove(QLatin1String), remove(const QString &), remove(const QCborValue &) + */ + +/*! + \fn bool QCborMap::contains(qint64 key) const + + Returns true if this map contains a key-value pair identified by key \a + key. CBOR recommends using integer keys, since they occupy less space and + are simpler to encode and decode. + + \sa value(qint64), operator[](qint64), find(qint64), remove(qint64), + contains(QLatin1String), remove(const QString &), remove(const QCborValue &) + */ + +/*! + Returns a QCborValueRef to the value in this map that corresponds to key \a + key. CBOR recommends using integer keys, since they occupy less space and + are simpler to encode and decode. + + QCborValueRef has the exact same API as \l QCborValue, with one important + difference: if you assign new values to it, this map will be updated with + that new value. + + If the map did not have a key equal to \a key, one is inserted and this + function returns a reference to the new value, which will be a QCborValue + with an undefined value. For that reason, it is not possible with this + function to tell apart the situation where the key was not present from the + situation where the key was mapped to an undefined value. + + If the map contains more than one key equal to \a key, it is undefined + which one the return will reference. QCborMap does not allow inserting + duplicate keys, but it is possible to create such a map by decoding a CBOR + stream with them. They are usually not permitted and having duplicate keys + is usually an indication of a problem in the sender. + + \sa value(qint64), find(qint64), contains(qint64), remove(qint64), + operator[](QLatin1String), operator[](const QString &), operator[](const QCborValue &) + */ +QCborValueRef QCborMap::operator[](qint64 key) +{ + auto it = find(key); + if (it == constEnd()) { + // insert element + detach(it.item.i + 2); + d->append(key); + d->append(Undefined{}); + } + return { d.data(), it.item.i }; +} + +/*! + \fn QCborValue QCborMap::value(QLatin1String key) const + \overload + + Returns the QCborValue element in this map that corresponds to key \a key, + if there is one. + + If the map does not contain key \a key, this function returns a QCborValue + containing an undefined value. For that reason, it is not possible with + this function to tell apart the situation where the key was not present + from the situation where the key was mapped to an undefined value. + + If the map contains more than one key equal to \a key, it is undefined + which one this function will return. QCborMap does not allow inserting + duplicate keys, but it is possible to create such a map by decoding a CBOR + stream with them. They are usually not permitted and having duplicate keys + is usually an indication of a problem in the sender. + + \sa operator[](QLatin1String), find(QLatin1String), constFind(QLatin1String), + remove(QLatin1String), contains(QLatin1String) + value(qint64), value(const QString &), value(const QCborValue &) + */ + +/*! + \fn QCborValue QCborMap::operator[](QLatin1String key) const + \overload + + Returns the QCborValue element in this map that corresponds to key \a key, + if there is one. + + If the map does not contain key \a key, this function returns a QCborValue + containing an undefined value. For that reason, it is not possible with + this function to tell apart the situation where the key was not present + from the situation where the key was mapped to an undefined value. + + If the map contains more than one key equal to \a key, it is undefined + which one this function will return. QCborMap does not allow inserting + duplicate keys, but it is possible to create such a map by decoding a CBOR + stream with them. They are usually not permitted and having duplicate keys + is usually an indication of a problem in the sender. + + \sa value(QLatin1String), find(QLatin1String), constFind(QLatin1String), + remove(QLatin1String), contains(QLatin1String) + operator[](qint64), operator[](const QString &), operator[](const QCborOperator[] &) + */ + +/*! + \fn QCborValue QCborMap::take(QLatin1String key) + + Removes the key \a key and the corresponding value from the map and returns + the value, if it is found. If the map contains no such key, this function does nothing. + + If the map contains more than one key equal to \a key, it is undefined + which one this function will remove. QCborMap does not allow inserting + duplicate keys, but it is possible to create such a map by decoding a CBOR + stream with them. They are usually not permitted and having duplicate keys + is usually an indication of a problem in the sender. + + \sa value(QLatin1String), operator[](QLatin1String), find(QLatin1String), contains(QLatin1String), + take(qint64), take(const QString &), take(const QCborValue &), insert() + */ + +/*! + \fn void QCborMap::remove(QLatin1String key) + \overload + + Removes the key \a key and the corresponding value from the map, if it is + found. If the map contains no such key, this function does nothing. + + If the map contains more than one key equal to \a key, it is undefined + which one this function will remove. QCborMap does not allow inserting + duplicate keys, but it is possible to create such a map by decoding a CBOR + stream with them. They are usually not permitted and having duplicate keys + is usually an indication of a problem in the sender. + + \sa value(QLatin1String), operator[](QLatin1String), find(QLatin1String), contains(QLatin1String) + remove(qint64), remove(const QString &), remove(const QCborValue &) + */ + +/*! + \fn bool QCborMap::contains(QLatin1String key) const + \overload + + Returns true if this map contains a key-value pair identified by key \a + key. + + \sa value(QLatin1String), operator[](QLatin1String), find(QLatin1String), remove(QLatin1String), + contains(qint64), remove(const QString &), remove(const QCborValue &) + */ + +/*! + \overload + + Returns a QCborValueRef to the value in this map that corresponds to key \a + key. + + QCborValueRef has the exact same API as \l QCborValue, with one important + difference: if you assign new values to it, this map will be updated with + that new value. + + If the map did not have a key equal to \a key, one is inserted and this + function returns a reference to the new value, which will be a QCborValue + with an undefined value. For that reason, it is not possible with this + function to tell apart the situation where the key was not present from the + situation where the key was mapped to an undefined value. + + If the map contains more than one key equal to \a key, it is undefined + which one the return will reference. QCborMap does not allow inserting + duplicate keys, but it is possible to create such a map by decoding a CBOR + stream with them. They are usually not permitted and having duplicate keys + is usually an indication of a problem in the sender. + + \sa value(QLatin1String), find(QLatin1String), contains(QLatin1String), remove(QLatin1String), + operator[](qint64), operator[](const QString &), operator[](const QCborValue &) + */ +QCborValueRef QCborMap::operator[](QLatin1String key) +{ + auto it = find(key); + if (it == constEnd()) { + // insert element + detach(it.item.i + 2); + d->append(key); + d->append(Undefined{}); + } + return { d.data(), it.item.i }; +} + +/*! + \fn QCborValue QCborMap::value(const QString &key) const + \overload + + Returns the QCborValue element in this map that corresponds to key \a key, + if there is one. + + If the map does not contain key \a key, this function returns a QCborValue + containing an undefined value. For that reason, it is not possible with + this function to tell apart the situation where the key was not present + from the situation where the key was mapped to an undefined value. + + If the map contains more than one key equal to \a key, it is undefined + which one this function will return. QCborMap does not allow inserting + duplicate keys, but it is possible to create such a map by decoding a CBOR + stream with them. They are usually not permitted and having duplicate keys + is usually an indication of a problem in the sender. + + \sa operator[](const QString &), find(const QString &), constFind(const QString &), + remove(const QString &), contains(const QString &) + value(qint64), value(QLatin1String), value(const QCborValue &) + */ + +/*! + \fn QCborValue QCborMap::operator[](const QString &key) const + \overload + + Returns the QCborValue element in this map that corresponds to key \a key, + if there is one. + + If the map does not contain key \a key, this function returns a QCborValue + containing an undefined value. For that reason, it is not possible with + this function to tell apart the situation where the key was not present + from the situation where the key was mapped to an undefined value. + + If the map contains more than one key equal to \a key, it is undefined + which one this function will return. QCborMap does not allow inserting + duplicate keys, but it is possible to create such a map by decoding a CBOR + stream with them. They are usually not permitted and having duplicate keys + is usually an indication of a problem in the sender. + + \sa value(const QString &), find(const QString &), constFind(const QString &), + remove(const QString &), contains(const QString &) + operator[](qint64), operator[](QLatin1String), operator[](const QCborOperator[] &) + */ + +/*! + \fn QCborValue QCborMap::take(const QString &key) + + Removes the key \a key and the corresponding value from the map and returns + the value, if it is found. If the map contains no such key, this function does nothing. + + If the map contains more than one key equal to \a key, it is undefined + which one this function will remove. QCborMap does not allow inserting + duplicate keys, but it is possible to create such a map by decoding a CBOR + stream with them. They are usually not permitted and having duplicate keys + is usually an indication of a problem in the sender. + + \sa value(const QString &), operator[](const QString &), find(const QString &), contains(const QString &), + take(QLatin1String), take(qint64), take(const QCborValue &), insert() + */ + +/*! + \fn void QCborMap::remove(const QString &key) + \overload + + Removes the key \a key and the corresponding value from the map, if it is + found. If the map contains no such key, this function does nothing. + + If the map contains more than one key equal to \a key, it is undefined + which one this function will remove. QCborMap does not allow inserting + duplicate keys, but it is possible to create such a map by decoding a CBOR + stream with them. They are usually not permitted and having duplicate keys + is usually an indication of a problem in the sender. + + \sa value(const QString &), operator[](const QString &), find(const QString &), + contains(const QString &) + remove(qint64), remove(QLatin1String), remove(const QCborValue &) + */ + +/*! + \fn bool QCborMap::contains(const QString &key) const + \overload + + Returns true if this map contains a key-value pair identified by key \a + key. + + \sa value(const QString &), operator[](const QString &), find(const QString &), + remove(const QString &), + contains(qint64), remove(QLatin1String), remove(const QCborValue &) + */ + +/*! + \overload + + Returns a QCborValueRef to the value in this map that corresponds to key \a + key. + + QCborValueRef has the exact same API as \l QCborValue, with one important + difference: if you assign new values to it, this map will be updated with + that new value. + + If the map did not have a key equal to \a key, one is inserted and this + function returns a reference to the new value, which will be a QCborValue + with an undefined value. For that reason, it is not possible with this + function to tell apart the situation where the key was not present from the + situation where the key was mapped to an undefined value. + + If the map contains more than one key equal to \a key, it is undefined + which one the return will reference. QCborMap does not allow inserting + duplicate keys, but it is possible to create such a map by decoding a CBOR + stream with them. They are usually not permitted and having duplicate keys + is usually an indication of a problem in the sender. + + \sa value(const QString &), find(const QString &), contains(const QString &), remove(const QString &), + operator[](qint64), operator[](QLatin1String), operator[](const QCborValue &) + */ +QCborValueRef QCborMap::operator[](const QString & key) +{ + auto it = find(key); + if (it == constEnd()) { + // insert element + detach(it.item.i + 2); + d->append(key); + d->append(Undefined{}); + } + return { d.data(), it.item.i }; +} + +/*! + \fn QCborValue QCborMap::value(const QCborValue &key) const + + Returns the QCborValue element in this map that corresponds to key \a key, + if there is one. + + If the map does not contain key \a key, this function returns a QCborValue + containing an undefined value. For that reason, it is not possible with + this function to tell apart the situation where the key was not present + from the situation where the key was mapped to an undefined value. + + If the map contains more than one key equal to \a key, it is undefined + which one this function will return. QCborMap does not allow inserting + duplicate keys, but it is possible to create such a map by decoding a CBOR + stream with them. They are usually not permitted and having duplicate keys + is usually an indication of a problem in the sender. + + \sa operator[](const QCborValue &), find(const QCborValue &), constFind(const QCborValue &), + remove(const QCborValue &), contains(const QCborValue &) + value(qint64), value(QLatin1String), value(const QString &) + */ + +/*! + \fn QCborValue QCborMap::operator[](const QCborValue &key) const + + Returns the QCborValue element in this map that corresponds to key \a key, + if there is one. + + If the map does not contain key \a key, this function returns a QCborValue + containing an undefined value. For that reason, it is not possible with + this function to tell apart the situation where the key was not present + from the situation where the key was mapped to an undefined value. + + If the map contains more than one key equal to \a key, it is undefined + which one this function will return. QCborMap does not allow inserting + duplicate keys, but it is possible to create such a map by decoding a CBOR + stream with them. They are usually not permitted and having duplicate keys + is usually an indication of a problem in the sender. + + \sa value(const QCborValue &), find(const QCborValue &), constFind(const QCborValue &), + remove(const QCborValue &), contains(const QCborValue &) + operator[](qint64), operator[](QLatin1String), operator[](const QCborOperator[] &) + */ + +/*! + \fn QCborValue QCborMap::take(const QCborValue &key) + + Removes the key \a key and the corresponding value from the map and returns + the value, if it is found. If the map contains no such key, this function does nothing. + + If the map contains more than one key equal to \a key, it is undefined + which one this function will remove. QCborMap does not allow inserting + duplicate keys, but it is possible to create such a map by decoding a CBOR + stream with them. They are usually not permitted and having duplicate keys + is usually an indication of a problem in the sender. + + \sa value(const QCborValue &), operator[](const QCborValue &), find(const QCborValue &), contains(const QCborValue &), + take(QLatin1String), take(const QString &), take(qint64), insert() + */ + +/*! + \fn void QCborMap::remove(const QCborValue &key) + + Removes the key \a key and the corresponding value from the map, if it is + found. If the map contains no such key, this function does nothing. + + If the map contains more than one key equal to \a key, it is undefined + which one this function will remove. QCborMap does not allow inserting + duplicate keys, but it is possible to create such a map by decoding a CBOR + stream with them. They are usually not permitted and having duplicate keys + is usually an indication of a problem in the sender. + + \sa value(const QCborValue &), operator[](const QCborValue &), find(const QCborValue &), + contains(const QCborValue &) + remove(qint64), remove(QLatin1String), remove(const QString &) + */ + +/*! + \fn bool QCborMap::contains(const QCborValue &key) const + + Returns true if this map contains a key-value pair identified by key \a + key. + + \sa value(const QCborValue &), operator[](const QCborValue &), find(const QCborValue &), + remove(const QCborValue &), + contains(qint64), remove(QLatin1String), remove(const QString &) + */ + +/*! + \overload + + Returns a QCborValueRef to the value in this map that corresponds to key \a + key. + + QCborValueRef has the exact same API as \l QCborValue, with one important + difference: if you assign new values to it, this map will be updated with + that new value. + + If the map did not have a key equal to \a key, one is inserted and this + function returns a reference to the new value, which will be a QCborValue + with an undefined value. For that reason, it is not possible with this + function to tell apart the situation where the key was not present from the + situation where the key was mapped to an undefined value. + + If the map contains more than one key equal to \a key, it is undefined + which one the return will reference. QCborMap does not allow inserting + duplicate keys, but it is possible to create such a map by decoding a CBOR + stream with them. They are usually not permitted and having duplicate keys + is usually an indication of a problem in the sender. + + \sa value(const QCborValue &), find(const QCborValue &), contains(const QCborValue &), remove(const QCborValue &), + operator[](qint64), operator[](QLatin1String), operator[](const QString &) + */ +QCborValueRef QCborMap::operator[](const QCborValue &key) +{ + auto it = find(key); + if (it == constEnd()) { + // insert element + detach(it.item.i + 2); + d->append(key); + d->append(Undefined{}); + } + return { d.data(), it.item.i }; +} + +/*! + \fn QCborMap::iterator QCborMap::find(qint64 key) + \fn QCborMap::const_iterator QCborMap::find(qint64 key) const + + Returns a map iterator to the key-value pair whose key is \a key, if the + map contains such a pair. If it doesn't, this function returns end(). + + CBOR recommends using integer keys, since they occupy less + space and are simpler to encode and decode. + + If the map contains more than one key equal to \a key, it is undefined + which one this function will find. QCborMap does not allow inserting + duplicate keys, but it is possible to create such a map by decoding a CBOR + stream with them. They are usually not permitted and having duplicate keys + is usually an indication of a problem in the sender. + + \sa value(qint64), operator[](qint64), constFind(qint64), remove(qint64), contains(qint64) + value(QLatin1String), value(const QString &), value(const QCborValue &) + */ +QCborMap::iterator QCborMap::find(qint64 key) +{ + auto it = constFind(key); + if (it != constEnd()) + detach(); + return { d.data(), it.item.i }; +} + +/*! + \fn QCborMap::iterator QCborMap::find(QLatin1String key) + \fn QCborMap::const_iterator QCborMap::find(QLatin1String key) const + \overload + + Returns a map iterator to the key-value pair whose key is \a key, if the + map contains such a pair. If it doesn't, this function returns end(). + + If the map contains more than one key equal to \a key, it is undefined + which one this function will find. QCborMap does not allow inserting + duplicate keys, but it is possible to create such a map by decoding a CBOR + stream with them. They are usually not permitted and having duplicate keys + is usually an indication of a problem in the sender. + + \sa value(QLatin1String), operator[](QLatin1String), constFind(QLatin1String), + remove(QLatin1String), contains(QLatin1String) + value(qint64), value(const QString &), value(const QCborValue &) + */ +QCborMap::iterator QCborMap::find(QLatin1String key) +{ + auto it = constFind(key); + if (it != constEnd()) + detach(); + return { d.data(), it.item.i }; +} + +/*! + \fn QCborMap::iterator QCborMap::find(const QString & key) + \fn QCborMap::const_iterator QCborMap::find(const QString & key) const + \overload + + Returns a map iterator to the key-value pair whose key is \a key, if the + map contains such a pair. If it doesn't, this function returns end(). + + If the map contains more than one key equal to \a key, it is undefined + which one this function will find. QCborMap does not allow inserting + duplicate keys, but it is possible to create such a map by decoding a CBOR + stream with them. They are usually not permitted and having duplicate keys + is usually an indication of a problem in the sender. + + \sa value(const QString &), operator[](const QString &), constFind(const QString &), + remove(const QString &), contains(const QString &) + value(qint64), value(QLatin1String), value(const QCborValue &) + */ +QCborMap::iterator QCborMap::find(const QString & key) +{ + auto it = constFind(key); + if (it != constEnd()) + detach(); + return { d.data(), it.item.i }; +} + +/*! + \fn QCborMap::iterator QCborMap::find(const QCborValue &key) + \fn QCborMap::const_iterator QCborMap::find(const QCborValue &key) const + \overload + + Returns a map iterator to the key-value pair whose key is \a key, if the + map contains such a pair. If it doesn't, this function returns end(). + + If the map contains more than one key equal to \a key, it is undefined + which one this function will find. QCborMap does not allow inserting + duplicate keys, but it is possible to create such a map by decoding a CBOR + stream with them. They are usually not permitted and having duplicate keys + is usually an indication of a problem in the sender. + + \sa value(const QCborValue &), operator[](const QCborValue &), constFind(const QCborValue &), + remove(const QCborValue &), contains(const QCborValue &) + value(qint64), value(QLatin1String), value(const QString &) + */ +QCborMap::iterator QCborMap::find(const QCborValue &key) +{ + auto it = constFind(key); + if (it != constEnd()) + detach(); + return { d.data(), it.item.i }; +} + +/*! + Returns a map iterator to the key-value pair whose key is \a key, if the + map contains such a pair. If it doesn't, this function returns constEnd(). + + CBOR recommends using integer keys, since they occupy less + space and are simpler to encode and decode. + + If the map contains more than one key equal to \a key, it is undefined + which one this function will find. QCborMap does not allow inserting + duplicate keys, but it is possible to create such a map by decoding a CBOR + stream with them. They are usually not permitted and having duplicate keys + is usually an indication of a problem in the sender. + + \sa value(qint64), operator[](qint64), find(qint64), remove(qint64), contains(qint64) + value(QLatin1String), value(const QString &), value(const QCborValue &) + */ +QCborMap::const_iterator QCborMap::constFind(qint64 key) const +{ + for (qsizetype i = 0; i < 2 * size(); i += 2) { + const auto &e = d->elements.at(i); + if (e.type == QCborValue::Integer && e.value == key) + return { d.data(), i + 1 }; + } + return constEnd(); +} + +/*! + \overload + + Returns a map iterator to the key-value pair whose key is \a key, if the + map contains such a pair. If it doesn't, this function returns constEnd(). + + If the map contains more than one key equal to \a key, it is undefined + which one this function will find. QCborMap does not allow inserting + duplicate keys, but it is possible to create such a map by decoding a CBOR + stream with them. They are usually not permitted and having duplicate keys + is usually an indication of a problem in the sender. + + \sa value(QLatin1String), operator[](QLatin1String), find(QLatin1String), + remove(QLatin1String), contains(QLatin1String) + value(qint64), value(const QString &), value(const QCborValue &) + */ +QCborMap::const_iterator QCborMap::constFind(QLatin1String key) const +{ + for (qsizetype i = 0; i < 2 * size(); i += 2) { + if (d->stringEqualsElement(i, key)) + return { d.data(), i + 1 }; + } + return constEnd(); +} + +/*! + \overload + + Returns a map iterator to the key-value pair whose key is \a key, if the + map contains such a pair. If it doesn't, this function returns constEnd(). + + If the map contains more than one key equal to \a key, it is undefined + which one this function will find. QCborMap does not allow inserting + duplicate keys, but it is possible to create such a map by decoding a CBOR + stream with them. They are usually not permitted and having duplicate keys + is usually an indication of a problem in the sender. + + \sa value(const QString &), operator[](const QString &), find(const QString &), + remove(const QString &), contains(const QString &) + value(qint64), value(QLatin1String), value(const QCborValue &) + */ +QCborMap::const_iterator QCborMap::constFind(const QString & key) const +{ + for (qsizetype i = 0; i < 2 * size(); i += 2) { + if (d->stringEqualsElement(i, key)) + return { d.data(), i + 1 }; + } + return constEnd(); +} + +/*! + \overload + + Returns a map iterator to the key-value pair whose key is \a key, if the + map contains such a pair. If it doesn't, this function returns constEnd(). + + If the map contains more than one key equal to \a key, it is undefined + which one this function will find. QCborMap does not allow inserting + duplicate keys, but it is possible to create such a map by decoding a CBOR + stream with them. They are usually not permitted and having duplicate keys + is usually an indication of a problem in the sender. + + \sa value(const QCborValue &), operator[](const QCborValue &), find(const QCborValue &), + remove(const QCborValue &), contains(const QCborValue &), + value(qint64), value(QLatin1String), value(const QString &) + */ +QCborMap::const_iterator QCborMap::constFind(const QCborValue &key) const +{ + for (qsizetype i = 0; i < 2 * size(); i += 2) { + int cmp = d->compareElement(i, key); + if (cmp == 0) + return { d.data(), i + 1 }; + } + return constEnd(); +} + +/*! + \fn QCborMap::iterator QCborMap::insert(qint64 key, const QCborValue &value) + \overload + + Inserts the key \a key and value \a value into this map and returns a map + iterator pointing to the newly inserted pair. + + If the map already had a key equal to \a key, its value will be overwritten + by \a value. + + \sa erase(), remove(qint64), value(qint64), operator[](qint64), find(qint64), + contains(qint64), take(qint64), extract() + */ + +/*! + \fn QCborMap::iterator QCborMap::insert(QLatin1String key, const QCborValue &value) + \overload + + Inserts the key \a key and value \a value into this map and returns a map + iterator pointing to the newly inserted pair. + + If the map already had a key equal to \a key, its value will be overwritten + by \a value. + + \sa erase(), remove(QLatin1String), value(QLatin1String), operator[](QLatin1String), + find(QLatin1String), contains(QLatin1String), take(QLatin1String), extract() + */ + +/*! + \fn QCborMap::iterator QCborMap::insert(const QString &key, const QCborValue &value) + \overload + + Inserts the key \a key and value \a value into this map and returns a map + iterator pointing to the newly inserted pair. + + If the map already had a key equal to \a key, its value will be overwritten + by \a value. + + \sa erase(), remove(const QString &), value(const QString &), operator[](const QString &), + find(const QString &), contains(const QString &), take(const QString &), extract() + */ + +/*! + \fn QCborMap::iterator QCborMap::insert(const QCborValue &key, const QCborValue &value) + \overload + + Inserts the key \a key and value \a value into this map and returns a map + iterator pointing to the newly inserted pair. + + If the map already had a key equal to \a key, its value will be overwritten + by \a value. + + \sa erase(), remove(const QCborValue &), value(const QCborValue &), operator[](const QCborValue &), + find(const QCborValue &), contains(const QCborValue &), take(const QCborValue &), extract() + */ + +/*! + \fn QCborMap::iterator QCborMap::insert(value_type v) + \overload + + Inserts the key-value pair in \a v into this map and returns a map iterator + pointing to the newly inserted pair. + + If the map already had a key equal to \c{v.first}, its value will be + overwritten by \c{v.second}. + + \sa operator[], erase(), extract() + */ + + +/*! + \fn QCborMap::iterator QCborMap::erase(const_iterator it) + + Removes the key-value pair pointed to by the map iterator \a it and returns a + pointer to the next element, after removal. + + \sa remove(), begin(), end(), insert(), extract() + */ + +/*! + \overload + + Removes the key-value pair pointed to by the map iterator \a it and returns a + pointer to the next element, after removal. + + \sa remove(), begin(), end(), insert() + */ +QCborMap::iterator QCborMap::erase(QCborMap::iterator it) +{ + detach(); + + // remove both key and value + // ### optimize? + d->removeAt(it.item.i - 1); + d->removeAt(it.item.i - 1); + return it; +} + +/*! + \fn QCborValue QCborMap::extract(iterator it) + \fn QCborValue QCborMap::extract(const_iterator it) + + Extracts a value from the map at the position indicated by iterator \a it + and returns the value so extracted. + + \sa insert(), erase(), take(), remove() + */ +QCborValue QCborMap::extract(iterator it) +{ + detach(); + QCborValue v = d->extractAt(it.item.i); + // remove both key and value + // ### optimize? + d->removeAt(it.item.i - 1); + d->removeAt(it.item.i - 1); + + return v; +} + +/*! + \fn bool QCborMap::empty() const + + Synonym for isEmpty(). This function is provided for compatibility with + generic code that uses the Standard Library API. + + Returns true if this map is empty (size() == 0). + + \sa isEmpty(), size() + */ + +/*! + \fn int QCborMap::compare(const QCborMap &other) const + + Compares this map and \a other, comparing each element in sequence, and + returns an integer that indicates whether this map should be sorted prior + to (if the result is negative) or after \a other (if the result is + positive). If this function returns 0, the two maps are equal and contain + the same elements. + + Note that CBOR maps are unordered, which means that two maps containing the + very same pairs but in different order will still compare differently. To + avoid this, it is recommended to insert elements into the map in a + predictable order, such as by ascending key value. In fact, maps with keys + in sorted order are required for Canonical CBOR representation. + + For more information on CBOR sorting order, see QCborValue::compare(). + + \sa QCborValue::compare(), QCborArray::compare(), operator==() + */ + +/*! + \fn bool QCborMap::operator==(const QCborMap &other) const + + Compares this map and \a other, comparing each element in sequence, and + returns true if the two maps contains the same elements in the same order, + false otherwise. + + Note that CBOR maps are unordered, which means that two maps containing the + very same pairs but in different order will still compare differently. To + avoid this, it is recommended to insert elements into the map in a + predictable order, such as by ascending key value. In fact, maps with keys + in sorted order are required for Canonical CBOR representation. + + For more information on CBOR equality in Qt, see, QCborValue::compare(). + + \sa compare(), QCborValue::operator==(), QCborMap::operator==(), + operator!=(), operator<() + */ + +/*! + \fn bool QCborMap::operator!=(const QCborMap &other) const + + Compares this map and \a other, comparing each element in sequence, and + returns true if the two maps contains any different elements or elements in + different orders, false otherwise. + + Note that CBOR maps are unordered, which means that two maps containing the + very same pairs but in different order will still compare differently. To + avoid this, it is recommended to insert elements into the map in a + predictable order, such as by ascending key value. In fact, maps with keys + in sorted order are required for Canonical CBOR representation. + + For more information on CBOR equality in Qt, see, QCborValue::compare(). + + \sa compare(), QCborValue::operator==(), QCborMap::operator==(), + operator==(), operator<() + */ + +/*! + \fn bool QCborMap::operator<(const QCborMap &other) const + + Compares this map and \a other, comparing each element in sequence, and + returns true if this map should be sorted before \a other, false + otherwise. + + Note that CBOR maps are unordered, which means that two maps containing the + very same pairs but in different order will still compare differently. To + avoid this, it is recommended to insert elements into the map in a + predictable order, such as by ascending key value. In fact, maps with keys + in sorted order are required for Canonical CBOR representation. + + For more information on CBOR sorting order, see QCborValue::compare(). + + \sa compare(), QCborValue::operator==(), QCborMap::operator==(), + operator==(), operator!=() + */ + +void QCborMap::detach(qsizetype reserved) +{ + d = QCborContainerPrivate::detach(d.data(), reserved ? reserved : size() * 2); +} + +/*! + \class QCborMap::Iterator + \inmodule QtCore + \ingroup cbor + \reentrant + \since 5.12 + + \brief The QCborMap::Iterator class provides an STL-style non-const iterator for QCborMap. + + QCborMap::Iterator allows you to iterate over a QCborMap and to modify the + value (but not the key) stored under a particular key. If you want to + iterate over a const QCborMap, you should use QCborMap::ConstIterator. It + is generally good practice to use QCborMap::ConstIterator on a non-const + QCborMap as well, unless you need to change the QCborMap through the + iterator. Const iterators are slightly faster, and improve code + readability. + + You must initialize the iterator using a QCborMap function like + QCborMap::begin(), QCborMap::end(), or QCborMap::find() before you can + start iterating.. + + Multiple iterators can be used on the same object. Existing iterators will however + become dangling once the object gets modified. + + \sa QCborMap::ConstIterator +*/ + +/*! + \typedef QCborMap::Iterator::difference_type + \internal +*/ + +/*! + \typedef QCborMap::Iterator::iterator_category + + A synonym for \e {std::random_access_iterator_tag} indicating + this iterator is a random-access iterator. +*/ + +/*! + \typedef QCborMap::Iterator::reference + \internal +*/ + +/*! + \typedef QCborMap::Iterator::value_type + \internal +*/ + +/*! + \typedef QCborMap::Iterator::pointer + \internal +*/ + +/*! + \fn QCborMap::Iterator::Iterator() + + Constructs an uninitialized iterator. + + Functions like key(), value(), and operator++() must not be + called on an uninitialized iterator. Use operator=() to assign a + value to it before using it. + + \sa QCborMap::begin(), QCborMap::end() +*/ + +/*! + \fn QCborMap::Iterator::Iterator(const Iterator &other) + + Constructs an iterator as a copy of \a other. + */ + +/*! + \fn QCborMap::Iterator &QCborMap::Iterator::operator=(const Iterator &other) + + Makes this iterator a copy of \a other and returns a reference to this + iterator. + */ + +/*! + \fn QCborValue QCborMap::Iterator::key() const + + Returns the current item's key. + + There is no direct way of changing an item's key through an iterator, + although it can be done by calling QCborMap::erase() followed by + QCborMap::insert(). + + \sa value() +*/ + +/*! + \fn QCborValueRef QCborMap::Iterator::value() const + + Returns a modifiable reference to the current item's value. + + You can change the value for a key by using value() on the left side of an + assignment. + + The return value is of type QCborValueRef, a helper class for QCborArray + and QCborMap. When you get an object of type QCborValueRef, you can use it + as if it were a reference to a QCborValue. If you assign to it, the + assignment will apply to the element in the QCborArray or QCborMap from + which you got the reference. + + \sa key(), operator*() +*/ + +/*! + \fn QCborMap::Iterator::value_type QCborMap::Iterator::operator*() const + + Returns a pair containing the current item's key and a modifiable reference + to the current item's value. + + The second element of the pair is of type QCborValueRef, a helper class for + QCborArray and QCborMap. When you get an object of type QCborValueRef, you + can use it as if it were a reference to a QCborValue. If you assign to it, + the assignment will apply to the element in the QCborArray or QCborMap from + which you got the reference. + + \sa key(), value() +*/ + +/*! + \fn QCborValueRef *QCborMap::Iterator::operator->() const + + Returns a pointer to a modifiable reference to the current pair's value. +*/ + +/*! + \fn bool QCborMap::Iterator::operator==(const Iterator &other) const + \fn bool QCborMap::Iterator::operator==(const ConstIterator &other) const + + Returns \c true if \a other points to the same entry in the map as this + iterator; otherwise returns \c false. + + \sa operator!=() +*/ + +/*! + \fn bool QCborMap::Iterator::operator!=(const Iterator &other) const + \fn bool QCborMap::Iterator::operator!=(const ConstIterator &other) const + + Returns \c true if \a other points to a different entry in the map than + this iterator; otherwise returns \c false. + + \sa operator==() +*/ + +/*! + \fn bool QCborMap::Iterator::operator<(const Iterator& other) const + \fn bool QCborMap::Iterator::operator<(const ConstIterator& other) const + + Returns \c true if the entry in the map pointed to by this iterator + occurs before the entry pointed to by the \a other iterator. +*/ + +/*! + \fn bool QCborMap::Iterator::operator<=(const Iterator& other) const + \fn bool QCborMap::Iterator::operator<=(const ConstIterator& other) const + + Returns \c true if the entry in the map pointed to by this iterator + occurs before or is the same entry as is pointed to by the \a other + iterator. +*/ + +/*! + \fn bool QCborMap::Iterator::operator>(const Iterator& other) const + \fn bool QCborMap::Iterator::operator>(const ConstIterator& other) const + + Returns \c true if the entry in the map pointed to by this iterator + occurs after the entry pointed to by the \a other iterator. + */ + +/*! + \fn bool QCborMap::Iterator::operator>=(const Iterator& other) const + \fn bool QCborMap::Iterator::operator>=(const ConstIterator& other) const + + Returns \c true if the entry in the map pointed to by this iterator + occurs after or is the same entry as is pointed to by the \a other + iterator. +*/ + +/*! + \fn QCborMap::Iterator &QCborMap::Iterator::operator++() + + The prefix ++ operator, \c{++i}, advances the iterator to the next item in + the map and returns this iterator. + + Calling this function on QCborMap::end() leads to undefined results. + + \sa operator--() +*/ + +/*! + \fn QCborMap::Iterator QCborMap::Iterator::operator++(int) + \overload + + The postfix ++ operator, \c{i++}, advances the iterator to the next item in + the map and returns an iterator to the previously current item. +*/ + +/*! + \fn QCborMap::Iterator QCborMap::Iterator::operator--() + + The prefix -- operator, \c{--i}, makes the preceding item current and + returns this iterator. + + Calling this function on QCborMap::begin() leads to undefined results. + + \sa operator++() +*/ + +/*! + \fn QCborMap::Iterator QCborMap::Iterator::operator--(int) + \overload + + The postfix -- operator, \c{i--}, makes the preceding item current and + returns an iterator pointing to the previously current item. +*/ + +/*! + \fn QCborMap::Iterator QCborMap::Iterator::operator+(qsizetype j) const + + Returns an iterator to the item at \a j positions forward from this + iterator. If \a j is negative, the iterator goes backward. + + \sa operator-() +*/ + +/*! + \fn QCborMap::Iterator QCborMap::Iterator::operator-(qsizetype j) const + + Returns an iterator to the item at \a j positions backward from this + iterator. If \a j is negative, the iterator goes forward. + + \sa operator+() +*/ + +/*! + \fn qsizetype QCborMap::Iterator::operator-(QCborMap::Iterator j) const + + Returns the position of the item at iterator \a j relative to the item + at this iterator. If the item at \a j is forward of this time, the returned + value is negative. + + \sa operator+() +*/ + +/*! + \fn QCborMap::Iterator &QCborMap::Iterator::operator+=(qsizetype j) + + Advances the iterator by \a j items. If \a j is negative, the iterator goes + backward. Returns a reference to this iterator. + + \sa operator-=(), operator+() +*/ + +/*! + \fn QCborMap::Iterator &QCborMap::Iterator::operator-=(qsizetype j) + + Makes the iterator go back by \a j items. If \a j is negative, the iterator + goes forward. Returns a reference to this iterator. + + \sa operator+=(), operator-() +*/ + +/*! + \class QCborMap::ConstIterator + \inmodule QtCore + \ingroup cbor + \since 5.12 + + \brief The QCborMap::ConstIterator class provides an STL-style const iterator for QCborMap. + + QCborMap::ConstIterator allows you to iterate over a QCborMap. If you want + to modify the QCborMap as you iterate over it, you must use + QCborMap::Iterator instead. It is generally good practice to use + QCborMap::ConstIterator, even on a non-const QCborMap, when you don't need + to change the QCborMap through the iterator. Const iterators are slightly + faster and improve code readability. + + You must initialize the iterator using a QCborMap function like + QCborMap::begin(), QCborMap::end(), or QCborMap::find() before you can + start iterating.. + + Multiple iterators can be used on the same object. Existing iterators + will however become dangling if the object gets modified. + + \sa QCborMap::Iterator +*/ + +/*! + \typedef QCborMap::ConstIterator::difference_type + \internal +*/ + +/*! + \typedef QCborMap::ConstIterator::iterator_category + + A synonym for \e {std::random_access_iterator_tag} indicating + this iterator is a random-access iterator. +*/ + +/*! + \typedef QCborMap::ConstIterator::reference + \internal +*/ + +/*! + \typedef QCborMap::ConstIterator::value_type + \internal +*/ + +/*! + \typedef QCborMap::ConstIterator::pointer + \internal +*/ + +/*! + \fn QCborMap::ConstIterator::ConstIterator() + + Constructs an uninitialized iterator. + + Functions like key(), value(), and operator++() must not be + called on an uninitialized iterator. Use operator=() to assign a + value to it before using it. + + \sa QCborMap::constBegin(), QCborMap::constEnd() +*/ + +/*! + \fn QCborMap::ConstIterator::ConstIterator(const ConstIterator &other) + + Constructs an iterator as a copy of \a other. + */ + +/*! + \fn QCborMap::ConstIterator &QCborMap::ConstIterator::operator=(const ConstIterator &other) + + Makes this iterator a copy of \a other and returns a reference to this + iterator. + */ + +/*! + \fn QString QCborMap::ConstIterator::key() const + + Returns the current item's key. + + \sa value() +*/ + +/*! + \fn QCborValue QCborMap::ConstIterator::value() const + + Returns the current item's value. + + \sa key(), operator*() +*/ + +/*! + \fn QCborMap::ConstIterator::value_type QCborMap::ConstIterator::operator*() const + + Returns a pair containing the curent item's key and value. + + \sa key(), value() + */ + +/*! + \fn const QCborValueRef *QCborMap::ConstIterator::operator->() const + + Returns a pointer to the current pair's value. + */ + +/*! + \fn bool QCborMap::ConstIterator::operator==(const ConstIterator &other) const + \fn bool QCborMap::ConstIterator::operator==(const Iterator &other) const + + Returns \c true if \a other points to the same entry in the map as this + iterator; otherwise returns \c false. + + \sa operator!=() +*/ + +/*! + \fn bool QCborMap::ConstIterator::operator!=(const ConstIterator &other) const + \fn bool QCborMap::ConstIterator::operator!=(const Iterator &other) const + + Returns \c true if \a other points to a different entry in the map than + this iterator; otherwise returns \c false. + + \sa operator==() + */ + +/*! + \fn bool QCborMap::ConstIterator::operator<(const Iterator &other) const + \fn bool QCborMap::ConstIterator::operator<(const ConstIterator &other) const + + Returns \c true if the entry in the map pointed to by this iterator + occurs before the entry pointed to by the \a other iterator. +*/ + +/*! + \fn bool QCborMap::ConstIterator::operator<=(const Iterator &other) const + \fn bool QCborMap::ConstIterator::operator<=(const ConstIterator &other) const + + Returns \c true if the entry in the map pointed to by this iterator + occurs before or is the same entry as is pointed to by the \a other + iterator. +*/ + +/*! + \fn bool QCborMap::ConstIterator::operator>(const Iterator &other) const + \fn bool QCborMap::ConstIterator::operator>(const ConstIterator &other) const + + Returns \c true if the entry in the map pointed to by this iterator + occurs after the entry pointed to by the \a other iterator. +*/ + +/*! + \fn bool QCborMap::ConstIterator::operator>=(const Iterator &other) const + \fn bool QCborMap::ConstIterator::operator>=(const ConstIterator &other) const + + Returns \c true if the entry in the map pointed to by this iterator + occurs after or is the same entry as is pointed to by the \a other + iterator. +*/ + +/*! + \fn QCborMap::ConstIterator &QCborMap::ConstIterator::operator++() + + The prefix ++ operator, \c{++i}, advances the iterator to the next item in + the map and returns this iterator. + + Calling this function on QCborMap::end() leads to undefined results. + + \sa operator--() +*/ + +/*! + \fn QCborMap::ConstIterator QCborMap::ConstIterator::operator++(int) + \overload + + The postfix ++ operator, \c{i++}, advances the iterator to the next item in + the map and returns an iterator to the previously current item. + */ + +/*! + \fn QCborMap::ConstIterator &QCborMap::ConstIterator::operator--() + + The prefix -- operator, \c{--i}, makes the preceding item current and + returns this iterator. + + Calling this function on QCborMap::begin() leads to undefined results. + + \sa operator++() +*/ + +/*! + \fn QCborMap::ConstIterator QCborMap::ConstIterator::operator--(int) + \overload + + The postfix -- operator, \c{i--}, makes the preceding item current and + returns an iterator pointing to the previously current item. + */ + +/*! + \fn QCborMap::ConstIterator QCborMap::ConstIterator::operator+(qsizetype j) const + + Returns an iterator to the item at \a j positions forward from this + iterator. If \a j is negative, the iterator goes backward. + + \sa operator-() +*/ + +/*! + \fn QCborMap::ConstIterator QCborMap::ConstIterator::operator-(qsizetype j) const + + Returns an iterator to the item at \a j positions backward from this + iterator. If \a j is negative, the iterator goes forward. + + \sa operator+() +*/ + +/*! + \fn qsizetype QCborMap::ConstIterator::operator-(QCborMap::ConstIterator j) const + + Returns the position of the item at iterator \a j relative to the item + at this iterator. If the item at \a j is forward of this time, the returned + value is negative. + + \sa operator+() +*/ + +/*! + \fn QCborMap::ConstIterator &QCborMap::ConstIterator::operator+=(qsizetype j) + + Advances the iterator by \a j items. If \a j is negative, the iterator goes + backward. Returns a reference to this iterator. + + \sa operator-=(), operator+() +*/ + +/*! + \fn QCborMap::ConstIterator &QCborMap::ConstIterator::operator-=(qsizetype j) + + Makes the iterator go back by \a j items. If \a j is negative, the iterator + goes forward. Returns a reference to this iterator. + + \sa operator+=(), operator-() +*/ + +#if !defined(QT_NO_DEBUG_STREAM) +QDebug operator<<(QDebug dbg, const QCborMap &m) +{ + QDebugStateSaver saver(dbg); + dbg.nospace() << "QCborMap{"; + const char *open = "{"; + for (auto pair : m) { + dbg << open << pair.first << ", " << pair.second << '}'; + open = ", {"; + } + return dbg << '}'; +} +#endif + +QT_END_NAMESPACE diff --git a/src/corelib/serialization/qcbormap.h b/src/corelib/serialization/qcbormap.h new file mode 100644 index 0000000000..2e9422cf94 --- /dev/null +++ b/src/corelib/serialization/qcbormap.h @@ -0,0 +1,346 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QCBORMAP_H +#define QCBORMAP_H + +#include <QtCore/qcborvalue.h> +#include <QtCore/qpair.h> + +#include <initializer_list> + +QT_BEGIN_NAMESPACE + +template <class Key, class T> class QMap; +typedef QMap<QString, QVariant> QVariantMap; +template <class Key, class T> class QHash; +typedef QHash<QString, QVariant> QVariantHash; +class QJsonObject; + +class QCborContainerPrivate; +class Q_CORE_EXPORT QCborMap +{ +public: + typedef QPair<QCborValue, QCborValue> value_type; + typedef QCborValue key_type; + typedef QCborValue mapped_type; + typedef qsizetype size_type; + + class ConstIterator; + class Iterator { + mutable QCborValueRef item; // points to the value + friend class ConstIterator; + friend class QCborMap; + Iterator(QCborContainerPrivate *dd, qsizetype ii) : item(dd, ii) {} + public: + typedef std::random_access_iterator_tag iterator_category; + typedef qsizetype difference_type; + typedef QPair<const QCborValueRef, QCborValueRef> value_type; + typedef QPair<const QCborValueRef, QCborValueRef> reference; + typedef QPair<const QCborValueRef, QCborValueRef> pointer; + + Q_DECL_CONSTEXPR Iterator() = default; + Q_DECL_CONSTEXPR Iterator(const Iterator &) = default; + Iterator &operator=(const Iterator &other) + { + // rebind the reference + item.d = other.item.d; + item.i = other.item.i; + return *this; + } + + value_type operator*() const { return { {item.d, item.i - 1}, item }; } + QCborValueRef *operator->() const { return &item; } + QCborValue key() const { return QCborValueRef(item.d, item.i - 1); } + QCborValueRef value() const { return item; } + + bool operator==(const Iterator &o) const { return item.d == o.item.d && item.i == o.item.i; } + bool operator!=(const Iterator &o) const { return !(*this == o); } + bool operator<(const Iterator& other) const { Q_ASSERT(item.d == other.item.d); return item.i < other.item.i; } + bool operator<=(const Iterator& other) const { Q_ASSERT(item.d == other.item.d); return item.i <= other.item.i; } + bool operator>(const Iterator& other) const { Q_ASSERT(item.d == other.item.d); return item.i > other.item.i; } + bool operator>=(const Iterator& other) const { Q_ASSERT(item.d == other.item.d); return item.i >= other.item.i; } + bool operator==(const ConstIterator &o) const { return item.d == o.item.d && item.i == o.item.i; } + bool operator!=(const ConstIterator &o) const { return !(*this == o); } + bool operator<(const ConstIterator& other) const { Q_ASSERT(item.d == other.item.d); return item.i < other.item.i; } + bool operator<=(const ConstIterator& other) const { Q_ASSERT(item.d == other.item.d); return item.i <= other.item.i; } + bool operator>(const ConstIterator& other) const { Q_ASSERT(item.d == other.item.d); return item.i > other.item.i; } + bool operator>=(const ConstIterator& other) const { Q_ASSERT(item.d == other.item.d); return item.i >= other.item.i; } + Iterator &operator++() { item.i += 2; return *this; } + Iterator operator++(int) { Iterator n = *this; item.i += 2; return n; } + Iterator &operator--() { item.i -= 2; return *this; } + Iterator operator--(int) { Iterator n = *this; item.i -= 2; return n; } + Iterator &operator+=(qsizetype j) { item.i += 2 * j; return *this; } + Iterator &operator-=(qsizetype j) { item.i -= 2 * j; return *this; } + Iterator operator+(qsizetype j) const { return Iterator({ item.d, item.i + 2 * j }); } + Iterator operator-(qsizetype j) const { return Iterator({ item.d, item.i - 2 * j }); } + qsizetype operator-(Iterator j) const { return (item.i - j.item.i) / 2; } + }; + + class ConstIterator { + QCborValueRef item; // points to the value + friend class Iterator; + friend class QCborMap; + ConstIterator(QCborContainerPrivate *dd, qsizetype ii) : item(dd, ii) {} + public: + typedef std::random_access_iterator_tag iterator_category; + typedef qsizetype difference_type; + typedef QPair<const QCborValueRef, const QCborValueRef> value_type; + typedef QPair<const QCborValueRef, const QCborValueRef> reference; + typedef QPair<const QCborValueRef, const QCborValueRef> pointer; + + Q_DECL_CONSTEXPR ConstIterator() = default; + Q_DECL_CONSTEXPR ConstIterator(const ConstIterator &) = default; + ConstIterator &operator=(const ConstIterator &other) + { + // rebind the reference + item.d = other.item.d; + item.i = other.item.i; + return *this; + } + + value_type operator*() const { return { {item.d, item.i - 1}, item }; } + const QCborValueRef *operator->() const { return &item; } + QCborValue key() const { return QCborValueRef(item.d, item.i - 1); } + QCborValueRef value() const { return item; } + + bool operator==(const Iterator &o) const { return item.d == o.item.d && item.i == o.item.i; } + bool operator!=(const Iterator &o) const { return !(*this == o); } + bool operator<(const Iterator& other) const { Q_ASSERT(item.d == other.item.d); return item.i < other.item.i; } + bool operator<=(const Iterator& other) const { Q_ASSERT(item.d == other.item.d); return item.i <= other.item.i; } + bool operator>(const Iterator& other) const { Q_ASSERT(item.d == other.item.d); return item.i > other.item.i; } + bool operator>=(const Iterator& other) const { Q_ASSERT(item.d == other.item.d); return item.i >= other.item.i; } + bool operator==(const ConstIterator &o) const { return item.d == o.item.d && item.i == o.item.i; } + bool operator!=(const ConstIterator &o) const { return !(*this == o); } + bool operator<(const ConstIterator& other) const { Q_ASSERT(item.d == other.item.d); return item.i < other.item.i; } + bool operator<=(const ConstIterator& other) const { Q_ASSERT(item.d == other.item.d); return item.i <= other.item.i; } + bool operator>(const ConstIterator& other) const { Q_ASSERT(item.d == other.item.d); return item.i > other.item.i; } + bool operator>=(const ConstIterator& other) const { Q_ASSERT(item.d == other.item.d); return item.i >= other.item.i; } + ConstIterator &operator++() { item.i += 2; return *this; } + ConstIterator operator++(int) { ConstIterator n = *this; item.i += 2; return n; } + ConstIterator &operator--() { item.i -= 2; return *this; } + ConstIterator operator--(int) { ConstIterator n = *this; item.i -= 2; return n; } + ConstIterator &operator+=(qsizetype j) { item.i += 2 * j; return *this; } + ConstIterator &operator-=(qsizetype j) { item.i -= 2 * j; return *this; } + ConstIterator operator+(qsizetype j) const { return ConstIterator({ item.d, item.i + 2 * j }); } + ConstIterator operator-(qsizetype j) const { return ConstIterator({ item.d, item.i - 2 * j }); } + qsizetype operator-(ConstIterator j) const { return (item.i - j.item.i) / 2; } + }; + + QCborMap() noexcept; + QCborMap(const QCborMap &other) noexcept; + QCborMap &operator=(const QCborMap &other) noexcept; + QCborMap(std::initializer_list<value_type> args) + : QCborMap() + { + detach(args.size()); + for (auto pair : args) + insert(pair.first, pair.second); + } + ~QCborMap(); + + void swap(QCborMap &other) noexcept + { + qSwap(d, other.d); + } + + QCborValue toCborValue() const { return *this; } + + qsizetype size() const noexcept Q_DECL_PURE_FUNCTION; + bool isEmpty() const { return size() == 0; } + QVector<QCborValue> keys() const; + + QCborValue value(qint64 key) const + { const_iterator it = find(key); return it == end() ? QCborValue() : it.value(); } + QCborValue value(QLatin1String key) const + { const_iterator it = find(key); return it == end() ? QCborValue() : it.value(); } + QCborValue value(const QString & key) const + { const_iterator it = find(key); return it == end() ? QCborValue() : it.value(); } + QCborValue value(const QCborValue &key) const + { const_iterator it = find(key); return it == end() ? QCborValue() : it.value(); } + QCborValue operator[](qint64 key) const + { const_iterator it = find(key); return it == end() ? QCborValue() : it.value(); } + QCborValue operator[](QLatin1String key) const + { const_iterator it = find(key); return it == end() ? QCborValue() : it.value(); } + QCborValue operator[](const QString & key) const + { const_iterator it = find(key); return it == end() ? QCborValue() : it.value(); } + QCborValue operator[](const QCborValue &key) const + { const_iterator it = find(key); return it == end() ? QCborValue() : it.value(); } + QCborValueRef operator[](qint64 key); + QCborValueRef operator[](QLatin1String key); + QCborValueRef operator[](const QString & key); + QCborValueRef operator[](const QCborValue &key); + + QCborValue take(qint64 key) + { const_iterator it = constFind(key); if (it != constEnd()) return extract(it); return QCborValue(); } + QCborValue take(QLatin1String key) + { const_iterator it = constFind(key); if (it != constEnd()) return extract(it); return QCborValue(); } + QCborValue take(const QString &key) + { const_iterator it = constFind(key); if (it != constEnd()) return extract(it); return QCborValue(); } + QCborValue take(const QCborValue &key) + { const_iterator it = constFind(key); if (it != constEnd()) return extract(it); return QCborValue(); } + void remove(qint64 key) + { const_iterator it = constFind(key); if (it != constEnd()) erase(it); } + void remove(QLatin1String key) + { const_iterator it = constFind(key); if (it != constEnd()) erase(it); } + void remove(const QString & key) + { const_iterator it = constFind(key); if (it != constEnd()) erase(it); } + void remove(const QCborValue &key) + { const_iterator it = constFind(key); if (it != constEnd()) erase(it); } + bool contains(qint64 key) const + { const_iterator it = find(key); return it != end(); } + bool contains(QLatin1String key) const + { const_iterator it = find(key); return it != end(); } + bool contains(const QString & key) const + { const_iterator it = find(key); return it != end(); } + bool contains(const QCborValue &key) const + { const_iterator it = find(key); return it != end(); } + + int compare(const QCborMap &other) const noexcept Q_DECL_PURE_FUNCTION; +#if 0 && QT_HAS_INCLUDE(<compare>) + std::strong_ordering operator<=>(const QCborMap &other) const + { + int c = compare(other); + if (c > 0) return std::strong_ordering::greater; + if (c == 0) return std::strong_ordering::equivalent; + return std::strong_ordering::less; + } +#else + bool operator==(const QCborMap &other) const noexcept + { return compare(other) == 0; } + bool operator!=(const QCborMap &other) const noexcept + { return !(*this == other); } + bool operator<(const QCborMap &other) const + { return compare(other) < 0; } +#endif + + typedef Iterator iterator; + typedef ConstIterator const_iterator; + iterator begin() { detach(); return iterator{d.data(), 1}; } + const_iterator constBegin() const { return const_iterator{d.data(), 1}; } + const_iterator begin() const { return constBegin(); } + const_iterator cbegin() const { return constBegin(); } + iterator end() { detach(); return iterator{d.data(), 2 * size() + 1}; } + const_iterator constEnd() const { return const_iterator{d.data(), 2 * size() + 1}; } + const_iterator end() const { return constEnd(); } + const_iterator cend() const { return constEnd(); } + iterator erase(iterator it); + iterator erase(const_iterator it) { return erase(iterator{ it.item.d, it.item.i }); } + QCborValue extract(iterator it); + QCborValue extract(const_iterator it) { return extract(iterator{ it.item.d, it.item.i }); } + bool empty() const { return isEmpty(); } + + iterator find(qint64 key); + iterator find(QLatin1String key); + iterator find(const QString & key); + iterator find(const QCborValue &key); + const_iterator constFind(qint64 key) const; + const_iterator constFind(QLatin1String key) const; + const_iterator constFind(const QString & key) const; + const_iterator constFind(const QCborValue &key) const; + const_iterator find(qint64 key) const { return constFind(key); } + const_iterator find(QLatin1String key) const { return constFind(key); } + const_iterator find(const QString & key) const { return constFind(key); } + const_iterator find(const QCborValue &key) const { return constFind(key); } + + iterator insert(qint64 key, const QCborValue &value_) + { + QCborValueRef v = operator[](key); // detaches + v = value_; + return { d.data(), v.i }; + } + iterator insert(QLatin1String key, const QCborValue &value_) + { + QCborValueRef v = operator[](key); // detaches + v = value_; + return { d.data(), v.i }; + } + iterator insert(const QString &key, const QCborValue &value_) + { + QCborValueRef v = operator[](key); // detaches + v = value_; + return { d.data(), v.i }; + } + iterator insert(const QCborValue &key, const QCborValue &value_) + { + QCborValueRef v = operator[](key); // detaches + v = value_; + return { d.data(), v.i }; + } + iterator insert(value_type v) { return insert(v.first, v.second); } + + static QCborMap fromVariantMap(const QVariantMap &map); + static QCborMap fromVariantHash(const QVariantHash &hash); + static QCborMap fromJsonObject(const QJsonObject &o); + QVariantMap toVariantMap() const; + QVariantHash toVariantHash() const; + QJsonObject toJsonObject() const; + +private: + void detach(qsizetype reserve = 0); + + friend QCborValue; + explicit QCborMap(QCborContainerPrivate &dd) noexcept; + QExplicitlySharedDataPointer<QCborContainerPrivate> d; +}; + +Q_DECLARE_SHARED(QCborMap) + +inline QCborValue::QCborValue(QCborMap &&m) + : n(-1), container(m.d.take()), t(Map) +{ +} + +inline QCborMap QCborValueRef::toMap() const +{ + return concrete().toMap(); +} + +inline QCborMap QCborValueRef::toMap(const QCborMap &m) const +{ + return concrete().toMap(m); +} + +#if !defined(QT_NO_DEBUG_STREAM) +Q_CORE_EXPORT QDebug operator<<(QDebug, const QCborMap &m); +#endif + +QT_END_NAMESPACE + +#endif // QCBORMAP_H diff --git a/src/corelib/serialization/qcborstream.cpp b/src/corelib/serialization/qcborstream.cpp new file mode 100644 index 0000000000..aed286a11f --- /dev/null +++ b/src/corelib/serialization/qcborstream.cpp @@ -0,0 +1,2954 @@ +/**************************************************************************** +** +** 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> + +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; +} +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_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. + + \code + QCborValue(QCborTag(2), QByteArray("\x01\0\0\0\0\0\0\0\0", 9)); + \endcode + + \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 (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). + */ + +/*! + \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 from TinyCBOR + CborError err = CborError(int(c)); + 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 + + \code + writer.startMap(4); // 4 elements in the map + + writer.append(QLatin1String("label")); + writer.append(QLatin1String("journald")); + + writer.append(QLatin1String("autoDetect")); + writer.append(false); + + writer.append(QLatin1String("condition")); + writer.append(QLatin1String("libs.journald")); + + writer.append(QLatin1String("output")); + writer.startArray(1); + writer.append(QLatin1String("privateFeature")); + writer.endArray(); + + writer.endMap(); + \endcode + + \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: + + \code + QFile f("output", QIODevice::WriteOnly); + QCborStreamWriter writer(&f); + writer.startMap(0); + writer.endMap(); + \endcode + + 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. + + \code + QByteArray encodedNumber(qint64 value) + { + QByteArray ba; + QCborStreamWriter writer(&ba); + writer.append(value); + return ba; + } + \endcode + + 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: + + \code + writer.append(0U); + writer.append(Q_UINT64_C(4294967296)); + writer.append(std::numeric_limits<quint64>::max()); + \endcode + + \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: + + \code + writer.append(0); + writer.append(-1); + writer.append(Q_INT64_C(4294967296)); + writer.append(std::numeric_limits<qint64>::max()); + \endcode + + \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: + \code + writer.append(QCborNegativeInteger(1)); + writer.append(QCborNegativeInteger(Q_INT64_C(4294967296))); + writer.append(QCborNegativeInteger(-quint64(std::numeric_limits<qint64>::min()))); + \endcode + + 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: + + \code + void writeFile(QCborStreamWriter &writer, const QString &fileName) + { + QFile f(fileName); + if (f.open(QIODevice::ReadOnly)) + writer.append(f.readAll()); + } + \endcode + + 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: + + \code + writer.append(QLatin1String("Hello, World")); + \endcode + + \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: + + \code + void writeString(QCborStreamWriter &writer, const QString &str) + { + writer.append(str); + } + \endcode + + \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: + + \code + void writeRxPattern(QCborStreamWriter &writer, const QRegularExpression &rx) + { + writer.append(QCborTag(36)); + writer.append(rx.pattern()); + } + \endcode + + \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: + + \code + void writeCurrentTime(QCborStreamWriter &writer) + { + writer.append(QCborKnownTags::UnixTime_t); + writer.append(time(nullptr)); + } + \endcode + + \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. + + \code + writer.append(QCborSimpleType::Null); + writer.append(QCborSimpleType(32)); + \endcode + + \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. + + \code + void writeFloat(QCborStreamWriter &writer, float f) + { + qfloat16 f16 = f; + if (qIsNaN(f) || f16 == f) + writer.append(f16); + else + writer.append(f); + } + \endcode + + \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. + + \code + void writeFloat(QCborStreamWriter &writer, double d) + { + float f = d; + if (qIsNaN(d) || d == f) + writer.append(f); + else + writer.append(d); + } + \endcode + + \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. + + \code + void writeDouble(QCborStreamWriter &writer, double d) + { + float f; + if (qIsNaN(d)) { + writer.append(qfloat16(qQNaN())); + } else if (qIsInf(d)) { + writer.append(d < 0 ? -qInf() : qInf()); + } else if ((f = d) == d) { + qfloat16 f16 = f; + if (f16 == f) + writer.append(f16); + else + writer.append(f); + } else { + writer.append(d); + } + } + \endcode + + 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): + + \code + writer.append(b ? QCborSimpleType::True : QCborSimpleType::False); + \endcode + + \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. + + \code + writer.append(QCborSimpleType::Null); + \endcode + + \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): + + \code + writer.append(QCborSimpleType::Null); + \endcode + + \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): + + \code + writer.append(QCborSimpleType::Undefined); + \endcode + + \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: + + \code + void appendList(QCborStreamWriter &writer, const QLinkedList<QString> &list) + { + writer.startArray(); + for (const QString &s : list) + writer.append(s); + writer.endArray(); + } + \endcode + + \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: + + \code + void appendList(QCborStreamWriter &writer, const QStringList &list) + { + writer.startArray(list.size()); + for (const QString &s : list) + writer.append(s); + writer.endArray(); + } + \endcode + + \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: + + \code + void appendMap(QCborStreamWriter &writer, const QLinkedList<QPair<int, QString>> &list) + { + writer.startMap(); + for (const auto pair : list) { + writer.append(pair.first) + writer.append(pair.second); + } + writer.endMap(); + } + \endcode + + \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: + + \code + void appendMap(QCborStreamWriter &writer, const QMap<int, QString> &map) + { + writer.startMap(map.size()); + for (auto it = map.begin(); it != map.end(); ++it) { + writer.append(it.key()); + writer.append(it.value()); + } + writer.endMap(); + } + \endcode + + \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: + + \code + void handleStream(QCborStreamReader &reader) + { + switch (reader.type()) + case QCborStreamReader::UnsignedInteger: + case QCborStreamReader::NegativeInteger: + case QCborStreamReader::SimpleType: + case QCborStreamReader::Float16: + case QCborStreamReader::Float: + case QCborStreamReader::Double: + handleFixedWidth(reader); + reader.next(); + break; + case QCborStreamReader::ByteArray: + case QCborStreamReader::String: + handleString(reader); + break; + case QCborStreamReader::Array: + case QCborStreamReader::Map: + reader.enterContainer(); + while (reader.lastError() == QCborError::NoError) + handleStream(reader); + if (reader.lastError() == QCborError::NoError) + reader.leaveContainer(); + } + } + \endcode + + \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 + + 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 Container QCborStreamReader::StringResult::data + Contains the actual data from the string if \l status is \c Ok. + */ + +/*! + \variable QCborStreamReader::StringResultCode 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: + + \code + QVariantList populateFromCbor(QCborStreamReader &reader) + { + QVariantList list; + if (reader.isLengthKnown()) + list.reserve(reader.length()); + + reader.enterContainer(); + while (reader.lastError() == QCborError::NoError && reader.hasNext()) + list.append(readOneElement(reader)); + if (reader.lastError() == QCborError::NoError) + reader.leaveContainer(); + } + \endcode + + \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: + + \code + QVariantMap populateFromCbor(QCborStreamReader &reader) + { + QVariantMap map; + if (reader.isLengthKnown()) + map.reserve(reader.length()); + + reader.enterContainer(); + while (reader.lastError() == QCborError::NoError && reader.hasNext()) { + QString key = readElementAsString(reader); + map.insert(key, readOneElement(reader)); + } + if (reader.lastError() == QCborError::NoError) + reader.leaveContainer(); + } + \endcode + + 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); + } + + 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; + + // our error codes are the same (for now) + lastError = { QCborError::Code(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) { + 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_ = SimpleType; + 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: + + \code + QString decodeString(QCborStreamReader &reader) + { + QString result; + auto r = reader.readString(); + while (r.code == QCborStreamReader::Ok) { + result += r.data; + r = reader.readString(); + } + + if (r.code == QCborStreamReader::Error) { + // handle error condition + result.clear(); + } + return result; + } + \endcode + + 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: + + \code + QBytearray decodeBytearray(QCborStreamReader &reader) + { + QBytearray result; + auto r = reader.readBytearray(); + while (r.code == QCborStreamReader::Ok) { + result += r.data; + r = reader.readByteArray(); + } + + if (r.code == QCborStreamReader::Error) { + // handle error condition + result.clear(); + } + return result; + } + \endcode + + 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: + + \code + QCborStreamReader<qsizetype> result; + do { + qsizetype size = reader.currentStringChunkSize(); + qsizetype oldsize = buffer.size(); + buffer.resize(oldsize + size); + result = reader.readStringChunk(buffer.data() + oldsize, size); + } while (result.status() == QCborStreamReader::Ok); + \endcode + + 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" diff --git a/src/corelib/serialization/qcborstream.h b/src/corelib/serialization/qcborstream.h new file mode 100644 index 0000000000..3b13a309ab --- /dev/null +++ b/src/corelib/serialization/qcborstream.h @@ -0,0 +1,267 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QCBORSTREAM_H +#define QCBORSTREAM_H + +#include <QtCore/qbytearray.h> +#include <QtCore/qcborcommon.h> +#include <QtCore/qfloat16.h> +#include <QtCore/qscopedpointer.h> +#include <QtCore/qstring.h> +#include <QtCore/qstringview.h> + +// See qcborcommon.h for why we check +#if defined(QT_X11_DEFINES_FOUND) +# undef True +# undef False +#endif + +QT_BEGIN_NAMESPACE + +class QIODevice; + +enum class QCborNegativeInteger : quint64 {}; + +class QCborStreamWriterPrivate; +class Q_CORE_EXPORT QCborStreamWriter +{ +public: + explicit QCborStreamWriter(QIODevice *device); + explicit QCborStreamWriter(QByteArray *data); + ~QCborStreamWriter(); + Q_DISABLE_COPY(QCborStreamWriter) + + void setDevice(QIODevice *device); + QIODevice *device() const; + + void append(quint64 u); + void append(qint64 i); + void append(QCborNegativeInteger n); + void append(const QByteArray &ba) { appendByteString(ba.constData(), ba.size()); } + void append(QLatin1String str); + void append(QStringView str); + void append(QCborTag tag); + void append(QCborKnownTags tag) { append(QCborTag(tag)); } + void append(QCborSimpleType st); + void append(std::nullptr_t) { append(QCborSimpleType::Null); } + void append(qfloat16 f); + void append(float f); + void append(double d); + + void appendByteString(const char *data, qsizetype len); + void appendTextString(const char *utf8, qsizetype len); + + // convenience + void append(bool b) { append(b ? QCborSimpleType::True : QCborSimpleType::False); } + void appendNull() { append(QCborSimpleType::Null); } + void appendUndefined() { append(QCborSimpleType::Undefined); } + +#ifndef Q_QDOC + // overloads to make normal code not complain + void append(int i) { append(qint64(i)); } + void append(uint u) { append(quint64(u)); } +#endif +#ifndef QT_NO_CAST_FROM_ASCII + void append(const char *str, qsizetype size = -1) + { appendTextString(str, (str && size == -1) ? int(strlen(str)) : size); } +#endif + + void startArray(); + void startArray(quint64 count); + bool endArray(); + void startMap(); + void startMap(quint64 count); + bool endMap(); + + // no API for encoding chunked strings + +private: + QScopedPointer<QCborStreamWriterPrivate> d; +}; + +class QCborStreamReaderPrivate; +class Q_CORE_EXPORT QCborStreamReader +{ + Q_GADGET +public: + enum Type : quint8 { + UnsignedInteger = 0x00, + NegativeInteger = 0x20, + ByteString = 0x40, + ByteArray = ByteString, + TextString = 0x60, + String = TextString, + Array = 0x80, + Map = 0xa0, + Tag = 0xc0, + SimpleType = 0xe0, + HalfFloat = 0xf9, + Float16 = HalfFloat, + Float = 0xfa, + Double = 0xfb, + + Invalid = 0xff + }; + Q_ENUM(Type) + + enum StringResultCode { + EndOfString = 0, + Ok = 1, + Error = -1 + }; + template <typename Container> struct StringResult { + Container data; + StringResultCode status = Error; + }; + Q_ENUM(StringResultCode) + + QCborStreamReader(); + QCborStreamReader(const char *data, qsizetype len); + QCborStreamReader(const quint8 *data, qsizetype len); + explicit QCborStreamReader(const QByteArray &data); + explicit QCborStreamReader(QIODevice *device); + ~QCborStreamReader(); + Q_DISABLE_COPY(QCborStreamReader) + + void setDevice(QIODevice *device); + QIODevice *device() const; + void addData(const QByteArray &data); + void addData(const char *data, qsizetype len); + void addData(const quint8 *data, qsizetype len) + { addData(reinterpret_cast<const char *>(data), len); } + void reparse(); + void clear(); + void reset(); + + QCborError lastError(); + + qint64 currentOffset() const; + + bool isValid() const { return !isInvalid(); } + + int containerDepth() const; + QCborStreamReader::Type parentContainerType() const; + bool hasNext() const noexcept Q_DECL_PURE_FUNCTION; + bool next(int maxRecursion = 10000); + + Type type() const { return QCborStreamReader::Type(type_); } + bool isUnsignedInteger() const { return type() == UnsignedInteger; } + bool isNegativeInteger() const { return type() == NegativeInteger; } + bool isInteger() const { return quint8(type()) <= quint8(NegativeInteger); } + bool isByteArray() const { return type() == ByteArray; } + bool isString() const { return type() == String; } + bool isArray() const { return type() == Array; } + bool isMap() const { return type() == Map; } + bool isTag() const { return type() == Tag; } + bool isSimpleType() const { return type() == SimpleType; } + bool isFloat16() const { return type() == Float16; } + bool isFloat() const { return type() == Float; } + bool isDouble() const { return type() == Double; } + bool isInvalid() const { return type() == Invalid; } + + bool isSimpleType(QCborSimpleType st) const { return isSimpleType() && toSimpleType() == st; } + bool isFalse() const { return isSimpleType(QCborSimpleType::False); } + bool isTrue() const { return isSimpleType(QCborSimpleType::True); } + bool isBool() const { return isFalse() || isTrue(); } + bool isNull() const { return isSimpleType(QCborSimpleType::Null); } + bool isUndefined() const { return isSimpleType(QCborSimpleType::Undefined); } + + bool isLengthKnown() const noexcept Q_DECL_PURE_FUNCTION; + quint64 length() const; + + bool isContainer() const { return isMap() || isArray(); } + bool enterContainer() { Q_ASSERT(isContainer()); return _enterContainer_helper(); } + bool leaveContainer(); + + StringResult<QString> readString() { Q_ASSERT(isString()); return _readString_helper(); } + StringResult<QByteArray> readByteArray(){ Q_ASSERT(isByteArray()); return _readByteArray_helper(); } + qsizetype currentStringChunkSize() const{ Q_ASSERT(isString() || isByteArray()); return _currentStringChunkSize(); } + StringResult<qsizetype> readStringChunk(char *ptr, qsizetype maxlen); + + bool toBool() const { Q_ASSERT(isBool()); return value64 - int(QCborSimpleType::False); } + QCborTag toTag() const { Q_ASSERT(isTag()); return QCborTag(value64); } + quint64 toUnsignedInteger() const { Q_ASSERT(isUnsignedInteger()); return value64; } + QCborNegativeInteger toNegativeInteger() const { Q_ASSERT(isNegativeInteger()); return QCborNegativeInteger(value64 + 1); } + QCborSimpleType toSimpleType() const{ Q_ASSERT(isSimpleType()); return QCborSimpleType(value64); } + qfloat16 toFloat16() const { Q_ASSERT(isFloat16()); return _toFloatingPoint<qfloat16>(); } + float toFloat() const { Q_ASSERT(isFloat()); return _toFloatingPoint<float>(); } + double toDouble() const { Q_ASSERT(isDouble()); return _toFloatingPoint<double>(); } + + qint64 toInteger() const + { + Q_ASSERT(isInteger()); + qint64 v = qint64(value64); + if (isNegativeInteger()) + return -v - 1; + return v; + } + +private: + void preparse(); + bool _enterContainer_helper(); + StringResult<QString> _readString_helper(); + StringResult<QByteArray> _readByteArray_helper(); + qsizetype _currentStringChunkSize() const; + + template <typename FP> FP _toFloatingPoint() const noexcept + { + using UInt = typename QIntegerForSizeof<FP>::Unsigned; + UInt u = UInt(value64); + FP f; + memcpy(static_cast<void *>(&f), &u, sizeof(f)); + return f; + } + + friend QCborStreamReaderPrivate; + friend class QCborContainerPrivate; + quint64 value64; + QScopedPointer<QCborStreamReaderPrivate> d; + quint8 type_; + quint8 reserved[3] = {}; +}; + +QT_END_NAMESPACE + +#if defined(QT_X11_DEFINES_FOUND) +# define True 1 +# define False 0 +#endif + +#endif // QCBORSTREAM_H diff --git a/src/corelib/serialization/qcborvalue.cpp b/src/corelib/serialization/qcborvalue.cpp new file mode 100644 index 0000000000..c1d78ef738 --- /dev/null +++ b/src/corelib/serialization/qcborvalue.cpp @@ -0,0 +1,2430 @@ +/**************************************************************************** +** +** 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 "qcborvalue.h" +#include "qcborvalue_p.h" + +#include "qcborarray.h" +#include "qcbormap.h" +#include "qcborstream.h" + +#include <qendian.h> +#include <qlocale.h> +#include <private/qnumeric_p.h> +#include <private/qsimd_p.h> + +#include <new> + +QT_BEGIN_NAMESPACE + +/*! + \class QCborValue + \inmodule QtCore + \ingroup cbor + \reentrant + \since 5.12 + + \brief The QCborValue class encapsulates a value in CBOR. + + This class can be used to hold one of the many types available in CBOR. + CBOR is the Concise Binary Object Representation, a very compact form of + binary data encoding that is a superset of 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}. + + CBOR has three groups of built-in types: + + \list + \li Basic types: integers, floating point (double), boolean, null, etc. + \li String-like types: strings and byte arrays + \li Containers: arrays and maps + \endlist + + Additionally, CBOR supports a form of type extensibility by associating a + "tag" to one of the above types to convey more information. For example, a + UUID is represented by a tag and a byte array containing the 16 bytes of + the UUID content. QCborValue supports creating and decoding several of those + extended types directly with Qt classes (like QUuid). + + For the complete list, see \l QCborValue::Type. The type of a QCborValue can + be queried using type() or one of the "isXxxx" functions. + + \section1 Extended types and tagged values + + A tagged value is a normal QCborValue that is paired with a number that + is its tag. See \l QCborKnownTags for more information on what tags are in + the API as well as the full, official list. Such combinations form extended + types. + + QCborValue has support for certain extended types in the API, like URL + (with \l QUrl) and UUID (with \l QUuid). Other extended types not supported + in the API are represented by a QCborValue of \l {Type}{Tag} type. The tag + can later be retrieved by tag() and the tagged value using taggedValue(). + + In order to support future compatibility, QCborValues containing extended + Qt types compare equal to the tag type of the same contents. In other + words, the following expression is true: + + \code + QCborValue(uuid) == QCborValue(QCborKnownTags::Uuid, uuid.toRfc4122()); + \endcode + + \section1 Undefined and null values + + QCborValue can contain a value of "null", which is not of any specific type. + It resembles the C++ \c {std::nullptr_t} type, whose only possible value is + \c nullptr. QCborValue has a constructor taking such a type and creates a + null QCborValue. + + Null values are used to indicate that an optional value is not present. In + that aspect, it is similar to the C++ Standard Library type \c + {std::optional} when that is disengaged. Unlike the C++ type, CBOR nulls + are simply of type "Null" and it is not possible to determine what concrete + type it is replacing. + + QCborValue can also be of the undefined type, which represents a value of + "undefined". In fact, that is what the QCborValue default constructor + creates. + + Undefined values are different from null values. While nulls are used to + indicate an optional value that is not provided, Undefined is usually + used to indicate that an expected value could not be provided, usually due + to an error or a precondition that could not be satisfied. + + Such values are completely valid and may appear in CBOR streams, unlike + JSON content and QJsonValue's undefined bit. But like QJsonValue's + Undefined, it is returned by QCborArray::value() when out of range or + QCborMap::operator[] when the key is not found in the container. It is not + possible to tell such a case apart from the value of Undefined, so if that + is required, check the QCborArray size and use the QCborMap iterator API. + + \section1 Simple types + + CBOR supports additional simple types that, like Null and Undefined, carry + no other value. They are called interchangeably "Simple Types" and "Simple + Values". CBOR encodes booleans as two distinct types (one for \c true and + one for \c false), but QCborValue has a convenience API for them. + + There are currently no other defined CBOR simple types. QCborValue supports + them simply by their number with API like isSimpleType() and + toSimpleType(), available for compatibility with future specifications + before the Qt API can be updated. Their use before such a specification is + discouraged, as other CBOR implementations may not support them fully. + + \section1 CBOR support + + QCborValue 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 QCborValue supports. + + \table + \header \li Feature \li Support + \row \li Unsigned numbers \li Yes (\l qint64 range) + \row \li Negative numbers \li Yes (\l qint64 range) + \row \li Byte strings \li Yes + \row \li Text strings \li Yes + \row \li Chunked strings \li See below + \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 + + Integers in QCborValue are limited to the range of the \l qint64 type. That + is, from -9,223,372,036,854,775,808 (-2\sup{63}) to + 9,223,372,036,854,775,807 (2\sup{63} - 1). CBOR itself can represent integer + values outside of this range, which QCborValue does not support. When + decoding a stream using fromCbor() containing one of those values, + QCborValue will convert automatically to \l {Type}{Double}, but that may + lose up to 11 bits of precision. + + fromCbor() is able to decode chunked strings, but will always merge the + chunks together into a single QCborValue. For that reason, it always writes + non-chunked strings when using toCbor() (which is required by the Canonical + format anyway). + + QCborValue will always convert half- and single-precision floating point + values in the CBOR stream to double-precision. The toCbor() function can + take a parameter indicating to recreate them. + + \section1 QCborValueRef + + QCborValueRef is a helper class for QCborArray and QCborMap. It is the type + you get when using one of the mutating APIs in those classes. Unlike + QCborValue, new values can be assigned to that class. When that is done, the + array or map it refers to will be modified with the new value. In all other + aspects, its API is identical to QCborValue. + + \sa QCborArray, QCborMap, QCborStreamReader, QCborStreamWriter + QJsonValue, QJsonDocument + */ + +/*! + \class QCborParserError + \inmodule QtCore + \ingroup cbor + \reentrant + \since 5.12 + + \brief The QCborParserError is used by QCborValue to report a parsing error. + + This class is used by \l {QCborValue::fromCbor(const QByteArray &ba, + QCborParserError *error)} to report a parser error and the byte offset + where the error was detected. + + \sa QCborValue, QCborError + */ + +/*! + \variable qint64 QCborParserError::offset + + This field contains the offset from the beginning of the data where the + error was detected. The offset should point to the beginning of the item + that contained the error, even if the error itself was elsewhere (for + example, for UTF-8 decoding issues). + + \sa QCborValue::fromCbor() + */ + +/*! + \variable QCborError QCborParserError::error + + This field contains the error code that indicates what decoding problem was + found. + + \sa QCborValue::fromCbor() + */ + +/*! + \fn QString QCborParserError::errorString() const + + Returns a string representation of the error code. This string is not + translated. + + \sa QCborError::toString(), QCborValue::fromCbor() + */ + +/*! + \enum QCborValue::EncodingOption + + This enum is used in the options argument to toCbor(), modifying the + behavior of the encoder. + + \omitvalue SortKeysInMaps + \value NoTransformation (Default) Performs no transformations. + \value UseFloat Tells the encoder to use IEEE 754 single-precision floating point + (that is, \c float) whenever possible. + \value UseFloat16 Tells the encoder to use IEEE 754 half-precision floating point + (that is, \c qfloat16), whenever possible. Implies \c UseFloat. + \value UseIntegers Tells the encoder to use integers whenever a value of type \l + {Type}{Double} contains an integer. + + The use of \c UseFloat16 is required to encode the stream in Canonical + Format, but is not otherwise necessary. + + \sa toCbor() + */ + +/*! + \enum QCborValue::DiagnosticNotationOption + + This enum is used in the option argument to toDiagnosticNotation(), to + modify the output format. + + \value Compact Does not use any line-breaks, producing a compact representation. + \value LineWrapped Uses line-breaks, one QCborValue per line. + \value ExtendedFormat Uses some different options to represent values, not found in + RFC 7049. Those options are subject to change. + + Currently, \c ExtendedFormat will change how byte arrays are represented. + Without it, they are always hex-encoded and without spaces. With it, + QCborValue::toCbor() will either use hex with spaces, base64 or base64url + encoding, depending on the context. + + \sa toDiagnosticNotation() + */ + +/*! + \enum QCborValue::Type + + This enum represents the QCborValue type. It is returned by the type() + function. + + The CBOR built-in types are: + + \value Integer \c qint64: An integer value + \value ByteArray \l QByteArray: a byte array ("byte string") + \value String \l QString: a Unicode string ("text string") + \value Array \l QCborArray: an array of QCborValues + \value Map \l QCborMap: an associative container of QCborValues + \value SimpleType \l QCborSimpleType: one of several simple types/values + \value False \c bool: the simple type for value \c false + \value True \c bool: the simple type for value \c true + \value Null \c std::nullptr_t: the simple type for the null value + \value Undefined (no type) the simple type for the undefined value + \value Double \c double: a double-precision floating point + \value Invalid Not a valid value, this usually indicates a CBOR decoding error + + Additionally, QCborValue can represent extended types: + + \value Tag An unknown or unrecognized extended type, represented by its + tag (a \l QCborTag) and the tagged value (a QCborValue) + \value DateTime \l QDateTime: a date and time stamp + \value Url \l QUrl: a URL or URI + \value RegularExpression \l QRegularExpression: the pattern of a regular expression + \value Uuid \l QUuid: a UUID + + \sa type() + */ + +/*! + \fn QCborValue::QCborValue() + + Creates a QCborValue of the \l {Type}{Undefined} type. + + CBOR undefined values are used to indicate missing information, usually as + a result of a previous operation that did not complete as expected. They + are also used by the QCborArray and QCborMap API to indicate the searched + item was not found. + + Undefined values are represented by the \l {QCborSimpleType}{Undefined + simple type}. Because of that, QCborValues with undefined values will also + return true for isSimpleType() and + \c{isSimpleType(QCborSimpleType::Undefined)}. + + Undefined values are different from null values. + + QCborValue objects with undefined values are also different from invalid + QCborValue objects. The API will not create invalid QCborValues, but they + may exist as a result of a parsing error. + + \sa isUndefined(), isNull(), isSimpleType() + */ + +/*! + \fn QCborValue::QCborValue(Type t_) + + Creates a QCborValue of type \a t_. The value associated with such a type + (if any) will be default constructed. + + \sa type() + */ + +/*! + \fn QCborValue::QCborValue(std::nullptr_t) + + Creates a QCborValue of the \l {Type}{Null} type. + + CBOR null values are used to indicate optional values that were not + provided. They are distinct from undefined values, in that null values are + usually not the result of an earlier error or problem. + + \sa isNull(), isUndefined(), isSimpleType() + */ + +/*! + \fn QCborValue::QCborValue(bool b) + + Creates a QCborValue with boolean value \a b. The value can later be + retrieved using toBool(). + + Internally, CBOR booleans are represented by a pair of types, one for true + and one for false. For that reason, boolean QCborValues will return true + for isSimpleType() and one of \c{isSimpleType(QCborSimpleType::False)} or + \c{isSimpleType(QCborSimpleType::True)}. + + \sa toBool(), isBool(), isTrue(), isFalse(), isSimpleType() + */ + +/*! + \fn QCborValue::QCborValue(qint64 i) + + Creates a QCborValue with integer value \a i. The value can later be + retrieved using toInteger(). + + CBOR integer values are distinct from floating point values. Therefore, + QCborValue objects with integers will compare differently to QCborValue + objects containing floating-point, even if the values contained in the + objects are equivalent. + + \sa toInteger(), isInteger(), isDouble() + */ + +/*! + \fn QCborValue::QCborValue(double d) + + Creates a QCborValue with floating point value \a d. The value can later be + retrieved using toDouble(). + + CBOR floating point values are distinct from integer values. Therefore, + QCborValue objects with integers will compare differently to QCborValue + objects containing floating-point, even if the values contained in the + objects are equivalent. + + \sa toDouble(), isDouble(), isInteger() + */ + +/*! + \fn QCborValue::QCborValue(QCborSimpleType st) + + Creates a QCborValue of simple type \a st. The type can later later be retrieved + using toSimpleType() as well as isSimpleType(st). + + CBOR simple types are types that do not have any associated value, like + C++'s \c{std::nullptr_t} type, whose only possible value is \c nullptr. + + If \a st is \c{QCborSimpleType::Null}, the resulting QCborValue will be of + the \l{Type}{Null} type and similarly for \c{QCborSimpleType::Undefined}. + If \a st is \c{QCborSimpleType::False} or \c{QCborSimpleType::True}, the + created QCborValue will be a boolean containing a value of false or true, + respectively. + + This function can be used with simple types not defined in the API. For + example, to create a QCborValue with simple type 12, one could write: + + \code + QCborValue value(QCborSimpleType(12)); + \endcode + + Simple types should not be used until a specification for them has been + published, since other implementations may not support them properly. + Simple type values 24 to 31 are reserved and must not be used. + + isSimpleType(), isNull(), isUndefined(), isTrue(), isFalse() + */ + +/*! + \fn QCborValue::QCborValue(QCborKnownTags tag, const QCborValue &taggedValue) + \overload + + Creates a QCborValue for the extended type represented by the tag value \a + tag, tagging value \a taggedValue. The tag can later be retrieved using + tag() and the tagged value using taggedValue(). + + \sa isTag(), tag(), taggedValue(), QCborKnownTags + */ + +/*! + \fn QCborValue::~QCborValue() + + Disposes of the current QCborValue object and frees any associated resources. + */ + +/*! + \fn QCborValue::QCborValue(QCborValue &&other) + \overload + + Moves the contents of the \a other CBorValue object into this one and frees + the resources of this one. + */ + +/*! + \fn QCborValue &&QCborValue::operator=(QCborValue &&other) + \overload + + Moves the contents of the \a other CBorValue object into this one and frees + the resources of this one. Returns a reference to this object. + */ + +/*! + \fn void QCborValue::swap(QCborValue &other) + + Swaps the contents of this QCborValue object and \a other. + */ + +/*! + \fn QCborValue::Type QCborValue::type() const + + Returns the type of this QCborValue. The type can also later be retrieved by one + of the "isXxx" functions. + + \sa isInteger(), isByteArray(), isString(), isArray(), isMap(), + isTag(), isFalse(), isTrue(), isBool(), isNull(), isUndefined, isDouble(), + isDateTime(), isUrl(), isRegularExpression(), isUuid() + */ + +/*! + \fn bool QCborValue::isInteger() const + + Returns true if this QCborValue is of the integer type. The integer value + can be retrieved using toInteger(). + + \sa type(), toInteger() + */ + +/*! + \fn bool QCborValue::isByteArray() const + + Returns true if this QCborValue is of the byte array type. The byte array + value can be retrieved using toByteArray(). + + \sa type(), toByteArray() + */ + +/*! + \fn bool QCborValue::isString() const + + Returns true if this QCborValue is of the string type. The string value + can be retrieved using toString(). + + \sa type(), toString() + */ + +/*! + \fn bool QCborValue::isArray() const + + Returns true if this QCborValue is of the array type. The array value can + be retrieved using toArray(). + + \sa type(), toArray() + */ + +/*! + \fn bool QCborValue::isMap() const + + Returns true if this QCborValue is of the map type. The map value can be + retrieved using toMap(). + + \sa type(), toMap() + */ + +/*! + \fn bool QCborValue::isTag() const + + Returns true if this QCborValue is of the tag type. The tag value can be + retrieved using tag() and the tagged value using taggedValue(). + + This function also returns true for extended types that the API + recognizes. For code that handles extended types directly before the Qt API + is updated to support them, it is possible to recreate the tag + tagged + value pair by using taggedValue(). + + \sa type(), tag(), taggedValue(), taggedValue() + */ + +/*! + \fn bool QCborValue::isFalse() const + + Returns true if this QCborValue is a boolean with false value. This + function exists because, internally, CBOR booleans are stored as two + separate types, one for true and one for false. + + \sa type(), isBool(), isTrue(), toBool() + */ + +/*! + \fn bool QCborValue::isTrue() const + + Returns true if this QCborValue is a boolean with true value. This + function exists because, internally, CBOR booleans are stored as two + separate types, one for false and one for true. + + \sa type(), isBool(), isFalse(), toBool() + */ + +/*! + \fn bool QCborValue::isBool() const + + Returns true if this QCborValue is a boolean. The value can be retrieved + using toBool(). + + \sa type(), toBool(), isTrue(), isFalse() + */ + +/*! + \fn bool QCborValue::isUndefined() const + + Returns true if this QCborValue is of the undefined type. + + CBOR undefined values are used to indicate missing information, usually as + a result of a previous operation that did not complete as expected. They + are also used by the QCborArray and QCborMap API to indicate the searched + item was not found. + + Undefined values are distinct from null values. + + QCborValue objects with undefined values are also different from invalid + QCborValue objects. The API will not create invalid QCborValues, but they + may exist as a result of a parsing error. + + \sa type(), isNull(), isInvalid() + */ + +/*! + \fn bool QCborValue::isNull() const + + Returns true if this QCborValue is of the null type. + + CBOR null values are used to indicate optional values that were not + provided. They are distinct from undefined values, in that null values are + usually not the result of an earlier error or problem. + + Null values are distinct from undefined values and from invalid QCborValue + objects. The API will not create invalid QCborValues, but they may exist as + a result of a parsing error. + + \sa type(), isUndefined(), isInvalid() + */ + +/*! + \fn bool QCborValue::isDouble() const + + Returns true if this QCborValue is of the floating-point type. The value + can be retrieved using toDouble(). + + \sa type(), toDouble() + */ + +/*! + \fn bool QCborValue::isDateTime() const + + Returns true if this QCborValue is of the date/time type. The value can be + retrieved using toDateTime(). Date/times are extended types that use the + tag \l{QCborKnownTags}{DateTime}. + + Additionally, when decoding from a CBOR stream, QCborValue will interpret + tags of value \l{QCborKnownTags}{UnixTime_t} and convert them to the + equivalent date/time. + + \sa type(), toDateTime() + */ + +/*! + \fn bool QCborValue::isUrl() const + + Returns true if this QCborValue is of the URL type. The URL value + can be retrieved using toUrl(). + + \sa type(), toUrl() + */ + +/*! + \fn bool QCborValue::isRegularExpression() const + + Returns true if this QCborValue contains a regular expression's pattern. + The pattern can be retrieved using toRegularExpression(). + + \sa type(), toRegularExpression() + */ + +/*! + \fn bool QCborValue::isUuid() const + + Returns true if this QCborValue contains a UUID. The value can be retrieved + using toUuid(). + + \sa type(), toUuid() + */ + +/*! + \fn bool QCborValue::isInvalid() const + + Returns true if this QCborValue is not of any valid type. Invalid + QCborValues are distinct from those with undefined values and they usually + represent a decoding error. + + \sa isUndefined(), isNull() + */ + +/*! + \fn bool QCborValue::isContainer() const + + This convenience function returns true if the QCborValue is either an array + or a map. + + \sa isArray(), isMap() + */ + +/*! + \fn bool QCborValue::isSimpleType() const + + Returns true if this QCborValue is of one of the CBOR simple types. The + type itself can later be retrieved using type(), even for types that don't have an + enumeration in the API. They can also be checked with the + \l{isSimpleType(QCborSimpleType)} overload. + + \sa QCborSimpleType, isSimpleType(QCborSimpleType), toSimpleType() + */ + +/*! + \fn bool QCborValue::isSimpleType(QCborSimpleType st) const + \overload + + Returns true if this QCborValue is of a simple type and toSimpleType() + would return \a st, false otherwise. This function can be used to check for + any CBOR simple type, even those for which there is no enumeration in the + API. For example, for the simple type of value 12, you could write: + + \code + value.isSimpleType(QCborSimpleType(12)); + \endcode + + \sa QCborValue::QCborValue(QCborSimpleType), isSimpleType(), isFalse(), + isTrue(), isNull, isUndefined(), toSimpleType() + */ + +/*! + \fn QCborSimpleType QCborValue::toSimpleType(QCborSimpleType defaultValue) const + + Returns the simple type this QCborValue is of, if it is a simple type. If + it is not a simple type, it returns \a defaultValue. + + The following types are simple types and this function will return the + listed values: + + \table + \row \li QCborValue::False \li QCborSimpleType::False + \row \li QCborValue::True \li QCborSimpleType::True + \row \li QCborValue::Null \li QCborSimpleType::Null + \row \li QCborValue::Undefined \li QCborSimpleType::Undefined + \endtable + + \sa type(), isSimpleType(), isBool(), isTrue(), isFalse(), isTrue(), + isNull(), isUndefined() + */ + +/*! + \fn qint64 QCborValue::toInteger(qint64 defaultValue) const + + Returns the integer value stored in this QCborValue, if it is of the + integer type. If it is of the Double type, this function returns the + floating point value converted to integer. In any other case, it returns \a + defaultValue. + + \sa isInteger(), isDouble(), toDouble() + */ + +/*! + \fn bool QCborValue::toBool(bool defaultValue) const + + Returns the boolean value stored in this QCborValue, if it is of a boolean + type. Otherwise, it returns \a defaultValue. + + \sa isBool(), isTrue(), isFalse() + */ + +/*! + \fn double QCborValue::toDouble(double defaultValue) const + + Returns the floating point value stored in this QCborValue, if it is of the + Double type. If it is of the Integer type, this function returns the + integer value converted to double. In any other case, it returns \a + defaultValue. + + \sa isDouble(), isInteger(), toInteger() + */ + +using namespace QtCbor; + +// in qcborstream.cpp +extern void qt_cbor_stream_set_error(QCborStreamReaderPrivate *d, QCborError error); + +static void writeDoubleToCbor(QCborStreamWriter &writer, double d, QCborValue::EncodingOptions opt) +{ + if (qt_is_nan(d)) { + if (opt & QCborValue::UseFloat16) { + if ((opt & QCborValue::UseFloat16) == QCborValue::UseFloat16) + return writer.append(qfloat16(qt_qnan())); + return writer.append(float(qt_qnan())); + } + return writer.append(qt_qnan()); + } + + if (qt_is_inf(d)) { + d = d > 0 ? qt_inf() : -qt_inf(); + } else if (opt & QCborValue::UseIntegers) { + quint64 i; + if (convertDoubleTo(d, &i)) { + if (d < 0) + return writer.append(QCborNegativeInteger(i)); + return writer.append(i); + } + } + + if (opt & QCborValue::UseFloat16) { + float f = float(d); + if (f == d) { + // no data loss, we could use float + if ((opt & QCborValue::UseFloat16) == QCborValue::UseFloat16) { + qfloat16 f16 = f; + if (f16 == f) + return writer.append(f16); + } + + return writer.append(f); + } + } + + writer.append(d); +} + +static inline int typeOrder(Element e1, Element e2) +{ + auto comparable = [](Element e) { + if (e.type >= 0x10000) // see QCborValue::isTag_helper() + return QCborValue::Tag; + return e.type; + }; + return comparable(e1) - comparable(e2); +} + +QCborContainerPrivate::~QCborContainerPrivate() +{ + // delete our elements + for (Element &e : elements) { + if (e.flags & Element::IsContainer) + e.container->deref(); + } +} + +void QCborContainerPrivate::compact(qsizetype reserved) +{ + if (usedData > data.size() / 2) + return; + + // 50% savings if we recreate the byte data + // ### TBD + Q_UNUSED(reserved); +} + +QCborContainerPrivate *QCborContainerPrivate::clone(QCborContainerPrivate *d, qsizetype reserved) +{ + if (!d) { + d = new QCborContainerPrivate; + } else { + d = new QCborContainerPrivate(*d); + if (reserved >= 0) { + d->elements.reserve(reserved); + d->compact(reserved); + } + for (auto &e : qAsConst(d->elements)) { + if (e.flags & Element::IsContainer) + e.container->ref.ref(); + } + } + return d; +} + +QCborContainerPrivate *QCborContainerPrivate::detach(QCborContainerPrivate *d, qsizetype reserved) +{ + if (!d || d->ref.load() != 1) + return clone(d, reserved); + return d; +} + +// Copies or moves \a value into element at position \a e. If \a disp is +// CopyContainer, then this function increases the reference count of the +// container, but otherwise leaves it unmodified. If \a disp is MoveContainer, +// then it transfers ownership (move semantics) and the caller must set +// value.container back to nullptr. +void QCborContainerPrivate::replaceAt_complex(Element &e, const QCborValue &value, ContainerDisposition disp) +{ + if (value.n < 0) { + // This QCborValue is an array, map, or tagged value (container points + // to itself). + + // detect self-assignment + if (Q_UNLIKELY(this == value.container)) { + Q_ASSERT(ref.load() >= 2); + if (disp == MoveContainer) + ref.deref(); // not deref() because it can't drop to 0 + QCborContainerPrivate *d = QCborContainerPrivate::clone(this); + d->elements.detach(); + d->ref.store(1); + e.container = d; + } else { + e.container = value.container; + if (disp == CopyContainer) + e.container->ref.ref(); + } + + e.type = value.type(); + e.flags = Element::IsContainer; + } else { + // String data, copy contents + e = value.container->elements.at(value.n); + + // Copy string data, if any + if (const ByteData *b = value.container->byteData(value.n)) + e.value = addByteData(b->byte(), b->len); + + if (disp == MoveContainer) + value.container->deref(); + } +} + +// in qstring.cpp +void qt_to_latin1_unchecked(uchar *dst, const ushort *uc, qsizetype len); + +Q_NEVER_INLINE void QCborContainerPrivate::appendAsciiString(const QString &s) +{ + qsizetype len = s.size(); + QtCbor::Element e; + e.value = addByteData(nullptr, len); + e.type = QCborValue::String; + e.flags = Element::HasByteData | Element::StringIsAscii; + elements.append(e); + + char *ptr = data.data() + e.value + sizeof(ByteData); + uchar *l = reinterpret_cast<uchar *>(ptr); + const ushort *uc = (const ushort *)s.unicode(); + qt_to_latin1_unchecked(l, uc, len); +} + +QCborValue QCborContainerPrivate::extractAt_complex(Element e) +{ + // create a new container for the returned value, containing the byte data + // from this element, if it's worth it + Q_ASSERT(e.flags & Element::HasByteData); + auto b = byteData(e); + auto container = new QCborContainerPrivate; + + if (b->len + qsizetype(sizeof(*b)) < data.size() / 4) { + // make a shallow copy of the byte data + container->appendByteData(b->byte(), b->len, e.type, e.flags); + usedData -= b->len + qsizetype(sizeof(*b)); + compact(elements.size()); + } else { + // just share with the original byte data + container->data = data; + container->elements.reserve(1); + container->elements.append(e); + } + + return makeValue(e.type, 0, container); +} + +QT_WARNING_DISABLE_MSVC(4146) // unary minus operator applied to unsigned type, result still unsigned +static int compareContainer(const QCborContainerPrivate *c1, const QCborContainerPrivate *c2); +static int compareElementNoData(const Element &e1, const Element &e2) +{ + Q_ASSERT(e1.type == e2.type); + + if (e1.type == QCborValue::Integer) { + // CBOR sorting order is 0, 1, 2, ..., INT64_MAX, -1, -2, -3, ... INT64_MIN + // So we transform: + // 0 -> 0 + // 1 -> 1 + // INT64_MAX -> INT64_MAX + // -1 -> INT64_MAX + 1 = INT64_MAX - (-1) + // -2 -> INT64_MAX + 2 = INT64_MAX - (-2) + // INT64_MIN -> UINT64_MAX = INT64_MAX - INT64_MIN + // Note how the unsigned arithmethic is well defined in C++ (it's + // always performed modulo 2^64). + auto makeSortable = [](qint64 v) { + quint64 u = quint64(v); + if (v < 0) + return quint64(std::numeric_limits<qint64>::max()) + (-u); + return u; + }; + quint64 u1 = makeSortable(e1.value); + quint64 u2 = makeSortable(e2.value); + if (u1 < u2) + return -1; + if (u1 > u2) + return 1; + } + + if (e1.type == QCborValue::Tag || e1.type == QCborValue::Double) { + // Perform unsigned comparisons for the tag value and floating point + quint64 u1 = quint64(e1.value); + quint64 u2 = quint64(e2.value); + if (u1 != u2) + return u1 < u2 ? -1 : 1; + } + + // Any other type is equal at this point: + // - simple types carry no value + // - empty strings, arrays and maps + return 0; +} + +static int compareElementRecursive(const QCborContainerPrivate *c1, const Element &e1, + const QCborContainerPrivate *c2, const Element &e2) +{ + int cmp = typeOrder(e1, e2); + if (cmp != 0) + return cmp; + + if ((e1.flags & Element::IsContainer) || (e2.flags & Element::IsContainer)) + return compareContainer(e1.flags & Element::IsContainer ? e1.container : nullptr, + e2.flags & Element::IsContainer ? e2.container : nullptr); + + // string data? + const ByteData *b1 = c1 ? c1->byteData(e1) : nullptr; + const ByteData *b2 = c2 ? c2->byteData(e2) : nullptr; + if (b1 || b2) { + auto len1 = b1 ? b1->len : 0; + auto len2 = b2 ? b2->len : 0; + + if (e1.flags & Element::StringIsUtf16) + len1 /= 2; + if (e2.flags & Element::StringIsUtf16) + len2 /= 2; + if (len1 == 0 || len2 == 0) + return len1 < len2 ? -1 : len1 == len2 ? 0 : 1; + + // we definitely have data from this point forward + Q_ASSERT(b1); + Q_ASSERT(b2); + + // Officially with CBOR, we sort first the string with the shortest + // UTF-8 length. The length of an ASCII string is the same as its UTF-8 + // and UTF-16 ones, but the UTF-8 length of a string is bigger than the + // UTF-16 equivalent. Combinations are: + // 1) UTF-16 and UTF-16 + // 2) UTF-16 and UTF-8 <=== this is the problem case + // 3) UTF-16 and US-ASCII + // 4) UTF-8 and UTF-8 + // 5) UTF-8 and US-ASCII + // 6) US-ASCII and US-ASCII + if ((e1.flags & Element::StringIsUtf16) && (e2.flags & Element::StringIsUtf16)) { + // Case 1: both UTF-16, so lengths are comparable. + // (we can't use memcmp in little-endian machines) + if (len1 == len2) + return QtPrivate::compareStrings(b1->asStringView(), b2->asStringView()); + return len1 < len2 ? -1 : 1; + } + + if (!(e1.flags & Element::StringIsUtf16) && !(e2.flags & Element::StringIsUtf16)) { + // Cases 4, 5 and 6: neither is UTF-16, so lengths are comparable too + // (this case includes byte arrays too) + if (len1 == len2) + return memcmp(b1->byte(), b2->byte(), size_t(len1)); + return len1 < len2 ? -1 : 1; + } + + if (!(e1.flags & Element::StringIsAscii) || !(e2.flags & Element::StringIsAscii)) { + // Case 2: one of them is UTF-8 and the other is UTF-16, so lengths + // are NOT comparable. We need to convert to UTF-16 first... + auto string = [](const Element &e, const ByteData *b) { + return e.flags & Element::StringIsUtf16 ? b->asQStringRaw() : b->toUtf8String(); + }; + + QString s1 = string(e1, b1); + QString s2 = string(e2, b2); + if (s1.size() == s2.size()) + return s1.compare(s2); + return s1.size() < s2.size() ? -1 : 1; + } + + // Case 3 (UTF-16 and US-ASCII) remains, so lengths are comparable again + if (len1 != len2) + return len1 < len2 ? -1 : 1; + if (e1.flags & Element::StringIsUtf16) + return QtPrivate::compareStrings(b1->asStringView(), b2->asLatin1()); + return QtPrivate::compareStrings(b1->asLatin1(), b2->asStringView()); + } + + return compareElementNoData(e1, e2); +} + +static int compareContainer(const QCborContainerPrivate *c1, const QCborContainerPrivate *c2) +{ + auto len1 = c1 ? c1->elements.size() : 0; + auto len2 = c2 ? c2->elements.size() : 0; + if (len1 != len2) { + // sort the shorter container first + return len1 < len2 ? -1 : 1; + } + + for (qsizetype i = 0; i < len1; ++i) { + const Element &e1 = c1->elements.at(i); + const Element &e2 = c2->elements.at(i); + int cmp = QCborContainerPrivate::compareElement_helper(c1, e1, c2, e2); + if (cmp) + return cmp; + } + + return 0; +} + +inline int QCborContainerPrivate::compareElement_helper(const QCborContainerPrivate *c1, Element e1, + const QCborContainerPrivate *c2, Element e2) +{ + return compareElementRecursive(c1, e1, c2, e2); +} + +/*! + \fn bool QCborValue::operator==(const QCborValue &other) const + + Compares this value and \a other, and returns true if they hold the same + contents, false otherwise. If each QCborValue contains an array or map, the + comparison is recursive to elements contained in them. + + For more information on CBOR equality in Qt, see, compare(). + + \sa compare(), QCborValue::operator==(), QCborMap::operator==(), + operator!=(), operator<() + */ + +/*! + \fn bool QCborValue::operator!=(const QCborValue &other) const + + Compares this value and \a other, and returns true if contents differ, + false otherwise. If each QCborValue contains an array or map, the comparison + is recursive to elements contained in them. + + For more information on CBOR equality in Qt, see, QCborValue::compare(). + + \sa compare(), QCborValue::operator==(), QCborMap::operator==(), + operator==(), operator<() + */ + +/*! + \fn bool QCborValue::operator<(const QCborValue &other) const + + Compares this value and \a other, and returns true if this value should be + sorted before \a other, false otherwise. If each QCborValue contains an + array or map, the comparison is recursive to elements contained in them. + + For more information on CBOR sorting order, see QCborValue::compare(). + + \sa compare(), QCborValue::operator==(), QCborMap::operator==(), + operator==(), operator!=() + */ + +/*! + Compares this value and \a other, and returns an integer that indicates + whether this value should be sorted prior to (if the result is negative) or + after \a other (if the result is positive). If this function returns 0, the + two values are equal and hold the same contents. + + If each QCborValue contains an array or map, the comparison is recursive to + elements contained in them. + + \section3 Extended types + + QCborValue compares equal a QCborValue containing an extended type, like + \l{Type}{Url} and \l{Type}{Url} and its equivalent tagged representation. + So, for example, the following expression is true: + + \code + QCborValue(QUrl("https://example.com")) == QCborValue(QCborKnownTags::Url, "https://example.com"); + \endcode + + Do note that Qt types like \l QUrl and \l QDateTime will normalize and + otherwise modify their arguments. The expression above is true only because + the string on the right side is the normalized value that the QCborValue on + the left would take. If, for example, the "https" part were uppercase in + both sides, the comparison would fail. For information on normalizations + performed by QCborValue, please consult the documentation of the + constructor taking the Qt type in question. + + \section3 Sorting order + + Sorting order in CBOR is defined in RFC 7049 + {https://tools.ietf.org/html/rfc7049#section-3.9}{section 3.9}, which + discusses the sorting of keys in a map when following the Canonical + encoding. According to the specification, "sorting is performed on the + bytes of the representation of the key data items" and lists as + consequences that: + + \list + \li "If two keys have different lengths, the shorter one sorts earlier;" + \li "If two keys have the same length, the one with the lower value in + (byte-wise) lexical order sorts earlier." + \endlist + + This results in surprising sorting of QCborValues, where the result of this + function is different from that which would later be retrieved by comparing the + contained elements. For example, the QCborValue containing string "zzz" + sorts before the QCborValue with string "foobar", even though when + comparing as \l{QString::compare()}{QStrings} or + \l{QByteArray}{QByteArrays} the "zzz" sorts after "foobar" + (dictionary order). + + The specification does not clearly indicate what sorting order should be + done for values of different types (it says sorting should not pay + "attention to the 3/5 bit splitting for major types"). QCborValue makes the + assumption that types should be sorted too. The numeric values of the + QCborValue::Type enumeration are in that order, with the exception of the + extended types, which compare as their tagged equivalents. + + \note Sorting order is preliminary and is subject to change. Applications + should not depend on the order returned by this function for the time + being. + + \sa QCborArray::compare(), QCborMap::compare(), operator==() + */ +int QCborValue::compare(const QCborValue &other) const +{ + Element e1 = QCborContainerPrivate::elementFromValue(*this); + Element e2 = QCborContainerPrivate::elementFromValue(other); + return compareElementRecursive(container, e1, other.container, e2); +} + +int QCborArray::compare(const QCborArray &other) const noexcept +{ + return compareContainer(d.data(), other.d.data()); +} + +int QCborMap::compare(const QCborMap &other) const noexcept +{ + return compareContainer(d.data(), other.d.data()); +} + +static void encodeToCbor(QCborStreamWriter &writer, const QCborContainerPrivate *d, qsizetype idx, + QCborValue::EncodingOptions opt) +{ + if (idx == -QCborValue::Array || idx == -QCborValue::Map) { + bool isArray = (idx == -QCborValue::Array); + qsizetype len = d ? d->elements.size() : 0; + if (isArray) + writer.startArray(quint64(len)); + else + writer.startMap(quint64(len) / 2); + + for (idx = 0; idx < len; ++idx) + encodeToCbor(writer, d, idx, opt); + + if (isArray) + writer.endArray(); + else + writer.endMap(); + } else if (idx < 0) { + if (d->elements.size() != 2) { + // invalid state! + qWarning("QCborValue: invalid tag state; are you encoding something that was improperly decoded?"); + return; + } + + // write the tag and the tagged element + writer.append(QCborTag(d->elements.at(0).value)); + encodeToCbor(writer, d, 1, opt); + } else { + // just one element + auto e = d->elements.at(idx); + const ByteData *b = d->byteData(idx); + switch (e.type) { + case QCborValue::Integer: + return writer.append(qint64(e.value)); + + case QCborValue::ByteArray: + if (b) + return writer.appendByteString(b->byte(), b->len); + return writer.appendByteString("", 0); + + case QCborValue::String: + if (b) { + if (e.flags & Element::StringIsUtf16) + return writer.append(b->asStringView()); + return writer.appendTextString(b->byte(), b->len); + } + return writer.append(QLatin1String()); + + case QCborValue::Array: + case QCborValue::Map: + case QCborValue::Tag: + // recurse + return encodeToCbor(writer, + e.flags & Element::IsContainer ? e.container : nullptr, + -qsizetype(e.type), opt); + + case QCborValue::SimpleType: + case QCborValue::False: + case QCborValue::True: + case QCborValue::Null: + case QCborValue::Undefined: + break; + + case QCborValue::Double: + return writeDoubleToCbor(writer, e.fpvalue(), opt); + + case QCborValue::Invalid: + return; + + case QCborValue::DateTime: + case QCborValue::Url: + case QCborValue::RegularExpression: + case QCborValue::Uuid: + // recurse as tag + return encodeToCbor(writer, e.container, -QCborValue::Tag, opt); + } + + // maybe it's a simple type + int simpleType = e.type - QCborValue::SimpleType; + if (unsigned(simpleType) < 0x100) + return writer.append(QCborSimpleType(simpleType)); + + // if we got here, we've got an unknown type + qWarning("QCborValue: found unknown type 0x%x", e.type); + } +} + +static inline double integerOutOfRange(const QCborStreamReader &reader) +{ + Q_ASSERT(reader.isInteger()); + if (reader.isUnsignedInteger()) { + quint64 v = reader.toUnsignedInteger(); + if (qint64(v) < 0) + return double(v); + } else { + quint64 v = quint64(reader.toNegativeInteger()); + if (qint64(v - 1) < 0) + return -double(v); + } + + // result is in range + return 0; +} + +static Element decodeBasicValueFromCbor(QCborStreamReader &reader) +{ + Element e = {}; + + switch (reader.type()) { + case QCborStreamReader::UnsignedInteger: + case QCborStreamReader::NegativeInteger: + if (double d = integerOutOfRange(reader)) { + e.type = QCborValue::Double; + qToUnaligned(d, &e.value); + } else { + e.type = QCborValue::Integer; + e.value = reader.toInteger(); + } + break; + case QCborStreamReader::SimpleType: + e.type = QCborValue::Type(quint8(reader.toSimpleType()) + 0x100); + break; + case QCborStreamReader::Float16: + e.type = QCborValue::Double; + qToUnaligned(double(reader.toFloat16()), &e.value); + break; + case QCborStreamReader::Float: + e.type = QCborValue::Double; + qToUnaligned(double(reader.toFloat()), &e.value); + break; + case QCborStreamReader::Double: + e.type = QCborValue::Double; + qToUnaligned(reader.toDouble(), &e.value); + break; + + default: + Q_UNREACHABLE(); + } + + reader.next(); + return e; +} + +static inline QCborContainerPrivate *createContainerFromCbor(QCborStreamReader &reader) +{ + auto d = new QCborContainerPrivate; + d->ref.store(1); + d->decodeFromCbor(reader); + return d; +} + +static QCborValue taggedValueFromCbor(QCborStreamReader &reader) +{ + auto d = new QCborContainerPrivate; + d->append(reader.toTag()); + reader.next(); + + if (reader.lastError() == QCborError::NoError) { + // decode tagged value + d->decodeValueFromCbor(reader); + } + + QCborValue::Type type = QCborValue::Tag; + if (reader.lastError() == QCborError::NoError) { + // post-process to create our extended types + qint64 tag = d->elements.at(0).value; + auto &e = d->elements[1]; + const ByteData *b = d->byteData(e); + + auto replaceByteData = [&](const char *buf, qsizetype len) { + d->data.clear(); + d->usedData = 0; + e.flags = Element::HasByteData | Element::StringIsAscii; + e.value = d->addByteData(buf, len); + }; + + switch (tag) { + case qint64(QCborKnownTags::DateTimeString): + case qint64(QCborKnownTags::UnixTime_t): { + QDateTime dt; + if (tag == qint64(QCborKnownTags::DateTimeString) && b && + e.type == QCborValue::String && (e.flags & Element::StringIsUtf16) == 0) { + // The data is supposed to be US-ASCII. If it isn't, + // QDateTime::fromString will fail anyway. + dt = QDateTime::fromString(b->asLatin1(), Qt::ISODateWithMs); + } else if (tag == qint64(QCborKnownTags::UnixTime_t) && e.type == QCborValue::Integer) { + dt = QDateTime::fromSecsSinceEpoch(e.value, Qt::UTC); + } else if (tag == qint64(QCborKnownTags::UnixTime_t) && e.type == QCborValue::Double) { + dt = QDateTime::fromMSecsSinceEpoch(qint64(e.fpvalue() * 1000), Qt::UTC); + } + if (dt.isValid()) { + QByteArray text = dt.toString(Qt::ISODateWithMs).toLatin1(); + replaceByteData(text, text.size()); + e.type = QCborValue::String; + d->elements[0].value = qint64(QCborKnownTags::DateTimeString); + type = QCborValue::DateTime; + } + break; + } + + case qint64(QCborKnownTags::Url): + if (e.type == QCborValue::String) { + if (b) { + // normalize to a short (decoded) form, so as to save space + QUrl url(e.flags & Element::StringIsUtf16 ? + b->asQStringRaw() : + b->toUtf8String()); + QByteArray encoded = url.toString(QUrl::DecodeReserved).toUtf8(); + replaceByteData(encoded, encoded.size()); + } + type = QCborValue::Url; + } + break; + + case quint64(QCborKnownTags::RegularExpression): + if (e.type == QCborValue::String) { + // no normalization is necessary + type = QCborValue::RegularExpression; + } + break; + + case qint64(QCborKnownTags::Uuid): + if (e.type == QCborValue::ByteArray) { + // force the size to 16 + char buf[sizeof(QUuid)] = {}; + if (b) + memcpy(buf, b->byte(), qMin(sizeof(buf), size_t(b->len))); + replaceByteData(buf, sizeof(buf)); + + type = QCborValue::Uuid; + } + break; + } + } else { + // decoding error + type = QCborValue::Invalid; + } + + // note: may return invalid state! + return QCborContainerPrivate::makeValue(type, -1, d); +} + +void QCborContainerPrivate::decodeStringFromCbor(QCborStreamReader &reader) +{ + auto addByteData_local = [this](QByteArray::size_type len) -> qint64 { + // this duplicates a lot of addByteData, but with overflow checking + QByteArray::size_type newSize; + QByteArray::size_type increment = sizeof(QtCbor::ByteData); + QByteArray::size_type alignment = alignof(QtCbor::ByteData); + QByteArray::size_type offset = data.size(); + + // calculate the increment we want + if (add_overflow(increment, len, &increment)) + return -1; + + // align offset + if (add_overflow(offset, alignment - 1, &offset)) + return -1; + offset &= ~(alignment - 1); + + // and calculate the final size + if (add_overflow(offset, increment, &newSize)) + return -1; + + // since usedData <= data.size(), this can't overflow + usedData += increment; + data.resize(newSize); + return offset; + }; + auto dataPtr = [this]() { + // Null happens when we're reading zero bytes. + Q_ASSERT(data.isNull() || data.isDetached()); + return const_cast<char *>(data.constData()); + }; + + Element e = {}; + e.type = (reader.isByteArray() ? QCborValue::ByteArray : QCborValue::String); + if (reader.lastError() != QCborError::NoError) + return; + + qsizetype rawlen = reader.currentStringChunkSize(); + QByteArray::size_type len = rawlen; + if (rawlen < 0) + return; // error + if (len != rawlen) { + // truncation + qt_cbor_stream_set_error(reader.d.data(), { QCborError::DataTooLarge }); + return; + } + + // allocate space, but only if there will be data + if (len != 0 || !reader.isLengthKnown()) { + e.flags = Element::HasByteData; + e.value = addByteData_local(len); + if (e.value < 0) { + // overflow + qt_cbor_stream_set_error(reader.d.data(), { QCborError::DataTooLarge }); + return; + } + } + + // read chunks + bool isAscii = (e.type == QCborValue::String); + auto r = reader.readStringChunk(dataPtr() + e.value + sizeof(ByteData), len); + while (r.status == QCborStreamReader::Ok) { + if (e.type == QCborValue::String && len) { + // verify UTF-8 string validity + auto utf8result = QUtf8::isValidUtf8(dataPtr() + data.size() - len, len); + if (!utf8result.isValidUtf8) { + r.status = QCborStreamReader::Error; + qt_cbor_stream_set_error(reader.d.data(), { QCborError::InvalidUtf8String }); + break; + } + isAscii = isAscii && utf8result.isValidAscii; + } + + // allocate space for the next chunk + rawlen = reader.currentStringChunkSize(); + len = rawlen; + if (len == rawlen) { + auto oldSize = data.size(); + auto newSize = oldSize; + if (!add_overflow(newSize, len, &newSize)) { + if (newSize != oldSize) + data.resize(newSize); + + // read the chunk + r = reader.readStringChunk(dataPtr() + oldSize, len); + continue; + } + } + + // error + r.status = QCborStreamReader::Error; + qt_cbor_stream_set_error(reader.d.data(), { QCborError::DataTooLarge }); + } + + if (r.status == QCborStreamReader::Error) { + // There can only be errors if there was data to be read. + Q_ASSERT(e.flags & Element::HasByteData); + data.truncate(e.value); + return; + } + + // update size + if (e.flags & Element::HasByteData) { + auto b = new (dataPtr() + e.value) ByteData; + b->len = data.size() - e.value - int(sizeof(*b)); + usedData += b->len; + + if (isAscii) { + // set the flag if it is US-ASCII only (as it often is) + Q_ASSERT(e.type == QCborValue::String); + e.flags |= Element::StringIsAscii; + } + } + + elements.append(e); +} + +void QCborContainerPrivate::decodeValueFromCbor(QCborStreamReader &reader) +{ + switch (reader.type()) { + case QCborStreamReader::UnsignedInteger: + case QCborStreamReader::NegativeInteger: + case QCborStreamReader::SimpleType: + case QCborStreamReader::Float16: + case QCborStreamReader::Float: + case QCborStreamReader::Double: + elements.append(decodeBasicValueFromCbor(reader)); + break; + + case QCborStreamReader::ByteArray: + case QCborStreamReader::String: + decodeStringFromCbor(reader); + break; + + case QCborStreamReader::Array: + case QCborStreamReader::Map: + case QCborStreamReader::Tag: + return append(QCborValue::fromCbor(reader)); + + case QCborStreamReader::Invalid: + return; // probably a decode error + } +} + +void QCborContainerPrivate::decodeFromCbor(QCborStreamReader &reader) +{ + int mapShift = reader.isMap() ? 1 : 0; + if (reader.isLengthKnown()) { + quint64 len = reader.length(); + + // Clamp allocation to 1M elements (avoids crashing due to corrupt + // stream or loss of precision when converting from quint64 to + // QVector::size_type). + len = qMin(len, quint64(1024 * 1024 - 1)); + elements.reserve(qsizetype(len) << mapShift); + } + + reader.enterContainer(); + if (reader.lastError() != QCborError::NoError) + return; + + while (reader.hasNext() && reader.lastError() == QCborError::NoError) + decodeValueFromCbor(reader); + + if (reader.lastError() == QCborError::NoError) + reader.leaveContainer(); +} + +/*! + Creates a QCborValue with byte array value \a ba. The value can later be + retrieved using toByteArray(). + + \sa toByteArray(), isByteArray(), isString() + */ +QCborValue::QCborValue(const QByteArray &ba) + : n(0), container(new QCborContainerPrivate), t(ByteArray) +{ + container->appendByteData(ba.constData(), ba.size(), t); + container->ref.store(1); +} + +/*! + Creates a QCborValue with string value \a s. The value can later be + retrieved using toString(). + + \sa toString(), isString(), isByteArray() + */ +QCborValue::QCborValue(const QString &s) + : n(0), container(new QCborContainerPrivate), t(String) +{ + container->append(s); + container->ref.store(1); +} + +/*! + \overload + + Creates a QCborValue with string value \a s. The value can later be + retrieved using toString(). + + \sa toString(), isString(), isByteArray() + */ +QCborValue::QCborValue(QLatin1String s) + : n(0), container(new QCborContainerPrivate), t(String) +{ + container->append(s); + container->ref.store(1); +} + +/*! + \fn QCborValue::QCborValue(const QCborArray &a) + \fn QCborValue::QCborValue(QCborArray &&a) + + Creates a QCborValue with the array \a a. The array can later be retrieved + using toArray(). + + \sa toArray(), isArray(), isMap() + */ +QCborValue::QCborValue(const QCborArray &a) + : n(-1), container(a.d.data()), t(Array) +{ + if (container) + container->ref.ref(); +} + +/*! + \fn QCborValue::QCborValue(const QCborMap &m) + \fn QCborValue::QCborValue(QCborMap &&m) + + Creates a QCborValue with the map \a m. The map can later be retrieved + using toMap(). + + \sa toMap(), isMap(), isArray() + */ +QCborValue::QCborValue(const QCborMap &m) + : n(-1), container(m.d.data()), t(Map) +{ + if (container) + container->ref.ref(); +} + +/*! + \fn QCborValue::QCborValue(QCborTag t, const QCborValue &tv) + \fn QCborValue::QCborValue(QCborKnownTags t, const QCborValue &tv) + + Creates a QCborValue for the extended type represented by the tag value \a + t, tagging value \a tv. The tag can later be retrieved using tag() and + the tagged value using taggedValue(). + + \sa isTag(), tag(), taggedValue(), QCborKnownTags + */ +QCborValue::QCborValue(QCborTag t, const QCborValue &tv) + : n(-1), container(new QCborContainerPrivate), t(Tag) +{ + container->ref.store(1); + container->append(t); + container->append(tv); +} + +/*! + Copies the contents of \a other into this object. + */ +QCborValue::QCborValue(const QCborValue &other) + : n(other.n), container(other.container), t(other.t) +{ + if (container) + container->ref.ref(); +} + +/*! + Creates a QCborValue object of the date/time extended type and containing + the value represented by \a dt. The value can later be retrieved using + toDateTime(). + + The CBOR date/time types are extension types using tags: either a string + (in ISO date format) tagged as a \l{QCborKnownTags}{DateTime} or a number + (of seconds since the start of 1970, UTC) tagged as a + \l{QCborKnownTags}{UnixTime_t}. When parsing CBOR streams, QCborValue will + convert \l{QCborKnownTags}{UnixTime_t} to the string-based type. + + \sa toDateTime(), isDateTime(), taggedValue() + */ +QCborValue::QCborValue(const QDateTime &dt) + : QCborValue(QCborKnownTags::DateTimeString, dt.toString(Qt::ISODateWithMs).toLatin1()) +{ + // change types + t = DateTime; + container->elements[1].type = String; +} + +/*! + Creates a QCborValue object of the URL extended type and containing the + value represented by \a url. The value can later be retrieved using toUrl(). + + The CBOR URL type is an extended type represented by a string tagged as an + \l{QCborKnownTags}{Url}. + + \sa toUrl(), isUrl(), taggedValue() + */ +QCborValue::QCborValue(const QUrl &url) + : QCborValue(QCborKnownTags::Url, url.toString(QUrl::DecodeReserved).toUtf8()) +{ + // change types + t = Url; + container->elements[1].type = String; +} + +/*! + Creates a QCborValue object of the regular expression pattern extended type + and containing the value represented by \a rx. The value can later be retrieved + using toRegularExpression(). + + The CBOR regular expression type is an extended type represented by a + string tagged as an \l{QCborKnownTags}{RegularExpression}. Note that CBOR + regular expressions only store the patterns, so any flags that the + QRegularExpression object may carry will be lost. + + \sa toRegularExpression(), isRegularExpression(), taggedValue() + */ +QCborValue::QCborValue(const QRegularExpression &rx) + : QCborValue(QCborKnownTags::RegularExpression, rx.pattern()) +{ + // change type + t = RegularExpression; +} + +/*! + Creates a QCborValue object of the UUID extended type and containing the + value represented by \a uuid. The value can later be retrieved using + toUuid(). + + The CBOR UUID type is an extended type represented by a byte array tagged + as an \l{QCborKnownTags}{Uuid}. + + \sa toUuid(), isUuid(), taggedValue() + */ +QCborValue::QCborValue(const QUuid &uuid) + : QCborValue(QCborKnownTags::Uuid, uuid.toRfc4122()) +{ + // change our type + t = Uuid; +} + +// destructor +void QCborValue::dispose() +{ + container->deref(); +} + +/*! + Replaces the contents of this QCborObject with a copy of \a other. + */ +QCborValue &QCborValue::operator=(const QCborValue &other) +{ + if (other.container) + other.container->ref.ref(); + if (container) + container->deref(); + + n = other.n; + container = other.container; + t = other.t; + return *this; +} + +/*! + Returns the tag of this extended QCborValue object, if it is of the tag + type, \a defaultValue otherwise. + + CBOR represents extended types by associating a number (the tag) with a + stored representation. This function returns that number. To retrieve the + representation, use taggedValue(). + + \sa isTag(), taggedValue(), isDateTime(), isUrl(), isRegularExpression(), isUuid() + */ +QCborTag QCborValue::tag(QCborTag defaultValue) const +{ + return isTag() && container && container->elements.size() == 2 ? + QCborTag(container->elements.at(0).value) : defaultValue; +} + +/*! + Returns the tagged value of this extended QCborValue object, if it is of + the tag type, \a defaultValue otherwise. + + CBOR represents extended types by associating a number (the tag) with a + stored representation. This function returns that representation. To + retrieve the tag, use tag(). + + \sa isTag(), tag(), isDateTime(), isUrl(), isRegularExpression(), isUuid() + */ +QCborValue QCborValue::taggedValue(const QCborValue &defaultValue) const +{ + return isTag() && container && container->elements.size() == 2 ? + container->valueAt(1) : defaultValue; +} + +/*! + Returns the byte array value stored in this QCborValue, if it is of the byte + array type. Otherwise, it returns \a defaultValue. + + Note that this function performs no conversion from other types to + QByteArray. + + \sa isByteArray(), isString(), toString() + */ +QByteArray QCborValue::toByteArray(const QByteArray &defaultValue) const +{ + if (!container || !isByteArray()) + return defaultValue; + + Q_ASSERT(n >= 0); + return container->byteArrayAt(n); +} + +/*! + Returns the string value stored in this QCborValue, if it is of the string + type. Otherwise, it returns \a defaultValue. + + Note that this function performs no conversion from other types to + QString. + + \sa isString(), isByteArray(), toByteArray() + */ +QString QCborValue::toString(const QString &defaultValue) const +{ + if (!container || !isString()) + return defaultValue; + + Q_ASSERT(n >= 0); + return container->stringAt(n); +} + +/*! + Returns the date/time value stored in this QCborValue, if it is of the + date/time extended type. Otherwise, it returns \a defaultValue. + + Note that this function performs no conversion from other types to + QDateTime. + + \sa isDateTime(), isTag(), taggedValue() + */ +QDateTime QCborValue::toDateTime(const QDateTime &defaultValue) const +{ + if (!container || !isDateTime() || container->elements.size() != 2) + return defaultValue; + + Q_ASSERT(n == -1); + const ByteData *byteData = container->byteData(1); + if (!byteData) + return defaultValue; // date/times are never empty, so this must be invalid + + // Our data must be US-ASCII. + Q_ASSERT((container->elements.at(1).flags & Element::StringIsUtf16) == 0); + return QDateTime::fromString(byteData->asLatin1(), Qt::ISODateWithMs); +} + +/*! + Returns the URL value stored in this QCborValue, if it is of the URL + extended type. Otherwise, it returns \a defaultValue. + + Note that this function performs no conversion from other types to QUrl. + + \sa isUrl(), isTag(), taggedValue() + */ +QUrl QCborValue::toUrl(const QUrl &defaultValue) const +{ + if (!container || !isUrl() || container->elements.size() != 2) + return defaultValue; + + Q_ASSERT(n == -1); + const ByteData *byteData = container->byteData(1); + if (!byteData) + return QUrl(); // valid, empty URL + + return QUrl::fromEncoded(byteData->asByteArrayView()); +} + +/*! + Returns the regular expression value stored in this QCborValue, if it is of + the regular expression pattern extended type. Otherwise, it returns \a + defaultValue. + + Note that this function performs no conversion from other types to + QRegularExpression. + + \sa isRegularExpression(), isTag(), taggedValue() + */ +QRegularExpression QCborValue::toRegularExpression(const QRegularExpression &defaultValue) const +{ + if (!container || !isRegularExpression() || container->elements.size() != 2) + return defaultValue; + + Q_ASSERT(n == -1); + return QRegularExpression(container->stringAt(1)); +} + +/*! + Returns the UUID value stored in this QCborValue, if it is of the UUID + extended type. Otherwise, it returns \a defaultValue. + + Note that this function performs no conversion from other types to QUuid. + + \sa isUuid(), isTag(), taggedValue() + */ +QUuid QCborValue::toUuid(const QUuid &defaultValue) const +{ + if (!container || !isUuid() || container->elements.size() != 2) + return defaultValue; + + Q_ASSERT(n == -1); + const ByteData *byteData = container->byteData(1); + if (!byteData) + return defaultValue; // UUIDs must always be 16 bytes, so this must be invalid + + return QUuid::fromRfc4122(byteData->asByteArrayView()); +} + +QCborArray QCborValue::toArray() const +{ + return toArray(QCborArray()); +} + +/*! + Returns the array value stored in this QCborValue, if it is of the array + type. Otherwise, it returns \a defaultValue. + + Note that this function performs no conversion from other types to + QCborArray. + + \sa isArray(), isByteArray(), isMap(), isContainer(), toMap() + */ +QCborArray QCborValue::toArray(const QCborArray &defaultValue) const +{ + if (!isArray()) + return defaultValue; + QCborContainerPrivate *dd = nullptr; + Q_ASSERT(n == -1 || container == nullptr); + if (n < 0) + dd = container; + return dd ? QCborArray(*dd) : defaultValue; +} + +QCborMap QCborValue::toMap() const +{ + return toMap(QCborMap()); +} + +/*! + Returns the map value stored in this QCborValue, if it is of the map type. + Otherwise, it returns \a defaultValue. + + Note that this function performs no conversion from other types to + QCborMap. + + \sa isMap(), isArray(), isContainer(), toArray() + */ +QCborMap QCborValue::toMap(const QCborMap &defaultValue) const +{ + if (!isMap()) + return defaultValue; + QCborContainerPrivate *dd = nullptr; + Q_ASSERT(n == -1 || container == nullptr); + if (n < 0) + dd = container; + return dd ? QCborMap(*dd) : defaultValue; +} + +/*! + If this QCborValue is a QCborMap, searches elements for the value whose key + matches \a key. If there's no key matching \a key in the map or if this + QCborValue object is not a map, returns the undefined value. + + This function is equivalent to: + + \code + value.toMap().value(key); + \endcode + + \sa operator[](qint64), QCborMap::operator[], QCborMap::value(), + QCborMap::find() + */ +const QCborValue QCborValue::operator[](const QString &key) const +{ + if (isMap()) + return toMap().value(key); + return QCborValue(); +} + +/*! + \overload + + If this QCborValue is a QCborMap, searches elements for the value whose key + matches \a key. If there's no key matching \a key in the map or if this + QCborValue object is not a map, returns the undefined value. + + This function is equivalent to: + + \code + value.toMap().value(key); + \endcode + + \sa operator[](qint64), QCborMap::operator[], QCborMap::value(), + QCborMap::find() + */ +const QCborValue QCborValue::operator[](QLatin1String key) const +{ + if (isMap()) + return toMap().value(key); + return QCborValue(); +} + +/*! + If this QCborValue is a QCborMap, searches elements for the value whose key + matches \a key. If this is an array, returns the element whose index is \a + key. If there's no matching value in the array or map, or if this + QCborValue object is not an array or map, returns the undefined value. + + \sa operator[], QCborMap::operator[], QCborMap::value(), + QCborMap::find(), QCborArray::operator[], QCborArray::at() + */ + +const QCborValue QCborValue::operator[](qint64 key) const +{ + if (isMap()) + return toMap().value(key); + if (isArray()) + return toArray().at(key); + return QCborValue(); +} + +/*! + Decodes one item from the CBOR stream found in \a reader and returns the + equivalent representation. This function is recursive: if the item is a map + or array, it will decode all items found in that map or array, until the + outermost object is finished. + + This function need not be used on the root element of a \l + QCborStreamReader. For example, the following code illustrates how to skip + the CBOR signature tag from the beginning of a file: + + \code + if (reader.isTag() && reader.toTag() == QCborKnownTags::Signature) + reader.next(); + + QCborValue contents = QCborValue::fromCbor(reader); + \endcode + + The returned value may be partially complete and indistinguishable from a + valid QCborValue even if the decoding failed. To determine if there was an + error, check if \l{QCborStreamReader::lastError()}{reader.lastError()} is + indicating an error condition. This function stops decoding immediately + after the first error. + + \sa toCbor(), toDiagnosticNotation(), toVariant(), toJsonValue() + */ +QCborValue QCborValue::fromCbor(QCborStreamReader &reader) +{ + QCborValue result; + auto t = reader.type(); + if (reader.lastError() != QCborError::NoError) + t = QCborStreamReader::Invalid; + + switch (t) { + // basic types, no container needed: + case QCborStreamReader::UnsignedInteger: + case QCborStreamReader::NegativeInteger: + case QCborStreamReader::SimpleType: + case QCborStreamReader::Float16: + case QCborStreamReader::Float: + case QCborStreamReader::Double: { + Element e = decodeBasicValueFromCbor(reader); + result.n = e.value; + result.t = e.type; + break; + } + + case QCborStreamReader::Invalid: + result.t = QCborValue::Invalid; + break; // probably a decode error + + // strings + case QCborStreamReader::ByteArray: + case QCborStreamReader::String: + result.n = 0; + result.t = reader.isString() ? String : ByteArray; + result.container = new QCborContainerPrivate; + result.container->ref.ref(); + result.container->decodeStringFromCbor(reader); + break; + + // containers + case QCborStreamReader::Array: + case QCborStreamReader::Map: + result.n = -1; + result.t = reader.isArray() ? Array : Map; + result.container = createContainerFromCbor(reader); + break; + + // tag + case QCborStreamReader::Tag: + result = taggedValueFromCbor(reader); + break; + } + + return result; +} + +/*! + \overload + + Decodes one item from the CBOR stream found in the byte array \a ba and + returns the equivalent representation. This function is recursive: if the + item is a map or array, it will decode all items found in that map or + array, until the outermost object is finished. + + This function stores the error state, if any, in the object pointed to by + \a error, along with the offset of where the error occurred. If no error + happened, it stores \l{QCborError}{NoError} in the error state and the + number of bytes that it consumed (that is, it stores the offset for the + first unused byte). Using that information makes it possible to parse + further data that may exist in the same byte array. + + The returned value may be partially complete and indistinguishable from a + valid QCborValue even if the decoding failed. To determine if there was an + error, check if there was an error stored in \a error. This function stops + decoding immediately after the first error. + + \sa toCbor(), toDiagnosticNotation(), toVariant(), toJsonValue() + */ +QCborValue QCborValue::fromCbor(const QByteArray &ba, QCborParserError *error) +{ + QCborStreamReader reader(ba); + QCborValue result = fromCbor(reader); + if (error) { + error->error = reader.lastError(); + error->offset = reader.currentOffset(); + } + return result; +} + +/*! + \fn QCborValue QCborValue::fromCbor(const char *data, qsizetype len, QCborParserError *error) + \fn QCborValue QCborValue::fromCbor(const quint8 *data, qsizetype len, QCborParserError *error) + \overload + + Converts \a len bytes of \a data to a QByteArray and then calls the + overload of this function that accepts a QByteArray, also passing \a error, + if provided. +*/ + +/*! + Encodes this QCborValue object to its CBOR representation, using the + options specified in \a opt, and return the byte array containing that + representation. + + This function will not fail, except if this QCborValue or any of the + contained items, if this is a map or array, are invalid. Invalid types are + not produced normally by the API, but can result from decoding errors. + + By default, this function performs no transformation on the values in the + QCborValue, writing all floating point directly as double-precision (\c + double) types. If the \l{EncodingOption}{UseFloat} option is specified, it + will use single precision (\c float) for any floating point value for which + there's no loss of precision in using that representation. That includes + infinities and NaN values. + + Similarly, if \l{EncodingOption}{UseFloat16} is specified, this function + will try to use half-precision (\c qfloat16) floating point if the + conversion to that results in no loss of precision. This is always true for + infinities and NaN. + + If \l{EncodingOption}{UseIntegers} is specified, it will use integers for + any floating point value that contains an actual integer. + + \sa fromCbor(), fromVariant(), fromJsonValue() + */ +QByteArray QCborValue::toCbor(EncodingOptions opt) +{ + QByteArray result; + QCborStreamWriter writer(&result); + toCbor(writer, opt); + return result; +} + +/*! + \overload + + Encodes this QCborValue object to its CBOR representation, using the + options specified in \a opt, to the writer specified by \a writer. The same + writer can be used by multiple QCborValues, for example, in order to encode + different elements in a larger array. + + This function will not fail, except if this QCborValue or any of the + contained items, if this is a map or array, are invalid. Invalid types are + not produced normally by the API, but can result from decoding errors. + + By default, this function performs no transformation on the values in the + QCborValue, writing all floating point directly as double-precision + (binary64) types. If the \l{EncodingOption}{UseFloat} option is + specified, it will use single precision (binary32) for any floating point + value for which there's no loss of precision in using that representation. + That includes infinities and NaN values. + + Similarly, if \l{EncodingOption}{UseFloat16} is specified, this function + will try to use half-precision (binary16) floating point if the conversion + to that results in no loss of precision. This is always true for infinities + and NaN. + + If \l{EncodingOption}{UseIntegers} is specified, it will use integers + for any floating point value that contains an actual integer. + + \sa fromCbor(), fromVariant(), fromJsonValue() + */ +Q_NEVER_INLINE void QCborValue::toCbor(QCborStreamWriter &writer, EncodingOptions opt) +{ + if (isContainer() || isTag()) + return encodeToCbor(writer, container, -type(), opt); + if (container) + return encodeToCbor(writer, container, n, opt); + + // very simple types + if (isSimpleType()) + return writer.append(toSimpleType()); + + switch (type()) { + case Integer: + return writer.append(n); + + case Double: + return writeDoubleToCbor(writer, fp_helper(), opt); + + case Invalid: + return; + + case SimpleType: + case False: + case True: + case Null: + case Undefined: + // handled by "if (isSimpleType())" + Q_UNREACHABLE(); + break; + + case ByteArray: + // Byte array with no container is empty + return writer.appendByteString("", 0); + + case String: + // String with no container is empty + return writer.appendTextString("", 0); + + case Array: + case Map: + case Tag: + // handled by "if (isContainer() || isTag())" + Q_UNREACHABLE(); + break; + + case DateTime: + case Url: + case RegularExpression: + case Uuid: + // not possible + Q_UNREACHABLE(); + break; + } +} + +void QCborValueRef::toCbor(QCborStreamWriter &writer, QCborValue::EncodingOptions opt) +{ + concrete().toCbor(writer, opt); +} + +void QCborValueRef::assign(QCborValueRef that, const QCborValue &other) +{ + that.d->replaceAt(that.i, other); +} + +void QCborValueRef::assign(QCborValueRef that, QCborValue &&other) +{ + that.d->replaceAt(that.i, other, QCborContainerPrivate::MoveContainer); +} + +void QCborValueRef::assign(QCborValueRef that, const QCborValueRef other) +{ + // ### optimize? + assign(that, other.concrete()); +} + +QCborValue QCborValueRef::concrete(QCborValueRef self) noexcept +{ + return self.d->valueAt(self.i); +} + +QCborValue::Type QCborValueRef::concreteType(QCborValueRef self) noexcept +{ + return self.d->elements.at(self.i).type; +} + +inline QCborArray::QCborArray(QCborContainerPrivate &dd) noexcept + : d(&dd) +{ +} + +inline QCborMap::QCborMap(QCborContainerPrivate &dd) noexcept + : d(&dd) +{ +} + +#if !defined(QT_NO_DEBUG_STREAM) +static QDebug debugContents(QDebug &dbg, const QCborValue &v) +{ + switch (v.type()) { + case QCborValue::Integer: + return dbg << v.toInteger(); + case QCborValue::ByteArray: + return dbg << "QByteArray(" << v.toByteArray() << ')'; + case QCborValue::String: + return dbg << v.toString(); + case QCborValue::Array: + return dbg << v.toArray(); + case QCborValue::Map: + return dbg << v.toMap(); + case QCborValue::Tag: + dbg << v.tag() << ", "; + return debugContents(dbg, v.taggedValue()); + case QCborValue::SimpleType: + break; + case QCborValue::True: + return dbg << true; + case QCborValue::False: + return dbg << false; + case QCborValue::Null: + return dbg << "nullptr"; + case QCborValue::Undefined: + return dbg; + case QCborValue::Double: { + qint64 i = qint64(v.toDouble()); + if (i == v.toDouble()) + return dbg << i << ".0"; + else + return dbg << v.toDouble(); + } + case QCborValue::DateTime: + return dbg << v.toDateTime(); + case QCborValue::Url: + return dbg << v.toUrl(); + case QCborValue::RegularExpression: + return dbg << v.toRegularExpression(); + case QCborValue::Uuid: + return dbg << v.toUuid(); + case QCborValue::Invalid: + return dbg << "<invalid>"; + } + if (v.isSimpleType()) + return dbg << v.toSimpleType(); + return dbg << "<unknown type " << hex << int(v.type()) << dec << '>'; +} +QDebug operator<<(QDebug dbg, const QCborValue &v) +{ + QDebugStateSaver saver(dbg); + dbg.nospace() << "QCborValue("; + return debugContents(dbg, v) << ')'; +} +#endif + +QT_END_NAMESPACE + +#include "qcborarray.cpp" +#include "qcbormap.cpp" + +#include "moc_qcborvalue.cpp" diff --git a/src/corelib/serialization/qcborvalue.h b/src/corelib/serialization/qcborvalue.h new file mode 100644 index 0000000000..f0bfa23392 --- /dev/null +++ b/src/corelib/serialization/qcborvalue.h @@ -0,0 +1,465 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QCBORVALUE_H +#define QCBORVALUE_H + +#include <QtCore/qbytearray.h> +#include <QtCore/qdatetime.h> +#include <QtCore/qcborcommon.h> +#include <QtCore/qregularexpression.h> +#include <QtCore/qstring.h> +#include <QtCore/qstringview.h> +#include <QtCore/qurl.h> +#include <QtCore/quuid.h> +#include <QtCore/qvariant.h> +#include <QtCore/qvector.h> + +// See qcborcommon.h for why we check +#if defined(QT_X11_DEFINES_FOUND) +# undef True +# undef False +#endif + +#if 0 && QT_HAS_INCLUDE(<compare>) +# include <compare> +#endif + +QT_BEGIN_NAMESPACE + +class QCborArray; +class QCborMap; +class QCborStreamReader; +class QCborStreamWriter; + +struct QCborParserError +{ + qint64 offset = 0; + QCborError error = {}; + + QString errorString() const { return error.toString(); } +}; + +class QCborContainerPrivate; +class Q_CORE_EXPORT QCborValue +{ + Q_GADGET +public: + enum EncodingOption { + SortKeysInMaps = 0x01, + UseFloat = 0x02, + UseFloat16 = UseFloat | 0x04, + UseIntegers = 0x08, + + NoTransformation = 0 + }; + Q_DECLARE_FLAGS(EncodingOptions, EncodingOption) + + enum DiagnosticNotationOption { + Compact = 0x00, + LineWrapped = 0x01, + ExtendedFormat = 0x02 + }; + Q_DECLARE_FLAGS(DiagnosticNotationOptions, DiagnosticNotationOption) + + // different from QCborStreamReader::Type because we have more types + enum Type : int { + Integer = 0x00, + ByteArray = 0x40, + String = 0x60, + Array = 0x80, + Map = 0xa0, + Tag = 0xc0, + + // range 0x100 - 0x1ff for Simple Types + SimpleType = 0x100, + False = SimpleType + int(QCborSimpleType::False), + True = SimpleType + int(QCborSimpleType::True), + Null = SimpleType + int(QCborSimpleType::Null), + Undefined = SimpleType + int(QCborSimpleType::Undefined), + + Double = 0x202, + + // extended (tagged) types + DateTime = 0x10000, + Url = 0x10020, + RegularExpression = 0x10023, + Uuid = 0x10025, + + Invalid = -1 + }; + Q_ENUM(Type) + + QCborValue() {} + QCborValue(Type t_) : t(t_) {} + QCborValue(std::nullptr_t) : t(Null) {} + QCborValue(bool b_) : t(b_ ? True : False) {} +#ifndef Q_QDOC + QCborValue(int i) : QCborValue(qint64(i)) {} + QCborValue(unsigned u) : QCborValue(qint64(u)) {} +#endif + QCborValue(qint64 i) : n(i), t(Integer) {} + QCborValue(double v) : t(Double) { memcpy(&n, &v, sizeof(n)); } + QCborValue(QCborSimpleType st) : t(type_helper(st)) {} + + QCborValue(const QByteArray &ba); + QCborValue(const QString &s); + QCborValue(QLatin1String s); +#ifndef QT_NO_CAST_FROM_ASCII + QT_ASCII_CAST_WARN QCborValue(const char *s) : QCborValue(QString::fromUtf8(s)) {} +#endif + QCborValue(const QCborArray &a); + QCborValue(QCborArray &&a); + QCborValue(const QCborMap &m); + QCborValue(QCborMap &&m); + QCborValue(QCborTag tag, const QCborValue &taggedValue = QCborValue()); + QCborValue(QCborKnownTags t_, const QCborValue &tv = QCborValue()) + : QCborValue(QCborTag(t_), tv) + {} + + explicit QCborValue(const QDateTime &dt); + explicit QCborValue(const QUrl &url); + explicit QCborValue(const QRegularExpression &rx); + explicit QCborValue(const QUuid &uuid); + + ~QCborValue() { if (container) dispose(); } + + // make sure const char* doesn't go call the bool constructor + QCborValue(const void *) = delete; + + QCborValue(const QCborValue &other); + QCborValue(QCborValue &&other) noexcept + : n(other.n), container(other.container), t(other.t) + { + other.t = Undefined; + other.container = nullptr; + } + QCborValue &operator=(const QCborValue &other); + QCborValue &operator=(QCborValue &&other) noexcept + { + QCborValue tmp; + qSwap(*this, tmp); + qSwap(other, *this); + return *this; + } + + void swap(QCborValue &other) noexcept + { + qSwap(n, other.n); + qSwap(container, other.container); + qSwap(t, other.t); + } + + Type type() const { return t; } + bool isInteger() const { return type() == Integer; } + bool isByteArray() const { return type() == ByteArray; } + bool isString() const { return type() == String; } + bool isArray() const { return type() == Array; } + bool isMap() const { return type() == Map; } + bool isTag() const { return isTag_helper(type()); } + bool isFalse() const { return type() == False; } + bool isTrue() const { return type() == True; } + bool isBool() const { return isFalse() || isTrue(); } + bool isNull() const { return type() == Null; } + bool isUndefined() const { return type() == Undefined; } + bool isDouble() const { return type() == Double; } + bool isDateTime() const { return type() == DateTime; } + bool isUrl() const { return type() == Url; } + bool isRegularExpression() const { return type() == RegularExpression; } + bool isUuid() const { return type() == Uuid; } + bool isInvalid() const { return type() == Invalid; } + bool isContainer() const { return isMap() || isArray(); } + + bool isSimpleType() const + { + return int(type()) >> 8 == int(SimpleType) >> 8; + } + bool isSimpleType(QCborSimpleType st) const + { + return type() == type_helper(st); + } + QCborSimpleType toSimpleType(QCborSimpleType defaultValue = QCborSimpleType::Undefined) const + { + return isSimpleType() ? QCborSimpleType(type() & 0xff) : defaultValue; + } + + qint64 toInteger(qint64 defaultValue = 0) const + { return isInteger() ? value_helper() : isDouble() ? qint64(fp_helper()) : defaultValue; } + bool toBool(bool defaultValue = false) const + { return isBool() ? isTrue() : defaultValue; } + double toDouble(double defaultValue = 0) const + { return isDouble() ? fp_helper() : isInteger() ? double(value_helper()) : defaultValue; } + + QCborTag tag(QCborTag defaultValue = QCborTag(-1)) const; + QCborValue taggedValue(const QCborValue &defaultValue = QCborValue()) const; + + QByteArray toByteArray(const QByteArray &defaultValue = {}) const; + QString toString(const QString &defaultValue = {}) const; + QDateTime toDateTime(const QDateTime &defaultValue = {}) const; + QUrl toUrl(const QUrl &defaultValue = {}) const; + QRegularExpression toRegularExpression(const QRegularExpression &defaultValue = {}) const; + QUuid toUuid(const QUuid &defaultValue = {}) const; + +#ifdef Q_QDOC + QCborArray toArray(const QCborArray &a = {}) const; + QCborMap toMap(const QCborMap &m = {}) const; +#else + // only forward-declared, need split functions + QCborArray toArray() const; + QCborArray toArray(const QCborArray &defaultValue) const; + QCborMap toMap() const; + QCborMap toMap(const QCborMap &defaultValue) const; +#endif + + const QCborValue operator[](const QString &key) const; + const QCborValue operator[](QLatin1String key) const; + const QCborValue operator[](qint64 key) const; + + int compare(const QCborValue &other) const; +#if 0 && QT_HAS_INCLUDE(<compare>) + std::strong_ordering operator<=>(const QCborValue &other) const + { + int c = compare(other); + if (c > 0) return std::partial_ordering::greater; + if (c == 0) return std::partial_ordering::equivalent; + return std::partial_ordering::less; + } +#else + bool operator==(const QCborValue &other) const noexcept + { return compare(other) == 0; } + bool operator!=(const QCborValue &other) const noexcept + { return !(*this == other); } + bool operator<(const QCborValue &other) const + { return compare(other) < 0; } +#endif + + static QCborValue fromVariant(const QVariant &variant); + QVariant toVariant() const; + static QCborValue fromJsonValue(const QJsonValue &v); + QJsonValue toJsonValue() const; + + static QCborValue fromCbor(QCborStreamReader &reader); + static QCborValue fromCbor(const QByteArray &ba, QCborParserError *error = nullptr); + static QCborValue fromCbor(const char *data, qsizetype len, QCborParserError *error = nullptr) + { return fromCbor(QByteArray(data, int(len)), error); } + static QCborValue fromCbor(const quint8 *data, qsizetype len, QCborParserError *error = nullptr) + { return fromCbor(QByteArray(reinterpret_cast<const char *>(data), int(len)), error); } + QByteArray toCbor(EncodingOptions opt = NoTransformation); + void toCbor(QCborStreamWriter &writer, EncodingOptions opt = NoTransformation); + + QString toDiagnosticNotation(DiagnosticNotationOptions opts = Compact) const; + +private: + friend class QCborValueRef; + friend class QCborContainerPrivate; + qint64 n = 0; + QCborContainerPrivate *container = nullptr; + Type t = Undefined; + + void dispose(); + qint64 value_helper() const + { + return n; + } + + double fp_helper() const + { + Q_STATIC_ASSERT(sizeof(double) == sizeof(n)); + double d; + memcpy(&d, &n, sizeof(d)); + return d; + } + + Q_DECL_CONSTEXPR static Type type_helper(QCborSimpleType st) + { + return Type(quint8(st) | SimpleType); + } + + Q_DECL_CONSTEXPR static bool isTag_helper(Type t) + { + return t == Tag || t >= 0x10000; + } +}; +Q_DECLARE_SHARED(QCborValue) + +class Q_CORE_EXPORT QCborValueRef +{ +public: + operator QCborValue() const { return concrete(); } + + QCborValueRef(const QCborValueRef &) noexcept = default; + QCborValueRef(QCborValueRef &&) noexcept = default; + QCborValueRef &operator=(const QCborValue &other) + { assign(*this, other); return *this; } + QCborValueRef &operator=(QCborValue &&other) + { assign(*this, std::move(other)); other.container = nullptr; return *this; } + QCborValueRef &operator=(const QCborValueRef &other) + { assign(*this, other); return *this; } + + QCborValue::Type type() const { return concreteType(); } + bool isInteger() const { return type() == QCborValue::Integer; } + bool isByteArray() const { return type() == QCborValue::ByteArray; } + bool isString() const { return type() == QCborValue::String; } + bool isArray() const { return type() == QCborValue::Array; } + bool isMap() const { return type() == QCborValue::Map; } + bool isTag() const { return QCborValue::isTag_helper(type()); } + bool isFalse() const { return type() == QCborValue::False; } + bool isTrue() const { return type() == QCborValue::True; } + bool isBool() const { return isFalse() || isTrue(); } + bool isNull() const { return type() == QCborValue::Null; } + bool isUndefined() const { return type() == QCborValue::Undefined; } + bool isDouble() const { return type() == QCborValue::Double; } + bool isDateTime() const { return type() == QCborValue::DateTime; } + bool isUrl() const { return type() == QCborValue::Url; } + bool isRegularExpression() const { return type() == QCborValue::RegularExpression; } + bool isUuid() const { return type() == QCborValue::Uuid; } + bool isInvalid() const { return type() == QCborValue::Invalid; } + bool isContainer() const { return isMap() || isArray(); } + bool isSimpleType() const + { + return type() >= QCborValue::SimpleType && type() < QCborValue::SimpleType + 0x100; + } + bool isSimpleType(QCborSimpleType st) const + { + return type() == QCborValue::type_helper(st); + } + + QCborTag tag(QCborTag defaultValue = QCborTag(-1)) const + { return concrete().tag(defaultValue); } + QCborValue taggedValue(const QCborValue &defaultValue = QCborValue()) const + { return concrete().taggedValue(defaultValue); } + + qint64 toInteger(qint64 defaultValue = 0) const + { return concrete().toInteger(defaultValue); } + bool toBool(bool defaultValue = false) const + { return concrete().toBool(defaultValue); } + double toDouble(double defaultValue = 0) const + { return concrete().toDouble(defaultValue); } + + QByteArray toByteArray(const QByteArray &defaultValue = {}) const + { return concrete().toByteArray(defaultValue); } + QString toString(const QString &defaultValue = {}) const + { return concrete().toString(defaultValue); } + QDateTime toDateTime(const QDateTime &defaultValue = {}) const + { return concrete().toDateTime(defaultValue); } + QUrl toUrl(const QUrl &defaultValue = {}) const + { return concrete().toUrl(defaultValue); } + QRegularExpression toRegularExpression(const QRegularExpression &defaultValue = {}) const + { return concrete().toRegularExpression(defaultValue); } + QUuid toUuid(const QUuid &defaultValue = {}) const + { return concrete().toUuid(defaultValue); } + +#ifdef Q_QDOC + QCborArray toArray(const QCborArray &a = {}) const; + QCborMap toMap(const QCborMap &m = {}) const; +#else + // only forward-declared, need split functions. Implemented in qcbor{array,map}.h + QCborArray toArray() const; + QCborArray toArray(const QCborArray &a) const; + QCborMap toMap() const; + QCborMap toMap(const QCborMap &m) const; +#endif + + int compare(const QCborValue &other) const + { return concrete().compare(other); } +#if 0 && QT_HAS_INCLUDE(<compare>) + std::strong_ordering operator<=>(const QCborValue &other) const + { + int c = compare(other); + if (c > 0) return std::strong_ordering::greater; + if (c == 0) return std::strong_ordering::equivalent; + return std::strong_ordering::less; + } +#else + bool operator==(const QCborValue &other) const + { return compare(other) == 0; } + bool operator!=(const QCborValue &other) const + { return !(*this == other); } + bool operator<(const QCborValue &other) const + { return compare(other) < 0; } +#endif + + QVariant toVariant() const { return concrete().toVariant(); } + QJsonValue toJsonValue() const; + + QByteArray toCbor(QCborValue::EncodingOptions opt = QCborValue::NoTransformation) + { return concrete().toCbor(opt); } + void toCbor(QCborStreamWriter &writer, QCborValue::EncodingOptions opt = QCborValue::NoTransformation); + + QString toDiagnosticNotation(QCborValue::DiagnosticNotationOptions opt = QCborValue::Compact) + { return concrete().toDiagnosticNotation(opt); } + +private: + friend class QCborArray; + friend class QCborMap; + friend class QCborContainerPrivate; + friend class QCborValueRefPtr; + + // static so we can pass this by value + static void assign(QCborValueRef that, const QCborValue &other); + static void assign(QCborValueRef that, QCborValue &&other); + static void assign(QCborValueRef that, const QCborValueRef other); + static QCborValue concrete(QCborValueRef that) noexcept; + QCborValue concrete() const noexcept { return concrete(*this); } + + static QCborValue::Type concreteType(QCborValueRef self) noexcept Q_DECL_PURE_FUNCTION; + QCborValue::Type concreteType() const noexcept { return concreteType(*this); } + + // this will actually be invalid... + Q_DECL_CONSTEXPR QCborValueRef() : d(nullptr), i(0) {} + + QCborValueRef(QCborContainerPrivate *dd, qsizetype ii) + : d(dd), i(ii) + {} + QCborContainerPrivate *d; + qsizetype i; +}; + +#if !defined(QT_NO_DEBUG_STREAM) +Q_CORE_EXPORT QDebug operator<<(QDebug, const QCborValue &v); +#endif + +QT_END_NAMESPACE + +#if defined(QT_X11_DEFINES_FOUND) +# define True 1 +# define False 0 +#endif + +#endif // QCBORVALUE_H diff --git a/src/corelib/serialization/qcborvalue_p.h b/src/corelib/serialization/qcborvalue_p.h new file mode 100644 index 0000000000..3a28707056 --- /dev/null +++ b/src/corelib/serialization/qcborvalue_p.h @@ -0,0 +1,398 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QCBORVALUE_P_H +#define QCBORVALUE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. +// This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qcborvalue.h" + +#include <private/qglobal_p.h> +#include <private/qutfcodec_p.h> + +#include <math.h> + +QT_BEGIN_NAMESPACE + +namespace QtCbor { +struct Undefined {}; +struct Element +{ + enum ValueFlag : quint32 { + IsContainer = 0x0001, + HasByteData = 0x0002, + StringIsUtf16 = 0x0004, + StringIsAscii = 0x0008 + }; + Q_DECLARE_FLAGS(ValueFlags, ValueFlag) + + union { + qint64 value; + QCborContainerPrivate *container; + }; + QCborValue::Type type; + ValueFlags flags = {}; + + Element(qint64 v = 0, QCborValue::Type t = QCborValue::Undefined, ValueFlags f = {}) + : value(v), type(t), flags(f) + {} + + Element(QCborContainerPrivate *d, QCborValue::Type t, ValueFlags f = {}) + : container(d), type(t), flags(f | IsContainer) + {} + + double fpvalue() const + { + double d; + memcpy(&d, &value, sizeof(d)); + return d; + } +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(Element::ValueFlags) +Q_STATIC_ASSERT(sizeof(Element) == 16); + +struct ByteData +{ + QByteArray::size_type len; + + const char *byte() const { return reinterpret_cast<const char *>(this + 1); } + char *byte() { return reinterpret_cast<char *>(this + 1); } + const QChar *utf16() const { return reinterpret_cast<const QChar *>(this + 1); } + QChar *utf16() { return reinterpret_cast<QChar *>(this + 1); } + + QByteArray toByteArray() const { return QByteArray(byte(), len); } + QString toString() const { return QString(utf16(), len / 2); } + QString toUtf8String() const { return QString::fromUtf8(byte(), len); } + + QByteArray asByteArrayView() const { return QByteArray::fromRawData(byte(), len); } + QLatin1String asLatin1() const { return QLatin1String(byte(), len); } + QStringView asStringView() const{ return QStringView(utf16(), len / 2); } + QString asQStringRaw() const { return QString::fromRawData(utf16(), len / 2); } +}; +Q_STATIC_ASSERT(std::is_pod<ByteData>::value); +} // namespace QtCbor + +Q_DECLARE_TYPEINFO(QtCbor::Element, Q_PRIMITIVE_TYPE); + +class QCborContainerPrivate : public QSharedData +{ + friend class QExplicitlySharedDataPointer<QCborContainerPrivate>; + ~QCborContainerPrivate(); + +public: + enum ContainerDisposition { CopyContainer, MoveContainer }; + + QByteArray::size_type usedData = 0; + QByteArray data; + QVector<QtCbor::Element> elements; + + void deref() { if (!ref.deref()) delete this; } + void compact(qsizetype reserved); + static QCborContainerPrivate *clone(QCborContainerPrivate *d, qsizetype reserved = -1); + static QCborContainerPrivate *detach(QCborContainerPrivate *d, qsizetype reserved); + + qptrdiff addByteData(const char *block, qsizetype len) + { + // This function does not do overflow checking, since the len parameter + // is expected to be trusted. There's another version of this function + // in decodeStringFromCbor(), which checks. + + qptrdiff offset = data.size(); + + // align offset + offset += Q_ALIGNOF(QtCbor::ByteData) - 1; + offset &= ~(Q_ALIGNOF(QtCbor::ByteData) - 1); + + qptrdiff increment = qptrdiff(sizeof(QtCbor::ByteData)) + len; + + usedData += increment; + data.resize(offset + increment); + + char *ptr = data.begin() + offset; + auto b = new (ptr) QtCbor::ByteData; + b->len = len; + if (block) + memcpy(b->byte(), block, len); + + return offset; + } + + const QtCbor::ByteData *byteData(QtCbor::Element e) const + { + if ((e.flags & QtCbor::Element::HasByteData) == 0) + return nullptr; + + size_t offset = size_t(e.value); + Q_ASSERT((offset % Q_ALIGNOF(QtCbor::ByteData)) == 0); + Q_ASSERT(offset + sizeof(QtCbor::ByteData) <= size_t(data.size())); + + auto b = reinterpret_cast<const QtCbor::ByteData *>(data.constData() + offset); + Q_ASSERT(offset + sizeof(*b) + size_t(b->len) <= size_t(data.size())); + return b; + } + const QtCbor::ByteData *byteData(qsizetype idx) const + { + return byteData(elements.at(idx)); + } + + QCborContainerPrivate *containerAt(qsizetype idx, QCborValue::Type type) const + { + const QtCbor::Element &e = elements.at(idx); + if (e.type != type || (e.flags & QtCbor::Element::IsContainer) == 0) + return nullptr; + return e.container; + } + + void replaceAt_complex(QtCbor::Element &e, const QCborValue &value, ContainerDisposition disp); + void replaceAt_internal(QtCbor::Element &e, const QCborValue &value, ContainerDisposition disp) + { + if (value.container) + return replaceAt_complex(e, value, disp); + + e.value = value.value_helper(); + e.type = value.type(); + if (value.isContainer()) + e.container = nullptr; + } + void replaceAt(qsizetype idx, const QCborValue &value, ContainerDisposition disp = CopyContainer) + { + QtCbor::Element &e = elements[idx]; + if (e.flags & QtCbor::Element::IsContainer) { + e.container->deref(); + e.container = nullptr; + e.flags = {}; + } else if (auto b = byteData(e)) { + usedData -= b->len + sizeof(QtCbor::ByteData); + } + replaceAt_internal(e, value, disp); + } + void insertAt(qsizetype idx, const QCborValue &value, ContainerDisposition disp = CopyContainer) + { + replaceAt_internal(*elements.insert(elements.begin() + idx, {}), value, disp); + } + + void append(QtCbor::Undefined) + { + elements.append(QtCbor::Element()); + } + void append(qint64 value) + { + elements.append(QtCbor::Element(value , QCborValue::Integer)); + } + void append(QCborTag tag) + { + elements.append(QtCbor::Element(qint64(tag), QCborValue::Tag)); + } + void appendByteData(const char *data, qsizetype len, QCborValue::Type type, + QtCbor::Element::ValueFlags extraFlags = {}) + { + elements.append(QtCbor::Element(addByteData(data, len), type, + QtCbor::Element::HasByteData | extraFlags)); + } + void append(QLatin1String s) + { + if (!QtPrivate::isAscii(s)) + return append(QString(s)); + + // US-ASCII is a subset of UTF-8, so we can keep in 8-bit + appendByteData(s.latin1(), s.size(), QCborValue::String, + QtCbor::Element::StringIsAscii); + } + void appendAsciiString(const QString &s); + void append(const QString &s) + { + if (QtPrivate::isAscii(s)) + appendAsciiString(s); + else + appendByteData(reinterpret_cast<const char *>(s.constData()), s.size() * 2, + QCborValue::String, QtCbor::Element::StringIsUtf16); + } + void append(const QCborValue &v) + { + insertAt(elements.size(), v); + } + + QByteArray byteArrayAt(qsizetype idx) const + { + const auto &e = elements.at(idx); + const auto data = byteData(e); + if (!data) + return QByteArray(); + return data->toByteArray(); + } + QString stringAt(qsizetype idx) const + { + const auto &e = elements.at(idx); + const auto data = byteData(e); + if (!data) + return QString(); + if (e.flags & QtCbor::Element::StringIsUtf16) + return data->toString(); + if (e.flags & QtCbor::Element::StringIsAscii) + return data->asLatin1(); + return data->toUtf8String(); + } + + static void resetValue(QCborValue &v) + { + v.container = nullptr; + } + + static QCborValue makeValue(QCborValue::Type type, qint64 n, QCborContainerPrivate *d = nullptr, + ContainerDisposition disp = CopyContainer) + { + QCborValue result(type); + result.n = n; + result.container = d; + if (d && disp == CopyContainer) + d->ref.ref(); + return result; + } + + QCborValue valueAt(qsizetype idx) const + { + const auto &e = elements.at(idx); + + if (e.flags & QtCbor::Element::IsContainer) { + if (e.type == QCborValue::Tag && e.container->elements.size() != 2) { + // invalid tags can be created due to incomplete parsing + return makeValue(QCborValue::Invalid, 0, nullptr); + } + return makeValue(e.type, -1, e.container); + } else if (e.flags & QtCbor::Element::HasByteData) { + return makeValue(e.type, idx, const_cast<QCborContainerPrivate *>(this)); + } + return makeValue(e.type, e.value); + } + QCborValue extractAt_complex(QtCbor::Element e); + QCborValue extractAt(qsizetype idx) + { + QtCbor::Element e; + qSwap(e, elements[idx]); + + if (e.flags & QtCbor::Element::IsContainer) { + if (e.type == QCborValue::Tag && e.container->elements.size() != 2) { + // invalid tags can be created due to incomplete parsing + e.container->deref(); + return makeValue(QCborValue::Invalid, 0, nullptr); + } + return makeValue(e.type, -1, e.container, MoveContainer); + } else if (e.flags & QtCbor::Element::HasByteData) { + return extractAt_complex(e); + } + return makeValue(e.type, e.value); + } + + static QtCbor::Element elementFromValue(const QCborValue &value) + { + if (value.n >= 0 && value.container) + return value.container->elements.at(value.n); + + QtCbor::Element e; + e.value = value.n; + e.type = value.t; + if (value.container) { + e.container = value.container; + e.flags = QtCbor::Element::IsContainer; + } + return e; + } + + bool stringEqualsElement(qsizetype idx, QLatin1String s) const + { + const auto &e = elements.at(idx); + if (e.type != QCborValue::String) + return false; + + const QtCbor::ByteData *b = byteData(idx); + if (!b) + return s.isEmpty(); + + if (e.flags & QtCbor::Element::StringIsUtf16) + return QtPrivate::compareStrings(b->asStringView(), s) == 0; + return QUtf8::compareUtf8(b->byte(), b->len, s) == 0; + } + bool stringEqualsElement(qsizetype idx, const QString &s) const + { + const auto &e = elements.at(idx); + if (e.type != QCborValue::String) + return false; + + const QtCbor::ByteData *b = byteData(idx); + if (!b) + return s.isEmpty(); + + if (e.flags & QtCbor::Element::StringIsUtf16) + return QtPrivate::compareStrings(b->asStringView(), s) == 0; + return QUtf8::compareUtf8(b->byte(), b->len, s.data(), s.size()) == 0; + } + + static int compareElement_helper(const QCborContainerPrivate *c1, QtCbor::Element e1, + const QCborContainerPrivate *c2, QtCbor::Element e2); + int compareElement(qsizetype idx, const QCborValue &value) const + { + auto &e1 = elements.at(idx); + auto e2 = elementFromValue(value); + return compareElement_helper(this, e1, value.container, e2); + } + + void removeAt(qsizetype idx) + { + replaceAt(idx, {}); + elements.remove(idx); + } + + void decodeValueFromCbor(QCborStreamReader &reader); + void decodeFromCbor(QCborStreamReader &reader); + void decodeStringFromCbor(QCborStreamReader &reader); +}; + +QT_END_NAMESPACE + +#endif // QCBORVALUE_P_H diff --git a/src/corelib/serialization/qdatastream.cpp b/src/corelib/serialization/qdatastream.cpp index 09e98cb2b4..60467b4824 100644 --- a/src/corelib/serialization/qdatastream.cpp +++ b/src/corelib/serialization/qdatastream.cpp @@ -564,6 +564,7 @@ void QDataStream::setByteOrder(ByteOrder bo) \value Qt_5_9 Same as Qt_5_6 \value Qt_5_10 Same as Qt_5_6 \value Qt_5_11 Same as Qt_5_6 + \value Qt_5_12 Same as Qt_5_6 \omitvalue Qt_DefaultCompiledVersion \sa setVersion(), version() diff --git a/src/corelib/serialization/qdatastream.h b/src/corelib/serialization/qdatastream.h index 1775022355..eae0146553 100644 --- a/src/corelib/serialization/qdatastream.h +++ b/src/corelib/serialization/qdatastream.h @@ -98,10 +98,11 @@ public: Qt_5_9 = Qt_5_8, Qt_5_10 = Qt_5_9, Qt_5_11 = Qt_5_10, -#if QT_VERSION >= 0x050c00 + Qt_5_12 = 18, +#if QT_VERSION >= 0x050d00 #error Add the datastream version for this Qt version and update Qt_DefaultCompiledVersion #endif - Qt_DefaultCompiledVersion = Qt_5_11 + Qt_DefaultCompiledVersion = Qt_5_12 }; enum ByteOrder { diff --git a/src/corelib/serialization/qjson_p.h b/src/corelib/serialization/qjson_p.h index dc56a49084..feba1faac6 100644 --- a/src/corelib/serialization/qjson_p.h +++ b/src/corelib/serialization/qjson_p.h @@ -69,6 +69,9 @@ QT_BEGIN_NAMESPACE +// in qstring.cpp +void qt_to_latin1_unchecked(uchar *dst, const ushort *uc, qsizetype len); + /* This defines a binary data structure for Json data. The data structure is optimised for fast reading and minimum allocations. The whole data structure can be mmap'ed and used directly. @@ -294,31 +297,10 @@ public: int len = d->length = str.length(); uchar *l = (uchar *)d->latin1; const ushort *uc = (const ushort *)str.unicode(); - int i = 0; -#ifdef __SSE2__ - for ( ; i + 16 <= len; i += 16) { - __m128i chunk1 = _mm_loadu_si128((__m128i*)&uc[i]); // load - __m128i chunk2 = _mm_loadu_si128((__m128i*)&uc[i + 8]); // load - // pack the two vector to 16 x 8bits elements - const __m128i result = _mm_packus_epi16(chunk1, chunk2); - _mm_storeu_si128((__m128i*)&l[i], result); // store - } -# ifdef Q_PROCESSOR_X86_64 - // we can do one more round, of 8 characters - if (i + 8 <= len) { - __m128i chunk = _mm_loadu_si128((__m128i*)&uc[i]); // load - // pack with itself, we'll discard the high part anyway - chunk = _mm_packus_epi16(chunk, chunk); - // unaligned 64-bit store - qToUnaligned(_mm_cvtsi128_si64(chunk), l + i); - i += 8; - } -# endif -#endif - for ( ; i < len; ++i) - l[i] = uc[i]; - for ( ; (quintptr)(l+i) & 0x3; ++i) - l[i] = 0; + qt_to_latin1_unchecked(l, uc, len); + + for ( ; (quintptr)(l+len) & 0x3; ++len) + l[len] = 0; return *this; } diff --git a/src/corelib/serialization/qjsoncbor.cpp b/src/corelib/serialization/qjsoncbor.cpp new file mode 100644 index 0000000000..158f1950d0 --- /dev/null +++ b/src/corelib/serialization/qjsoncbor.cpp @@ -0,0 +1,954 @@ +/**************************************************************************** +** +** 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 "qcborvalue.h" +#include "qcborvalue_p.h" + +#include "qcborarray.h" +#include "qcbormap.h" +#include "qjson_p.h" + +#include <private/qnumeric_p.h> +#include <quuid.h> + +QT_BEGIN_NAMESPACE + +using namespace QtCbor; + +static QJsonValue fpToJson(double v) +{ + return qt_is_finite(v) ? QJsonValue(v) : QJsonValue(); +} + +static QString simpleTypeString(QCborValue::Type t) +{ + int simpleType = t - QCborValue::SimpleType; + if (unsigned(simpleType) < 0x100) + return QString::fromLatin1("simple(%1)").arg(simpleType); + + // if we got here, we got an unknown type + qWarning("QCborValue: found unknown type 0x%x", t); + return QString(); + +} + +static QString encodeByteArray(const QCborContainerPrivate *d, qsizetype idx, QCborTag encoding) +{ + const ByteData *b = d->byteData(idx); + if (!b) + return QString(); + + QByteArray data = QByteArray::fromRawData(b->byte(), b->len); + if (encoding == QCborKnownTags::ExpectedBase16) + data = data.toHex(); + else if (encoding == QCborKnownTags::ExpectedBase64) + data = data.toBase64(); + else + data = data.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + + return QString::fromLatin1(data, data.size()); +} + +static QString makeString(const QCborContainerPrivate *d, qsizetype idx); + +static QString maybeEncodeTag(const QCborContainerPrivate *d) +{ + qint64 tag = d->elements.at(0).value; + const Element &e = d->elements.at(1); + const ByteData *b = d->byteData(e); + + switch (tag) { + case qint64(QCborKnownTags::DateTimeString): + case qint64(QCborKnownTags::Url): + if (e.type == QCborValue::String) + return makeString(d, 1); + break; + + case qint64(QCborKnownTags::ExpectedBase64url): + case qint64(QCborKnownTags::ExpectedBase64): + case qint64(QCborKnownTags::ExpectedBase16): + if (e.type == QCborValue::ByteArray) + return encodeByteArray(d, 1, QCborTag(tag)); + break; + + case qint64(QCborKnownTags::Uuid): + if (e.type == QCborValue::ByteArray && b->len == sizeof(QUuid)) + return QUuid::fromRfc4122(b->asByteArrayView()).toString(QUuid::WithoutBraces); + } + + // don't know what to do, bail out + return QString(); +} + +static QString encodeTag(const QCborContainerPrivate *d) +{ + QString s; + if (!d || d->elements.size() != 2) + return s; // invalid (incomplete?) tag state + + s = maybeEncodeTag(d); + if (s.isNull()) { + // conversion failed, ignore the tag and convert the tagged value + s = makeString(d, 1); + } + return s; +} + +static Q_NEVER_INLINE QString makeString(const QCborContainerPrivate *d, qsizetype idx) +{ + const auto &e = d->elements.at(idx); + + switch (e.type) { + case QCborValue::Integer: + return QString::number(qint64(e.value)); + + case QCborValue::Double: + return QString::number(e.fpvalue()); + + case QCborValue::ByteArray: + return encodeByteArray(d, idx, QCborTag(QCborKnownTags::ExpectedBase64url)); + + case QCborValue::String: + return d->stringAt(idx); + + case QCborValue::Array: + case QCborValue::Map: + return d->valueAt(idx).toDiagnosticNotation(QCborValue::Compact); + + case QCborValue::SimpleType: + break; + + case QCborValue::False: + return QStringLiteral("false"); + + case QCborValue::True: + return QStringLiteral("true"); + + case QCborValue::Null: + return QStringLiteral("null"); + + case QCborValue::Undefined: + return QStringLiteral("undefined"); + + case QCborValue::Invalid: + return QString(); + + case QCborValue::Tag: + case QCborValue::DateTime: + case QCborValue::Url: + case QCborValue::RegularExpression: + case QCborValue::Uuid: + return encodeTag(e.flags & Element::IsContainer ? e.container : nullptr); + } + + // maybe it's a simple type + return simpleTypeString(e.type); +} + +static QJsonValue convertToJson(const QCborContainerPrivate *d, qsizetype idx); + +static QJsonArray convertToJsonArray(const QCborContainerPrivate *d) +{ + QJsonArray a; + if (d) { + for (qsizetype idx = 0; idx < d->elements.size(); ++idx) + a.append(convertToJson(d, idx)); + } + return a; +} + +static QJsonObject convertToJsonObject(const QCborContainerPrivate *d) +{ + QJsonObject o; + if (d) { + for (qsizetype idx = 0; idx < d->elements.size(); idx += 2) + o.insert(makeString(d, idx), convertToJson(d, idx + 1)); + } + return o; +} + +static QJsonValue convertExtendedTypeToJson(const QCborContainerPrivate *d) +{ + qint64 tag = d->elements.at(0).value; + + switch (tag) { + case qint64(QCborKnownTags::Url): + // use the fullly-encoded URL form + if (d->elements.at(1).type == QCborValue::String) + return QUrl::fromEncoded(d->byteData(1)->asByteArrayView()).toString(QUrl::FullyEncoded); + Q_FALLTHROUGH(); + + case qint64(QCborKnownTags::DateTimeString): + case qint64(QCborKnownTags::ExpectedBase64url): + case qint64(QCborKnownTags::ExpectedBase64): + case qint64(QCborKnownTags::ExpectedBase16): + case qint64(QCborKnownTags::Uuid): { + // use the string conversion + QString s = maybeEncodeTag(d); + if (!s.isNull()) + return s; + } + } + + // for all other tags, ignore it and return the converted tagged item + return convertToJson(d, 1); +} + +static QJsonValue convertToJson(const QCborContainerPrivate *d, qsizetype idx) +{ + // encoding the container itself + if (idx == -QCborValue::Array) + return convertToJsonArray(d); + if (idx == -QCborValue::Map) + return convertToJsonObject(d); + if (idx < 0) { + // tag-like type + if (!d || d->elements.size() != 2) + return QJsonValue::Undefined; // invalid state + return convertExtendedTypeToJson(d); + } + + // an element in the container + const auto &e = d->elements.at(idx); + switch (e.type) { + case QCborValue::Integer: + return qint64(e.value); + + case QCborValue::ByteArray: + case QCborValue::String: + case QCborValue::SimpleType: + // make string + break; + + case QCborValue::Array: + case QCborValue::Map: + case QCborValue::Tag: + case QCborValue::DateTime: + case QCborValue::Url: + case QCborValue::RegularExpression: + case QCborValue::Uuid: + // recurse + return convertToJson(e.flags & Element::IsContainer ? e.container : nullptr, -e.type); + + case QCborValue::Null: + return QJsonValue(); + + case QCborValue::Undefined: + case QCborValue::Invalid: + return QJsonValue(QJsonValue::Undefined); + + case QCborValue::False: + return false; + + case QCborValue::True: + return true; + + case QCborValue::Double: + return fpToJson(e.fpvalue()); + } + + return makeString(d, idx); +} + +/*! + Converts this QCborValue object to an equivalent representation in JSON and + returns it as a QJsonValue. + + Please note that CBOR contains a richer and wider type set than JSON, so + some information may be lost in this conversion. The following table + compares CBOR types to JSON types and indicates whether information may be + lost or not. + + \table + \header \li CBOR Type \li JSON Type \li Comments + \row \li Bool \li Bool \li No data loss possible + \row \li Double \li Number \li Infinities and NaN will be converted to Null; + no data loss for other values + \row \li Integer \li Number \li Data loss possible in the conversion if the + integer is larger than 2\sup{53} or smaller + than -2\sup{53}. + \row \li Null \li Null \li No data loss possible + \row \li Undefined \li Null \li Type information lost + \row \li String \li String \li No data loss possible + \row \li Byte Array \li String \li Converted to a lossless encoding like Base64url, + but the distinction between strings and byte + arrays is lost + \row \li Other simple types \li String \li Type information lost + \row \li Array \li Array \li Conversion applies to each contained value + \row \li Map \li Object \li Keys are converted to string; values converted + according to this table + \row \li Tags and extended types \li Special \li The tag number itself is lost and the tagged + value is converted to JSON + \endtable + + For information on the conversion of CBOR map keys to string, see + QCborMap::toJsonObject(). + + If this QCborValue contains the undefined value, this function will return + an undefined QJsonValue too. Note that JSON does not support undefined + values and undefined QJsonValues are an extension to the specification. + They cannot be held in a QJsonArray or QJsonObject, but can be returned + from functions to indicate a failure. For all other intents and purposes, + they are the same as null. + + \section3 Special handling of tags and extended types + + Some tags are handled specially and change the transformation of the tagged + value from CBOR to JSON. The following table lists those special cases: + + \table + \header \li Tag \li CBOR type \li Transformation + \row \li ExpectedBase64url \li Byte array \li Encodes the byte array as Base64url + \row \li ExpectedBase64 \li Byte array \li Encodes the byte array as Base64 + \row \li ExpectedBase16 \li Byte array \li Encodes the byte array as hex + \row \li Url \li Url and String \li Uses QUrl::toEncoded() to normalize the + encoding to the URL's fully encoded format + \row \li Uuid \li Uuid and Byte array \li Uses QUuid::toString() to create + the string representation + \endtable + + \sa fromJsonValue(), toVariant(), QCborArray::toJsonArray(), QCborMap::toJsonObject() + */ +QJsonValue QCborValue::toJsonValue() const +{ + if (container) + return convertToJson(container, n < 0 ? -type() : n); + + // simple values + switch (type()) { + case Integer: + return n; + + case Null: + return QJsonValue(); + + case False: + return false; + + case True: + return true; + + case Double: + return fpToJson(fp_helper()); + + case SimpleType: + break; + + case Undefined: + case Invalid: + return QJsonValue(QJsonValue::Undefined); + + case ByteArray: + case String: + // empty strings + return QString(); + + case Array: + // empty array + return QJsonArray(); + + case Map: + // empty map + return QJsonObject(); + + case Tag: + case DateTime: + case Url: + case RegularExpression: + case Uuid: + Q_UNREACHABLE(); + return QJsonValue::Undefined; + } + + return simpleTypeString(type()); +} + +QJsonValue QCborValueRef::toJsonValue() const +{ + return convertToJson(d, i); +} + +/*! + Recursively converts every \l QCborValue element in this array to JSON + using QCborValue::toJsonValue() and returns the corresponding QJsonArray + composed of those elements. + + Please note that CBOR contains a richer and wider type set than JSON, so + some information may be lost in this conversion. For more details on what + conversions are applied, see QCborValue::toJsonValue(). + + \sa fromJsonArray(), QCborValue::toJsonValue(), QCborMap::toJsonObject(), toVariantList() + */ +QJsonArray QCborArray::toJsonArray() const +{ + return convertToJsonArray(d.data()); +} + +/*! + Recursively converts every \l QCborValue value in this array to JSON using + QCborValue::toJsonValue() and creates a string key for all keys that aren't + strings, then returns the corresponding QJsonObject composed of those + associations. + + Please note that CBOR contains a richer and wider type set than JSON, so + some information may be lost in this conversion. For more details on what + conversions are applied, see QCborValue::toJsonValue(). + + \section3 Map key conversion to string + + JSON objects are defined as having string keys, unlike CBOR, so the + conversion of a QCborMap to QJsonObject will imply a step of + "stringification" of the key values. The conversion will use the special + handling of tags and extended types from above and will also convert the + rest of the types as follows: + + \table + \header \li Type \li Transformation + \row \li Bool \li "true" and "false" + \row \li Null \li "null" + \row \li Undefined \li "undefined" + \row \li Integer \li The decimal string form of the number + \row \li Double \li The decimal string form of the number + \row \li Byte array \li Unless tagged differently (see above), encoded as + Base64url + \row \li Array \li Replaced by the compact form of its + \l{QCborValue::toDiagnosticNotation()}{Diagnostic notation} + \row \li Map \li Replaced by the compact form of its + \l{QCborValue::toDiagnosticNotation()}{Diagnostic notation} + \row \li Tags and extended types \li Tag number is dropped and the tagged value is converted + to string + \endtable + + \sa fromJsonObject(), QCborValue::toJsonValue(), QCborArray::toJsonArray(), toVariantMap() + */ +QJsonObject QCborMap::toJsonObject() const +{ + return convertToJsonObject(d.data()); +} + +/*! + Converts this value to a native Qt type and returns the corresponding QVariant. + + The following table lists the mapping performed between \l{Type}{QCborValue + types} and \l{QMetaType::Type}{Qt meta types}. + + \table + \header \li CBOR Type \li Qt or C++ type \li Notes + \row \li Integer \li \l qint64 \li + \row \li Double \li \c double \li + \row \li Bool \li \c bool \li + \row \li Null \li \c std::nullptr_t \li + \row \li Undefined \li no type (QVariant()) \li + \row \li Byte array \li \l QByteArray \li + \row \li String \li \l QString \li + \row \li Array \li \l QVariantList \li Recursively converts all values + \row \li Map \li \l QVariantMap \li Key types are "stringified" + \row \li Other simple types \li \l QCborSimpleType \li + \row \li DateTime \li \l QDateTime \li + \row \li Url \li \l QUrl \li + \row \li RegularExpression \li \l QRegularExpression \li + \row \li Uuid \li \l QUuid \li + \row \li Other tags \li Special \li The tag is ignored and the tagged + value is converted using this + function + \endtable + + Note that values in both CBOR Maps and Arrays are converted recursively + using this function too and placed in QVariantMap and QVariantList instead. + You will not find QCborMap and QCborArray stored inside the QVariants. + + QVariantMaps have string keys, unlike CBOR, so the conversion of a QCborMap + to QVariantMap will imply a step of "stringification" of the key values. + See QCborMap::toJsonObject() for details. + + \sa fromVariant(), toJsonValue(), QCborArray::toVariantList(), QCborMap::toVariantMap() + */ +QVariant QCborValue::toVariant() const +{ + switch (type()) { + case Integer: + return toInteger(); + + case Double: + return toDouble(); + + case SimpleType: + break; + + case False: + case True: + return isTrue(); + + case Null: + return QVariant::fromValue(nullptr); + + case Undefined: + return QVariant(); + + case ByteArray: + return toByteArray(); + + case String: + return toString(); + + case Array: + return toArray().toVariantList(); + + case Map: + return toMap().toVariantMap(); + + case Tag: + // ignore tags + return taggedValue().toVariant(); + + case DateTime: + return toDateTime(); + + case Url: + return toUrl(); + + case RegularExpression: + return toRegularExpression(); + + case Uuid: + return toUuid(); + + case Invalid: + return QVariant(); + } + + if (isSimpleType()) + return QVariant::fromValue(toSimpleType()); + + Q_UNREACHABLE(); + return QVariant(); +} + +/*! + Converts the JSON value contained in \a v into its corresponding CBOR value + and returns it. There is no data loss in converting from JSON to CBOR, as + the CBOR type set is richer than JSON's. Additionally, values converted to + CBOR using this function can be converted back to JSON using toJsonValue() + with no data loss. + + The following table lists the mapping of JSON types to CBOR types: + + \table + \header \li JSON Type \li CBOR Type + \row \li Bool \li Bool + \row \li Number \li Integer (if the number has no fraction and is in the \l qint64 + range) or Double + \row \li String \li String + \row \li Array \li Array + \row \li Object \li Map + \row \li Null \li Null + \endtable + + \l QJsonValue can also be undefined, indicating a previous operation that + failed to complete (for example, searching for a key not present in an + object). Undefined values are not JSON types and may not appear in JSON + arrays and objects, but this function does return the QCborValue undefined + value if the corresponding QJsonValue is undefined. + + \sa toJsonValue(), fromVariant(), QCborArray::fromJsonArray(), QCborMap::fromJsonObject() + */ +QCborValue QCborValue::fromJsonValue(const QJsonValue &v) +{ + switch (v.type()) { + case QJsonValue::Bool: + return v.b; + case QJsonValue::Double: + if (v.dbl == qint64(v.dbl)) + return qint64(v.dbl); + return v.dbl; + case QJsonValue::String: + return v.toString(); + case QJsonValue::Array: + return QCborArray::fromJsonArray(v.toArray()); + case QJsonValue::Object: + return QCborMap::fromJsonObject(v.toObject()); + case QJsonValue::Null: + return nullptr; + case QJsonValue::Undefined: + break; + } + return QCborValue(); +} + +static void appendVariant(QCborContainerPrivate *d, const QVariant &variant) +{ + // Handle strings and byte arrays directly, to avoid creating a temporary + // dummy container to hold their data. + int type = variant.userType(); + if (type == QVariant::String) { + d->append(variant.toString()); + } else if (type == QVariant::ByteArray) { + QByteArray ba = variant.toByteArray(); + d->appendByteData(ba.constData(), ba.size(), QCborValue::ByteArray); + } else { + // For everything else, use the function below. + d->append(QCborValue::fromVariant(variant)); + } +} + +/*! + Converts the QVariant \a variant into QCborValue and returns it. + + QVariants may contain a large list of different meta types, many of which + have no corresponding representation in CBOR. That includes all + user-defined meta types. When preparing transmission using CBOR, it is + suggested to encode carefully each value to prevent loss of representation. + + The following table lists the conversion this function will apply: + + \table + \header \li Qt (C++) type \li CBOR type + \row \li invalid (QVariant()) \li Undefined + \row \li \c bool \li Bool + \row \li \c std::nullptr_t \li Null + \row \li \c short, \c ushort, \c int, \c uint, \l qint64 \li Integer + \row \li \l quint64 \li Integer, but they are cast to \c qint64 first so + values higher than 2\sup{63}-1 (\c INT64_MAX) will + be wrapped to negative + \row \li \c float, \c double \li Double + \row \li \l QByteArray \li ByteArray + \row \li \l QDateTime \li DateTime + \row \li \l QCborSimpleType \li Simple type + \row \li \l QJsonArray \li Array, converted using QCborArray::formJsonArray() + \row \li \l QJsonDocument \li Array or Map + \row \li \l QJsonObject \li Map, converted using QCborMap::fromJsonObject() + \row \li \l QJsonValue \li converted using fromJsonValue() + \row \li \l QRegularExpression \li RegularExpression + \row \li \l QString \li String + \row \li \l QStringList \li Array + \row \li \l QVariantHash \li Map + \row \li \l QVariantList \li Array + \row \li \l QVariantMap \li Map + \row \li \l QUrl \li Url + \row \li \l QUuid \li Uuid + \endtable + + For any other types, this function will return Null if the QVariant itself + is null, and otherwise will try to convert to string using + QVariant::toString(). If the conversion to string fails, this function + returns Undefined. + + Please note that the conversions via QVariant::toString() are subject to + change at any time. QCborValue may be extended in the future to support + more types, which will result in a change in how this function performs + conversions. + + \sa toVariant(), fromJsonValue(), QCborArray::toVariantList(), QCborMap::toVariantMap() + */ +QCborValue QCborValue::fromVariant(const QVariant &variant) +{ + switch (variant.userType()) { + case QVariant::Invalid: + return {}; + case QMetaType::Nullptr: + return nullptr; + case QVariant::Bool: + return variant.toBool(); + case QMetaType::Short: + case QMetaType::UShort: + case QVariant::Int: + case QVariant::LongLong: + case QVariant::ULongLong: + case QVariant::UInt: + return variant.toLongLong(); + case QMetaType::Float: + case QVariant::Double: + return variant.toDouble(); + case QVariant::String: + return variant.toString(); + case QVariant::StringList: + return QCborArray::fromStringList(variant.toStringList()); + case QVariant::ByteArray: + return variant.toByteArray(); + case QVariant::DateTime: + return QCborValue(variant.toDateTime()); + case QVariant::Url: + return QCborValue(variant.toUrl()); + case QVariant::Uuid: + return QCborValue(variant.toUuid()); + case QVariant::List: + return QCborArray::fromVariantList(variant.toList()); + case QVariant::Map: + return QCborMap::fromVariantMap(variant.toMap()); + case QVariant::Hash: + return QCborMap::fromVariantHash(variant.toHash()); +#ifndef QT_BOOTSTRAPPED + case QVariant::RegularExpression: + return QCborValue(variant.toRegularExpression()); + case QMetaType::QJsonValue: + return fromJsonValue(variant.toJsonValue()); + case QMetaType::QJsonObject: + return QCborMap::fromJsonObject(variant.toJsonObject()); + case QMetaType::QJsonArray: + return QCborArray::fromJsonArray(variant.toJsonArray()); + case QMetaType::QJsonDocument: { + QJsonDocument doc = variant.toJsonDocument(); + if (doc.isArray()) + return QCborArray::fromJsonArray(doc.array()); + return QCborMap::fromJsonObject(doc.object()); + } + case QMetaType::QCborValue: + return variant.value<QCborValue>(); + case QMetaType::QCborArray: + return variant.value<QCborArray>(); + case QMetaType::QCborMap: + return variant.value<QCborMap>(); + case QMetaType::QCborSimpleType: + return variant.value<QCborSimpleType>(); +#endif + default: + break; + } + + if (variant.isNull()) + return QCborValue(nullptr); + + QString string = variant.toString(); + if (string.isNull()) + return QCborValue(); // undefined + return string; +} + +/*! + Recursively converts each \l QCborValue in this array using + QCborValue::toVariant() and returns the QVariantList composed of the + converted items. + + Conversion to \l QVariant is not completely lossless. Please see the + documentation in QCborValue::toVariant() for more information. + + \sa fromVariantList(), fromStringList(), toJsonArray(), + QCborValue::toVariant(), QCborMap::toVariantMap() + */ +QVariantList QCborArray::toVariantList() const +{ + QVariantList retval; + retval.reserve(size()); + for (qsizetype i = 0; i < size(); ++i) + retval.append(d->valueAt(i).toVariant()); + return retval; +} + +/*! + Returns a QCborArray containing all the strings found in the \a list list. + + \sa fromVariantList(), fromJsonArray() + */ +QCborArray QCborArray::fromStringList(const QStringList &list) +{ + QCborArray a; + a.detach(list.size()); + for (const QString &s : list) + a.d->append(s); + return a; +} + +/*! + Converts all the items in the \a list to CBOR using + QCborValue::fromVariant() and returns the array composed of those elements. + + Conversion from \l QVariant is not completely lossless. Please see the + documentation in QCborValue::fromVariant() for more information. + + \sa toVariantList(), fromStringList(), fromJsonArray(), QCborMap::fromVariantMap() + */ +QCborArray QCborArray::fromVariantList(const QVariantList &list) +{ + QCborArray a; + a.detach(list.size()); + for (const QVariant &v : list) + appendVariant(a.d.data(), v); + return a; +} + +/*! + Converts all JSON items found in the \a array array to CBOR using + QCborValue::fromJson(), and returns the CBOR array composed of those + elements. + + This conversion is lossless, as the CBOR type system is a superset of + JSON's. Moreover, the array returned by this function can be converted back + to the original \a array by using toJsonArray(). + + \sa toJsonArray(), toVariantList(), QCborValue::fromJsonValue(), QCborMap::fromJsonObject() + */ +QCborArray QCborArray::fromJsonArray(const QJsonArray &array) +{ + QCborArray a; + a.detach(array.size()); + for (const QJsonValue v : array) { + if (v.isString()) + a.d->append(v.toString()); + else + a.d->append(QCborValue::fromJsonValue(v)); + } + return a; +} + +/*! + Converts the CBOR values to QVariant using QCborValue::toVariant() and + "stringifies" all the CBOR keys in this map, returning the QVariantMap that + results from that association list. + + QVariantMaps have string keys, unlike CBOR, so the conversion of a QCborMap + to QVariantMap will imply a step of "stringification" of the key values. + See QCborMap::toJsonObject() for details. + + In addition, the conversion to \l QVariant is not completely lossless. + Please see the documentation in QCborValue::toVariant() for more + information. + + \sa fromVariantMap(), toVariantHash(), toJsonObject(), QCborValue::toVariant(), + QCborArray::toVariantList() + */ +QVariantMap QCborMap::toVariantMap() const +{ + QVariantMap retval; + for (qsizetype i = 0; i < 2 * size(); i += 2) + retval.insert(makeString(d.data(), i), d->valueAt(i + 1).toVariant()); + return retval; +} + +/*! + Converts the CBOR values to QVariant using QCborValue::toVariant() and + "stringifies" all the CBOR keys in this map, returning the QVariantHash that + results from that association list. + + QVariantMaps have string keys, unlike CBOR, so the conversion of a QCborMap + to QVariantMap will imply a step of "stringification" of the key values. + See QCborMap::toJsonObject() for details. + + In addition, the conversion to \l QVariant is not completely lossless. + Please see the documentation in QCborValue::toVariant() for more + information. + + \sa fromVariantHash(), toVariantMap(), toJsonObject(), QCborValue::toVariant(), + QCborArray::toVariantList() + */ +QVariantHash QCborMap::toVariantHash() const +{ + QVariantHash retval; + retval.reserve(size()); + for (qsizetype i = 0; i < 2 * size(); i += 2) + retval.insert(makeString(d.data(), i), d->valueAt(i + 1).toVariant()); + return retval; +} + +/*! + Converts all the items in \a map to CBOR using QCborValue::fromVariant() + and returns the map composed of those elements. + + Conversion from \l QVariant is not completely lossless. Please see the + documentation in QCborValue::fromVariant() for more information. + + \sa toVariantMap(), fromVariantHash(), fromJsonObject(), QCborValue::fromVariant() + */ +QCborMap QCborMap::fromVariantMap(const QVariantMap &map) +{ + QCborMap m; + m.detach(map.size()); + QCborContainerPrivate *d = m.d.data(); + + auto it = map.begin(); + auto end = map.end(); + for ( ; it != end; ++it) { + d->append(it.key()); + appendVariant(d, it.value()); + } + return m; +} + +/*! + Converts all the items in \a hash to CBOR using QCborValue::fromVariant() + and returns the map composed of those elements. + + Conversion from \l QVariant is not completely lossless. Please see the + documentation in QCborValue::fromVariant() for more information. + + \sa toVariantHash(), fromVariantMap(), fromJsonObject(), QCborValue::fromVariant() + */ +QCborMap QCborMap::fromVariantHash(const QVariantHash &hash) +{ + QCborMap m; + m.detach(hash.size()); + QCborContainerPrivate *d = m.d.data(); + + auto it = hash.begin(); + auto end = hash.end(); + for ( ; it != end; ++it) { + d->append(it.key()); + appendVariant(d, it.value()); + } + return m; +} + +/*! + Converts all JSON items found in the \a obj object to CBOR using + QCborValue::fromJson(), and returns the map composed of those elements. + + This conversion is lossless, as the CBOR type system is a superset of + JSON's. Moreover, the map returned by this function can be converted back + to the original \a obj by using toJsonObject(). + + \sa toJsonObject(), toVariantMap(), QCborValue::fromJsonValue(), QCborArray::fromJsonArray() + */ +QCborMap QCborMap::fromJsonObject(const QJsonObject &obj) +{ + QCborMap m; + m.detach(obj.size()); + QCborContainerPrivate *d = m.d.data(); + + auto it = obj.begin(); + auto end = obj.end(); + for ( ; it != end; ++it) { + d->append(it.key()); + if (it.value().isString()) + d->append(it.value().toString()); + else + d->append(QCborValue::fromJsonValue(it.value())); + } + return m; +} + +QT_END_NAMESPACE diff --git a/src/corelib/serialization/qjsonvalue.cpp b/src/corelib/serialization/qjsonvalue.cpp index aa44305fce..4469302e31 100644 --- a/src/corelib/serialization/qjsonvalue.cpp +++ b/src/corelib/serialization/qjsonvalue.cpp @@ -46,6 +46,11 @@ #include <qstringlist.h> #include <qdebug.h> +#ifndef QT_BOOTSTRAPPED +# include <qcborarray.h> +# include <qcbormap.h> +#endif + #include "qjson_p.h" QT_BEGIN_NAMESPACE @@ -423,6 +428,25 @@ QJsonValue &QJsonValue::operator =(const QJsonValue &other) \li QMetaType::QUuid \endlist \li QJsonValue::String. Since Qt 5.11, the resulting string will not include braces + \row + \li + \list + \li QMetaType::QCborValue + \endlist + \li Whichever type QCborValue::toJsonValue() returns. + \row + \li + \list + \li QMetaType::QCborArray + \endlist + \li QJsonValue::Array. See QCborValue::toJsonValue() for conversion restrictions. + \row + \li + \list + \li QMetaType::QCborMap + \endlist + \li QJsonValue::Map. See QCborValue::toJsonValue() for conversion restrictions and the + "stringification" of map keys. \endtable For all other QVariant types a conversion to a QString will be attempted. If the returned string @@ -469,6 +493,12 @@ QJsonValue QJsonValue::fromVariant(const QVariant &variant) QJsonDocument doc = variant.toJsonDocument(); return doc.isArray() ? QJsonValue(doc.array()) : QJsonValue(doc.object()); } + case QMetaType::QCborValue: + return variant.value<QCborValue>().toJsonValue(); + case QMetaType::QCborArray: + return variant.value<QCborArray>().toJsonArray(); + case QMetaType::QCborMap: + return variant.value<QCborMap>().toJsonObject(); #endif default: break; diff --git a/src/corelib/serialization/qjsonvalue.h b/src/corelib/serialization/qjsonvalue.h index 96538ebbf9..316d3fdf45 100644 --- a/src/corelib/serialization/qjsonvalue.h +++ b/src/corelib/serialization/qjsonvalue.h @@ -150,6 +150,7 @@ private: friend class QJsonPrivate::Value; friend class QJsonArray; friend class QJsonObject; + friend class QCborValue; friend Q_CORE_EXPORT QDebug operator<<(QDebug, const QJsonValue &); QJsonValue(QJsonPrivate::Data *d, QJsonPrivate::Base *b, const QJsonPrivate::Value& v); diff --git a/src/corelib/serialization/qtextstream.cpp b/src/corelib/serialization/qtextstream.cpp index ee3cb4efcb..05a5a55926 100644 --- a/src/corelib/serialization/qtextstream.cpp +++ b/src/corelib/serialization/qtextstream.cpp @@ -2595,6 +2595,21 @@ QTextStream &QTextStream::operator<<(const QString &string) Writes \a string to the stream, and returns a reference to the QTextStream. + \since 5.12 +*/ +QTextStream &QTextStream::operator<<(QStringView string) +{ + Q_D(QTextStream); + CHECK_VALID_STREAM(*this); + d->putString(string.cbegin(), int(string.size())); + return *this; +} + +/*! + \overload + + Writes \a string to the stream, and returns a reference to the + QTextStream. */ QTextStream &QTextStream::operator<<(QLatin1String string) { diff --git a/src/corelib/serialization/qtextstream.h b/src/corelib/serialization/qtextstream.h index ee0b09419d..ee90d01779 100644 --- a/src/corelib/serialization/qtextstream.h +++ b/src/corelib/serialization/qtextstream.h @@ -184,6 +184,7 @@ public: QTextStream &operator<<(float f); QTextStream &operator<<(double f); QTextStream &operator<<(const QString &s); + QTextStream &operator<<(QStringView s); QTextStream &operator<<(QLatin1String s); QTextStream &operator<<(const QStringRef &s); QTextStream &operator<<(const QByteArray &array); diff --git a/src/corelib/serialization/serialization.pri b/src/corelib/serialization/serialization.pri index 3d039dc30f..4f2dc64e4f 100644 --- a/src/corelib/serialization/serialization.pri +++ b/src/corelib/serialization/serialization.pri @@ -1,6 +1,12 @@ # Qt data formats core module HEADERS += \ + serialization/qcborarray.h \ + serialization/qcborcommon.h \ + serialization/qcbormap.h \ + serialization/qcborvalue.h \ + serialization/qcborvalue_p.h \ + serialization/qcborstream.h \ serialization/qdatastream.h \ serialization/qdatastream_p.h \ serialization/qjson_p.h \ @@ -17,8 +23,12 @@ HEADERS += \ serialization/qxmlutils_p.h SOURCES += \ + serialization/qcborstream.cpp \ + serialization/qcbordiagnostic.cpp \ + serialization/qcborvalue.cpp \ serialization/qdatastream.cpp \ serialization/qjson.cpp \ + serialization/qjsoncbor.cpp \ serialization/qjsondocument.cpp \ serialization/qjsonobject.cpp \ serialization/qjsonarray.cpp \ @@ -28,3 +38,9 @@ SOURCES += \ serialization/qtextstream.cpp \ serialization/qxmlstream.cpp \ serialization/qxmlutils.cpp + +false: SOURCES += \ + serialization/qcborarray.cpp \ + serialization/qcbormap.cpp + +INCLUDEPATH += ../3rdparty/tinycbor/src |