Move the scalar protobuf type serializer to a seprate TU

Move the scalar type serializers to a separated translation units. This
make code more readable. Adjust constraints to follow common project
coding standards and make constrains compilation faster. Make complex
functions out-of-line to avoid unwanted binary expansion.

Pick-to: 6.8 6.9
Task-number: QTBUG-128812
Change-Id: Ib5311f392e32e5f31416448d2f306ae91155f873
Reviewed-by: Dennis Oberst <dennis.oberst@qt.io>
This commit is contained in:
Alexey Edelev 2024-09-29 19:10:55 +02:00
parent 3df9d285da
commit 6a1012792c
7 changed files with 754 additions and 693 deletions

View File

@ -4,6 +4,8 @@
qt_internal_add_module(Protobuf
SOURCES
protobuffieldpresencechecker_p.h
protobufscalarserializers_p.h protobufscalarserializers_p.cpp
protobufscalarjsonserializers_p.h
qtprotobufglobal.h
qabstractprotobufserializer.cpp qabstractprotobufserializer.h
qprotobufdeserializerbase_p.h qprotobufdeserializerbase.cpp

View File

@ -0,0 +1,281 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef PROTOBUFSCALARJSONSERIALIZERS_P_H
#define PROTOBUFSCALARJSONSERIALIZERS_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QtProtobuf/qtprotobuftypes.h>
#include <QtCore/private/qnumeric_p.h>
#include <QtCore/qjsonarray.h>
#include <QtCore/qjsonvalue.h>
#include <QtCore/qtconfigmacros.h>
#include <limits>
QT_BEGIN_NAMESPACE
namespace ProtobufScalarJsonSerializers {
// Tests if V is JSON compatible integer
// int32 | int64 | uint32 | sint32 | sint64 | fixed32 | sfixed32 | sfixed64
template <typename T>
constexpr inline bool IsJsonInt = false;
template <>
constexpr inline bool IsJsonInt<QtProtobuf::int32> = true;
template <>
constexpr inline bool IsJsonInt<QtProtobuf::int64> = true;
template <>
constexpr inline bool IsJsonInt<QtProtobuf::uint32> = true;
template <>
constexpr inline bool IsJsonInt<QtProtobuf::sint32> = true;
template <>
constexpr inline bool IsJsonInt<QtProtobuf::sint64> = true;
template <>
constexpr inline bool IsJsonInt<QtProtobuf::fixed32> = true;
template <>
constexpr inline bool IsJsonInt<QtProtobuf::sfixed32> = true;
template <>
constexpr inline bool IsJsonInt<QtProtobuf::sfixed64> = true;
template <typename T>
using if_json_int = std::enable_if_t<IsJsonInt<T>, bool>;
// Tests if V is JSON incompatible 64-bit unsigned integer
// uint64 | fixed64
template <typename T>
constexpr inline bool IsJsonInt64 = false;
template <>
constexpr inline bool IsJsonInt64<QtProtobuf::uint64> = true;
template <>
constexpr inline bool IsJsonInt64<QtProtobuf::fixed64> = true;
template <typename T>
using if_json_int64 = std::enable_if_t<IsJsonInt64<T>, bool>;
// Tests if V is JSON compatible floating point number
// float | double
template <typename T>
constexpr inline bool IsJsonFloatingPoint = false;
template <>
constexpr inline bool IsJsonFloatingPoint<float> = true;
template <>
constexpr inline bool IsJsonFloatingPoint<double> = true;
template <typename T>
using if_json_floating_point = std::enable_if_t<IsJsonFloatingPoint<T>, bool>;
template <typename T, if_json_int<T> = true>
QJsonValue serialize(T propertyValue)
{
return QJsonValue(qint64(propertyValue));
}
template <typename T, if_json_int64<T> = true>
QJsonValue serialize(T propertyValue)
{
return QJsonValue(QString::number(propertyValue));
}
template <typename T, if_json_floating_point<T> = true>
QJsonValue serialize(T propertyValue)
{
if (propertyValue == -std::numeric_limits<T>::infinity())
return QJsonValue(QLatin1String("-Infinity"));
if (propertyValue == std::numeric_limits<T>::infinity())
return QJsonValue(QLatin1String("Infinity"));
if (propertyValue != propertyValue)
return QJsonValue(QLatin1String("NaN"));
return QJsonValue(propertyValue);
}
inline QJsonValue serialize(bool propertyValue)
{
return QJsonValue(propertyValue);
}
inline QJsonValue serialize(const QString &propertyValue)
{
return QJsonValue(propertyValue);
}
inline QJsonValue serialize(const QByteArray &propertyValue)
{
return QJsonValue(QString::fromUtf8(propertyValue.toBase64()));
}
template <typename L>
QJsonValue serializeList(const QVariant &propertyValue)
{
QJsonArray arr;
L listValue = propertyValue.value<L>();
for (const auto &value : listValue)
arr.append(serialize(value));
return QJsonValue(arr);
}
template <typename T>
QJsonValue serializeCommon(const QVariant &propertyValue)
{
return serialize(propertyValue.value<T>());
}
template <typename T, if_json_int<T> = true>
T deserialize(const QJsonValue &value, bool &ok)
{
auto variantValue = value.toVariant();
qint64 raw = 0;
switch (variantValue.metaType().id()) {
case QMetaType::QString: // TODO: check if string has prepending/appending whitespaces.
case QMetaType::LongLong:
raw = variantValue.toLongLong(&ok);
break;
case QMetaType::Double: {
double d = value.toDouble();
ok = convertDoubleTo(d, &raw) && double(raw) == d;
} break;
default:
break;
}
// For types that "smaller" than qint64 we need to check if the value fits its limits range
if constexpr (sizeof(T) != sizeof(qint64)) {
if (ok) {
if constexpr (std::is_same_v<T, QtProtobuf::sfixed32>
|| std::is_same_v<T, QtProtobuf::int32>) {
using limits = std::numeric_limits<qint32>;
ok = raw >= limits::min() && raw <= limits::max();
} else if constexpr (std::is_same_v<T, QtProtobuf::fixed32>) {
using limits = std::numeric_limits<quint32>;
ok = raw >= limits::min() && raw <= limits::max();
} else {
using limits = std::numeric_limits<T>;
ok = raw >= limits::min() && raw <= limits::max();
}
}
}
return T(raw);
}
template <typename T, if_json_int64<T> = true>
T deserialize(const QJsonValue &value, bool &ok)
{
quint64 raw = 0;
auto variantValue = value.toVariant();
switch (variantValue.metaType().id()) {
case QMetaType::QString:
case QMetaType::LongLong:
// Here we attempt converting the value to ULongLong
raw = variantValue.toULongLong(&ok);
break;
case QMetaType::Double: {
double d = value.toDouble();
ok = convertDoubleTo(d, &raw) && double(raw) == d;
} break;
default:
break;
}
return T(raw);
}
template <typename T, if_json_floating_point<T> = true>
T deserialize(const QJsonValue &value, bool &ok)
{
ok = true;
QByteArray data = value.toVariant().toByteArray();
if (data.toLower() == QByteArray("-infinity"))
return -std::numeric_limits<T>::infinity();
if (data.toLower() == QByteArray("infinity"))
return std::numeric_limits<T>::infinity();
if (data.toLower() == QByteArray("nan"))
return T(NAN);
if constexpr (std::is_same_v<T, float>)
return data.toFloat(&ok);
else
return data.toDouble(&ok);
}
template <typename T, std::enable_if_t<std::is_same_v<T, bool>, bool> = true>
bool deserialize(const QJsonValue &value, bool &ok)
{
if (value.isBool()) {
ok = true;
return value.toBool();
} else if (value.isString()) {
if (value.toString() == QLatin1String("true")) {
ok = true;
return true;
} else if (value.toString() == QLatin1String("false")) {
ok = true;
return false;
}
}
return false;
}
template <typename T, std::enable_if_t<std::is_same_v<T, QString>, bool> = true>
QString deserialize(const QJsonValue &value, bool &ok)
{
ok = value.isString();
return value.toString();
}
template <typename T, std::enable_if_t<std::is_same_v<T, QByteArray>, bool> = true>
QByteArray deserialize(const QJsonValue &value, bool &ok)
{
QByteArray data = value.toVariant().toByteArray();
if (value.isString()) {
ok = true;
return QByteArray::fromBase64(data);
}
return {};
}
template <typename L /*list*/, typename T /*element*/>
QVariant deserializeList(const QJsonValue &value, bool &ok)
{
if (!value.isArray()) {
ok = false;
return {};
}
L list;
QJsonArray array = value.toArray();
for (auto arrayValue : array) {
ok = false;
T value = deserialize<T>(arrayValue, ok);
if (!ok)
break;
list.append(value);
}
return QVariant::fromValue(list);
}
template <typename T>
QVariant deserializeCommon(const QJsonValue &value, bool &ok)
{
ok = false;
return QVariant::fromValue<T>(deserialize<T>(value, ok));
}
} // namespace ProtobufScalarJsonSerializers
QT_END_NAMESPACE
#endif // PROTOBUFSCALARJSONSERIALIZERS_P_H

View File

@ -0,0 +1,35 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include <QtProtobuf/private/protobufscalarserializers_p.h>
QT_BEGIN_NAMESPACE
/*
Gets length of a byte-array and prepends to it its serialized length value
using the appropriate serialization algorithm
Returns 'data' with its length prepended
*/
QByteArray ProtobufScalarSerializers::prependLengthDelimitedSize(const QByteArray &data)
{
return serializeVarintCommon<uint32_t>(data.size()) + data;
}
std::optional<QByteArray>
ProtobufScalarSerializers::deserializeLengthDelimited(QProtobufSelfcheckIterator &it)
{
if (it.bytesLeft() != 0) {
if (auto opt = deserializeVarintCommon<QtProtobuf::uint64>(it); opt) {
if (quint64 length = opt.value(); it.isValid() && quint64(it.bytesLeft()) >= length
&& length <= quint64(QByteArray::maxSize())) {
QByteArray result(it.data(), qsizetype(length));
it += length;
return { std::move(result) };
}
}
}
return std::nullopt;
}
QT_END_NAMESPACE

View File

@ -0,0 +1,397 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef PROTOBUFSCALARSERIALIZERS_P_H
#define PROTOBUFSCALARSERIALIZERS_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QtProtobuf/private/qprotobufselfcheckiterator_p.h>
#include <QtProtobuf/qtprotobuftypes.h>
#include <QtCore/qtconfigmacros.h>
#include <type_traits>
QT_BEGIN_NAMESPACE
namespace ProtobufScalarSerializers {
// The below type trait structures help to determine the required encoding method for protobuf
// types.
// See https://protobuf.dev/programming-guides/encoding for details.
// Tests if V is a Varint-compatible unsigned integer type.
// uint32 | uint64
template <typename V>
using is_unsigned_int = std::conjunction<std::is_integral<V>, std::is_unsigned<V>>;
template <typename V>
using if_unsigned_int = std::enable_if_t<is_unsigned_int<V>::value, bool>;
// Tests if V is a Varint-compatible signed integer type.
// int32 | int64
template <typename V>
constexpr inline bool IsSignedInt = false;
template <>
constexpr inline bool IsSignedInt<QtProtobuf::int32> = true;
template <>
constexpr inline bool IsSignedInt<QtProtobuf::int64> = true;
template <typename V>
using is_signed_int = std::integral_constant<bool, IsSignedInt<V>>;
template <typename V>
using if_signed_int = std::enable_if_t<is_signed_int<V>::value, bool>;
// Tests if V is a Varint-compatible signed integer type, with ZigZag encoding support.
// sint32 | sint64
template <typename V>
using is_zigzag_int = std::conjunction<std::is_integral<V>, std::is_signed<V>>;
template <typename V>
using if_zigzag_int = std::enable_if_t<is_zigzag_int<V>::value, bool>;
// Tests if V is a Varint-compatible integer type.
// int32 | int64 | uint32 | uint64 | sint32 | sint64
template <typename V>
using is_int = std::disjunction<std::is_integral<V>, is_signed_int<V>>;
// Tests if V is a 32-bit integer without the need for encoding.
// sfixed32 | fixed32
template <typename V>
constexpr inline bool IsFixed32Int = false;
template <>
constexpr inline bool IsFixed32Int<QtProtobuf::fixed32> = true;
template <>
constexpr inline bool IsFixed32Int<QtProtobuf::sfixed32> = true;
template <typename V>
using is_fixed32_int = std::integral_constant<bool, IsFixed32Int<V>>;
// Tests if V is a 64-bit integer without the need for encoding.
// sfixed64 | fixed64
template <typename V>
constexpr inline bool IsFixed64Int = false;
template <>
constexpr inline bool IsFixed64Int<QtProtobuf::fixed64> = true;
template <>
constexpr inline bool IsFixed64Int<QtProtobuf::sfixed64> = true;
template <typename V>
using is_fixed64_int = std::integral_constant<bool, IsFixed64Int<V>>;
// Tests if V is a 32-bit type without the need for encoding.
// sfixed32 | fixed32 | float
template <typename V>
using is_i32 = std::disjunction<is_fixed32_int<V>, std::is_same<V, float>>;
// Tests if V is a 64-bit type without the need for encoding.
// sfixed64 | fixed64 | double
template <typename V>
using is_i64 = std::disjunction<is_fixed64_int<V>, std::is_same<V, double>>;
// Tests if V is a type without the need for encoding.
// sfixed32 | fixed32 | float | sfixed64 | fixed64 | double
template <typename V>
using is_i32_or_i64 = std::disjunction<is_i32<V>, is_i64<V>>;
template <typename V>
using if_i32_or_i64 = std::enable_if_t<is_i32_or_i64<V>::value, bool>;
// Tests if V is the length-delimited non-message, non-list type.
// QString | QByteArray
template <typename V>
constexpr inline bool IsLengthDelimited = false;
template <>
constexpr inline bool IsLengthDelimited<QByteArray> = true;
template <>
constexpr inline bool IsLengthDelimited<QString> = true;
template <typename V>
using is_length_delimited = std::integral_constant<bool, IsLengthDelimited<V>>;
template <typename V>
using if_length_delimited = std::enable_if_t<is_length_delimited<V>::value, bool>;
template <typename V>
using if_not_length_delimited = std::enable_if_t<std::negation_v<is_length_delimited<V>>, bool>;
// Test if V can be stored in non-packed repeated field
template <typename V>
using is_non_packed_compatible = std::disjunction<is_int<V>, is_i32_or_i64<V>,
is_length_delimited<V>>;
template <typename V>
using if_non_packed_compatible = std::enable_if_t<is_non_packed_compatible<V>::value, bool>;
[[nodiscard]] QByteArray prependLengthDelimitedSize(const QByteArray &data);
[[nodiscard]] std::optional<QByteArray> deserializeLengthDelimited(QProtobufSelfcheckIterator &it);
// ###########################################################################
// Serializers
// ###########################################################################
template <typename V, if_unsigned_int<V> = true>
[[nodiscard]] QByteArray serializeVarintCommon(const V &value)
{
if (value == 0)
return QByteArray(1, char(0));
quint64 varint = value;
QByteArray result;
while (varint != 0) {
// Put 7 bits to result buffer and mark as "not last" (0b10000000)
result.append((varint & 0b01111111) | 0b10000000);
// Divide values to chunks of 7 bits and move to next chunk
varint >>= 7;
}
result.data()[result.size() - 1] &= ~0b10000000;
return result;
}
//---------------Integral and floating point types serializers---------------
/*
Serialization of unsigned integral types
Use Varint encoding[0]:
"Varints are a method of serializing integers using one or more bytes. Smaller numbers
[regardless its type] take a smaller number of bytes."
[0]: https://protobuf.dev/programming-guides/encoding
value: Value to serialize
Returns a byte array with 'value' encoded
*/
template <typename V, if_unsigned_int<V> = true>
[[nodiscard]] QByteArray serializeBasic(const V &value)
{
return serializeVarintCommon(value);
}
/*
Serialization of fixed-length primitive types
Natural layout of bits is used: value is encoded in a byte array same way as it is located in
memory
value: Value to serialize
returns a byte array with 'value' encoded
*/
template <typename V, if_i32_or_i64<V> = true>
[[nodiscard]] QByteArray serializeBasic(const V &value)
{
// Reserve required number of bytes
QByteArray result(sizeof(V), Qt::Uninitialized);
qToUnaligned(qToLittleEndian(value), result.data());
return result;
}
/*
Serialization of signed integral types
Uses ZigZag encoding[0] first then apply serialization as for unsigned integral types
[0]: https://protobuf.dev/programming-guides/encoding
value: Value to serialize
Returns a byte array with 'value' encoded
*/
template <typename V, if_zigzag_int<V> = true>
[[nodiscard]] QByteArray serializeBasic(const V &value)
{
using UV = std::make_unsigned_t<V>;
UV uValue = 0;
// Use ZigZag convertion first and apply unsigned variant next
V zigZagValue = (value << 1) ^ (value >> (sizeof(UV) * 8 - 1));
uValue = static_cast<UV>(zigZagValue);
return serializeBasic(uValue);
}
template <typename V, if_signed_int<V> = true>
[[nodiscard]] QByteArray serializeBasic(const V &value)
{
// Non-ZigZag signed integers should always be (de)serialized as the
// QtProtobuf::uint64
return serializeBasic(static_cast<QtProtobuf::uint64>(value));
}
//------------------QString and QByteArray types serializers-----------------
template <typename V, if_length_delimited<V> = true>
[[nodiscard]] QByteArray serializeBasic(const V &value)
{
// Varint serialize field size and apply result as starting point
if constexpr (std::is_same<V, QString>::value)
return prependLengthDelimitedSize(value.toUtf8());
else
return prependLengthDelimitedSize(value);
}
//--------------------------List types serializers---------------------------
template <typename V, if_not_length_delimited<V> = true>
[[nodiscard]] QByteArray serializeListType(const QList<V> &listValue)
{
if (listValue.isEmpty())
return {};
QByteArray serializedList;
for (auto &value : listValue)
serializedList.append(serializeBasic<V>(value));
// If internal field type is not LengthDelimited, exact amount of fields to be specified
serializedList = prependLengthDelimitedSize(serializedList);
return serializedList;
}
template <typename V, if_non_packed_compatible<V> = true>
[[nodiscard]] QByteArray serializeNonPackedList(const QList<V> &listValue, const QByteArray &header)
{
QByteArray serializedList;
for (auto &value : listValue) {
serializedList.append(header);
serializedList.append(serializeBasic<V>(value));
}
return serializedList;
}
// ###########################################################################
// Deserializers
// ###########################################################################
template <typename V, if_unsigned_int<V> = true>
[[nodiscard]] std::optional<V> deserializeVarintCommon(QProtobufSelfcheckIterator &it)
{
quint64 value = 0;
int k = 0;
while (true) {
if (it.bytesLeft() == 0)
return std::nullopt;
quint64 byte = quint64(static_cast<unsigned char>(*it));
value += (byte & 0b01111111) << k;
k += 7;
if (((*it) & 0b10000000) == 0)
break;
++it;
}
++it;
return { V(value) };
}
//-------------Integral and floating point types deserializers---------------
template <typename V, if_i32_or_i64<V> = false>
[[nodiscard]] bool deserializeBasic(QProtobufSelfcheckIterator &it, QVariant &variantValue)
{
qsizetype size = sizeof(V);
if (it.bytesLeft() < size)
return false;
variantValue = QVariant::fromValue(qFromLittleEndian(qFromUnaligned<V>(it.data())));
it += size;
return true;
}
template <typename V, if_unsigned_int<V> = false>
[[nodiscard]] bool deserializeBasic(QProtobufSelfcheckIterator &it, QVariant &variantValue)
{
auto opt = deserializeVarintCommon<V>(it);
if (!opt)
return false;
variantValue = QVariant::fromValue(opt.value());
return true;
}
template <typename V, if_zigzag_int<V> = false>
[[nodiscard]] bool deserializeBasic(QProtobufSelfcheckIterator &it, QVariant &variantValue)
{
using UV = std::make_unsigned_t<V>;
auto opt = deserializeVarintCommon<UV>(it);
if (!opt)
return false;
UV unsignedValue = opt.value();
V value = (unsignedValue >> 1) ^ (-1 * (unsignedValue & 1));
variantValue = QVariant::fromValue<V>(value);
return true;
}
template <typename V, if_signed_int<V> = false>
[[nodiscard]] bool deserializeBasic(QProtobufSelfcheckIterator &it, QVariant &variantValue)
{
// Non-ZigZag signed integers should always be (de)serialized as the
// QtProtobuf::uint64
auto opt = deserializeVarintCommon<QtProtobuf::uint64>(it);
if (!opt)
return false;
QtProtobuf::uint64 unsignedValue = opt.value();
V value = static_cast<V>(unsignedValue);
variantValue = QVariant::fromValue(value);
return true;
}
//-----------------QString and QByteArray types deserializers----------------
template <typename V, if_length_delimited<V> = false>
[[nodiscard]] bool deserializeBasic(QProtobufSelfcheckIterator &it, QVariant &variantValue)
{
std::optional<QByteArray> result = deserializeLengthDelimited(it);
if (!result) {
variantValue = QVariant();
return false;
}
if constexpr (std::is_same<QString, V>::value)
variantValue = QVariant::fromValue(QString::fromUtf8(*result));
else
variantValue = QVariant::fromValue(*result);
return true;
}
//-------------------------List types deserializers--------------------------
template <typename V>
[[nodiscard]] bool deserializeList(QProtobufSelfcheckIterator &it, QVariant &previousValue)
{
QList<V> out;
auto opt = deserializeVarintCommon<QtProtobuf::uint64>(it);
if (!opt)
return false;
quint64 count = opt.value();
if (count > quint64(std::numeric_limits<qsizetype>::max()))
return false;
QProtobufSelfcheckIterator lastVarint = it + count;
if (!lastVarint.isValid())
return false;
while (it != lastVarint) {
QVariant variantValue;
if (!deserializeBasic<V>(it, variantValue))
return false;
out.append(variantValue.value<V>());
}
previousValue.setValue(out);
return true;
}
template <typename V>
[[nodiscard]] bool deserializeNonPackedList(QProtobufSelfcheckIterator &it, QVariant &previousValue)
{
Q_ASSERT_X(previousValue.metaType() == QMetaType::fromType<QList<V>>() && previousValue.data(),
"QProtobufSerializerPrivate::deserializeNonPackedList",
"Previous value metatype doesn't match the list metatype");
QVariant variantValue;
if (deserializeBasic<V>(it, variantValue)) {
QList<V> *out = reinterpret_cast<QList<V> *>(previousValue.data());
out->append(variantValue.value<V>());
return true;
}
return false;
}
} // namespace ProtobufScalarSerializers
QT_END_NAMESPACE
#endif // PROTOBUFSCALARSERIALIZERS_P_H

View File

@ -4,6 +4,7 @@
#include <QtProtobuf/qprotobufjsonserializer.h>
#include <QtProtobuf/private/protobuffieldpresencechecker_p.h>
#include <QtProtobuf/private/protobufscalarjsonserializers_p.h>
#include <QtProtobuf/private/qprotobufdeserializerbase_p.h>
#include <QtProtobuf/private/qprotobufregistration_p.h>
#include <QtProtobuf/private/qprotobufserializerbase_p.h>
@ -11,15 +12,13 @@
#include <QtProtobuf/private/qtprotobufserializerhelpers_p.h>
#include <QtCore/qcoreapplication.h>
#include <QtCore/qhash.h>
#include <QtCore/qjsonarray.h>
#include <QtCore/qjsondocument.h>
#include <QtCore/qjsonobject.h>
#include <QtCore/qvariant.h>
#include <QtCore/qhash.h>
#include <QtCore/private/qnumeric_p.h>
#include <cmath>
#include <limits>
#include <type_traits>
QT_BEGIN_NAMESPACE
@ -41,6 +40,7 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
using namespace QtProtobufPrivate;
using namespace ProtobufScalarJsonSerializers;
namespace {
@ -120,38 +120,6 @@ private:
class QProtobufJsonSerializerPrivate final
{
Q_DISABLE_COPY_MOVE(QProtobufJsonSerializerPrivate)
// Tests if V is JSON compatible integer
// int32 | int64 | uint32 | sint32 | sint64 | fixed32 | sfixed32 | sfixed64
template <typename V>
struct IsJsonInt
: std::integral_constant<
bool,
std::is_same_v<V, QtProtobuf::int32> || std::is_same_v<V, QtProtobuf::int64>
|| std::is_same_v<V, QtProtobuf::uint32> || std::is_same_v<V, QtProtobuf::sint32>
|| std::is_same_v<V, QtProtobuf::sint64> || std::is_same_v<V, QtProtobuf::fixed32>
|| std::is_same_v<V, QtProtobuf::sfixed32>
|| std::is_same_v<V, QtProtobuf::sfixed64>>
{
};
// Tests if V is JSON incompatible 64-bit unsigned integer
// uint64 | fixed64
template <typename V>
struct IsJsonInt64
: std::integral_constant<
bool, std::is_same_v<V, QtProtobuf::uint64> || std::is_same_v<V, QtProtobuf::fixed64>>
{
};
// Tests if V is JSON compatible floating point number
// float | double
template <typename V>
struct IsJsonFloatingPoint
: std::integral_constant<bool, std::is_same_v<V, float> || std::is_same_v<V, double>>
{
};
public:
using Serializer = std::function<QJsonValue(const QVariant &)>;
using Deserializer = std::function<QVariant(const QJsonValue&, bool &ok)>;
@ -167,16 +135,14 @@ public:
template <typename T>
static SerializationHandlers createCommonHandler()
{
return { QProtobufJsonSerializerPrivate::serializeCommon<T>,
QProtobufJsonSerializerPrivate::deserializeCommon<T>,
return { serializeCommon<T>, deserializeCommon<T>,
ProtobufFieldPresenceChecker::isPresent<T> };
}
template <typename L, typename T>
static SerializationHandlers createCommonListHandler()
{
return { QProtobufJsonSerializerPrivate::serializeList<L>,
QProtobufJsonSerializerPrivate::deserializeList<L, T>,
return { serializeList<L>, deserializeList<L, T>,
ProtobufFieldPresenceChecker::isPresent<L> };
}
@ -190,59 +156,6 @@ public:
using SerializerRegistry = QHash<int/*metatypeid*/, SerializationHandlers>;
template <typename T>
static QJsonValue serializeCommon(const QVariant &propertyValue)
{
return serialize(propertyValue.value<T>());
}
template <typename T, std::enable_if_t<IsJsonInt<T>::value, bool> = true>
static QJsonValue serialize(T propertyValue)
{
return QJsonValue(qint64(propertyValue));
}
template <typename T, std::enable_if_t<IsJsonInt64<T>::value, bool> = true>
static QJsonValue serialize(T propertyValue)
{
return QJsonValue(QString::number(propertyValue));
}
template <typename T, std::enable_if_t<IsJsonFloatingPoint<T>::value, bool> = true>
static QJsonValue serialize(T propertyValue)
{
if (propertyValue == -std::numeric_limits<T>::infinity())
return QJsonValue("-Infinity"_L1);
if (propertyValue == std::numeric_limits<T>::infinity())
return QJsonValue("Infinity"_L1);
if (propertyValue != propertyValue)
return QJsonValue("NaN"_L1);
return QJsonValue(propertyValue);
}
static QJsonValue serialize(bool propertyValue) { return QJsonValue(propertyValue); }
static QJsonValue serialize(const QString &propertyValue) { return QJsonValue(propertyValue); }
static QJsonValue serialize(const QByteArray &propertyValue)
{
return QJsonValue(QString::fromUtf8(propertyValue.toBase64()));
}
template <typename L>
static QJsonValue serializeList(const QVariant &propertyValue)
{
QJsonArray arr;
L listValue = propertyValue.value<L>();
for (const auto &value : listValue) {
arr.append(serialize(value));
}
return QJsonValue(arr);
}
QProtobufJsonSerializerPrivate() : deserializer(this)
{
[[maybe_unused]] static bool initialized = []() -> bool {
@ -263,14 +176,10 @@ public:
handlers[qMetaTypeId<bool>()] = createCommonHandler<bool>();
handlers[QMetaType::QString] = createCommonHandler<QString>();
handlers[QMetaType::QByteArray] = createCommonHandler<QByteArray>();
handlers[QMetaType::Float] = { QProtobufJsonSerializerPrivate::serializeCommon<float>,
QProtobufJsonSerializerPrivate::deserializeCommon<float>,
handlers[QMetaType::Float] = { serializeCommon<float>, deserializeCommon<float>,
QProtobufJsonSerializerPrivate::isPresent<float> };
handlers[QMetaType::Double] = {
QProtobufJsonSerializerPrivate::serializeCommon<double>,
QProtobufJsonSerializerPrivate::deserializeCommon<double>,
QProtobufJsonSerializerPrivate::isPresent<double>
};
handlers[QMetaType::Double] = { serializeCommon<double>, deserializeCommon<double>,
isPresent<double> };
handlers[qMetaTypeId<QtProtobuf::boolList>()] = createCommonListHandler<
QtProtobuf::boolList, bool>();
@ -306,148 +215,6 @@ public:
}
~QProtobufJsonSerializerPrivate() = default;
template <typename T>
static QVariant deserializeCommon(const QJsonValue &value, bool &ok)
{
ok = false;
return QVariant::fromValue<T>(deserialize<T>(value, ok));
}
template <typename T, std::enable_if_t<IsJsonInt<T>::value, bool> = true>
static T deserialize(const QJsonValue &value, bool &ok)
{
auto variantValue = value.toVariant();
qint64 raw = 0;
switch (variantValue.metaType().id()) {
case QMetaType::QString: // TODO: check if string has prepending/appending whitespaces.
case QMetaType::LongLong:
raw = variantValue.toLongLong(&ok);
break;
case QMetaType::Double: {
double d = value.toDouble();
ok = convertDoubleTo(d, &raw) && double(raw) == d;
} break;
default:
break;
}
// For types that "smaller" than qint64 we need to check if the value fits its limits range
if constexpr (sizeof(T) != sizeof(qint64)) {
if (ok) {
if constexpr (std::is_same_v<T, QtProtobuf::sfixed32>
|| std::is_same_v<T, QtProtobuf::int32>) {
using limits = std::numeric_limits<qint32>;
ok = raw >= limits::min() && raw <= limits::max();
} else if constexpr (std::is_same_v<T, QtProtobuf::fixed32>) {
using limits = std::numeric_limits<quint32>;
ok = raw >= limits::min() && raw <= limits::max();
} else {
using limits = std::numeric_limits<T>;
ok = raw >= limits::min() && raw <= limits::max();
}
}
}
return T(raw);
}
template <typename T, std::enable_if_t<IsJsonInt64<T>::value, bool> = true>
static T deserialize(const QJsonValue &value, bool &ok)
{
quint64 raw = 0;
auto variantValue = value.toVariant();
switch (variantValue.metaType().id()) {
case QMetaType::QString:
case QMetaType::LongLong:
// Here we attempt converting the value to ULongLong
raw = variantValue.toULongLong(&ok);
break;
case QMetaType::Double: {
double d = value.toDouble();
ok = convertDoubleTo(d, &raw) && double(raw) == d;
} break;
default:
break;
}
return T(raw);
}
template <typename T, std::enable_if_t<IsJsonFloatingPoint<T>::value, bool> = true>
static T deserialize(const QJsonValue &value, bool &ok)
{
ok = true;
QByteArray data = value.toVariant().toByteArray();
if (data.toLower() == "-infinity"_ba)
return -std::numeric_limits<T>::infinity();
if (data.toLower() == "infinity"_ba)
return std::numeric_limits<T>::infinity();
if (data.toLower() == "nan"_ba)
return T(NAN);
if constexpr (std::is_same_v<T, float>)
return data.toFloat(&ok);
else
return data.toDouble(&ok);
}
template <typename T, std::enable_if_t<std::is_same_v<T, bool>, bool> = true>
static bool deserialize(const QJsonValue &value, bool &ok)
{
if (value.isBool()) {
ok = true;
return value.toBool();
} else if (value.isString()) {
if (value.toString() == "true"_L1) {
ok = true;
return true;
} else if (value.toString() == "false"_L1) {
ok = true;
return false;
}
}
return false;
}
template <typename T, std::enable_if_t<std::is_same_v<T, QString>, bool> = true>
static QString deserialize(const QJsonValue &value, bool &ok)
{
ok = value.isString();
return value.toString();
}
template <typename T, std::enable_if_t<std::is_same_v<T, QByteArray>, bool> = true>
static QByteArray deserialize(const QJsonValue &value, bool &ok)
{
QByteArray data = value.toVariant().toByteArray();
if (value.isString()) {
ok = true;
return QByteArray::fromBase64(data);
}
return {};
}
template <typename L /*list*/, typename T /*element*/>
static QVariant deserializeList(const QJsonValue &value, bool &ok)
{
if (!value.isArray()) {
ok = false;
return {};
}
L list;
QJsonArray array = value.toArray();
for (auto arrayValue : array) {
ok = false;
T value = deserialize<T>(arrayValue, ok);
if (!ok)
break;
list.append(value);
}
return QVariant::fromValue(list);
}
void clearError();
QAbstractProtobufSerializer::Error lastError = QAbstractProtobufSerializer::Error::None;
@ -477,8 +244,7 @@ bool QProtobufJsonSerializerImpl::serializeEnum(QVariant &value,
return false;
if (!ProtobufFieldPresenceChecker::isPresent<QStringList>(value))
return true;
m_result.insert(jsonName.toString(),
QProtobufJsonSerializerPrivate::serializeList<QStringList>(value));
m_result.insert(jsonName.toString(), serializeList<QStringList>(value));
} else {
if (!value.convert(QMetaType::fromType<QString>()))
return false;
@ -486,8 +252,7 @@ bool QProtobufJsonSerializerImpl::serializeEnum(QVariant &value,
&& !isOneofOrOptionalField(fieldInfo.fieldFlags())) {
return true;
}
m_result.insert(jsonName.toString(),
QProtobufJsonSerializerPrivate::serializeCommon<QString>(value));
m_result.insert(jsonName.toString(), serializeCommon<QString>(value));
}
return true;
}
@ -570,17 +335,14 @@ bool QProtobufJsonDeserializerImpl::deserializeEnum(QVariant &value,
bool ok = false;
auto &state = m_state.last();
if (fieldInfo.fieldFlags().testFlag(QtProtobufPrivate::FieldFlag::Repeated)) {
value = QProtobufJsonSerializerPrivate::deserializeList<QStringList,
QString>(state.scalarValue, ok);
value = deserializeList<QStringList, QString>(state.scalarValue, ok);
} else {
// It's allowed to pass single enum value as numeric value.
// Make the backward value conversion and deserialize enum as QtProtobuf::int64.
if (state.scalarValue.isString()) {
value = QProtobufJsonSerializerPrivate::deserializeCommon<QString>(state.scalarValue,
ok);
value = deserializeCommon<QString>(state.scalarValue, ok);
} else {
value = QProtobufJsonSerializerPrivate::deserializeCommon<
QtProtobuf::int64>(state.scalarValue, ok);
value = deserializeCommon<QtProtobuf::int64>(state.scalarValue, ok);
}
}

View File

@ -4,6 +4,7 @@
#include <QtProtobuf/qprotobufserializer.h>
#include <QtProtobuf/private/protobufscalarserializers_p.h>
#include <QtProtobuf/private/qprotobufmessage_p.h>
#include <QtProtobuf/private/qprotobufregistration_p.h>
#include <QtProtobuf/private/qprotobufserializer_p.h>
@ -37,6 +38,7 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
using namespace QtProtobufPrivate;
using namespace ProtobufScalarSerializers;
template<std::size_t N>
using SerializerRegistryType =
@ -44,25 +46,22 @@ using SerializerRegistryType =
namespace {
#define QT_CONSTRUCT_PROTOBUF_SERIALIZATION_HANDLER(Type, WireType) \
{ QMetaType::fromType<Type>(), \
QProtobufSerializerPrivate::serializeWrapper< \
Type, QProtobufSerializerPrivate::serializeBasic<Type>>, \
QProtobufSerializerPrivate::deserializeBasic<Type>, \
ProtobufFieldPresenceChecker::isPresent<Type>, WireType }
#define QT_CONSTRUCT_PROTOBUF_LIST_SERIALIZATION_HANDLER(ListType, Type) \
{ QMetaType::fromType<ListType>(), \
QProtobufSerializerPrivate::serializeWrapper< \
ListType, QProtobufSerializerPrivate::serializeListType<Type>>, \
QProtobufSerializerPrivate::deserializeList<Type>, \
ProtobufFieldPresenceChecker::isPresent<ListType>, QtProtobuf::WireTypes::LengthDelimited }
#define QT_CONSTRUCT_PROTOBUF_SERIALIZATION_HANDLER(Type, WireType) \
{ QMetaType::fromType<Type>(), \
QProtobufSerializerPrivate::serializeWrapper<Type, serializeBasic<Type>>, \
deserializeBasic<Type>, ProtobufFieldPresenceChecker::isPresent<Type>, WireType }
#define QT_CONSTRUCT_PROTOBUF_LIST_SERIALIZATION_HANDLER(ListType, Type) \
{ QMetaType::fromType<ListType>(), \
QProtobufSerializerPrivate::serializeWrapper<ListType, serializeListType<Type>>, \
deserializeList<Type>, ProtobufFieldPresenceChecker::isPresent<ListType>, \
QtProtobuf::WireTypes::LengthDelimited }
#define QT_CONSTRUCT_PROTOBUF_NON_PACKED_LIST_SERIALIZATION_HANDLER(ListType, Type, WireType) \
{ QMetaType::fromType<ListType>(), \
QProtobufSerializerPrivate::serializeNonPackedWrapper< \
ListType, QProtobufSerializerPrivate::serializeNonPackedList<Type>>, \
QProtobufSerializerPrivate::deserializeNonPackedList<Type>, \
ProtobufFieldPresenceChecker::isPresent<ListType>, WireType }
QProtobufSerializerPrivate::serializeNonPackedWrapper<ListType, \
serializeNonPackedList<Type>>, \
deserializeNonPackedList<Type>, ProtobufFieldPresenceChecker::isPresent<ListType>, \
WireType }
constexpr SerializerRegistryType<30> IntegratedTypesSerializers = { {
QT_CONSTRUCT_PROTOBUF_SERIALIZATION_HANDLER(float, QtProtobuf::WireTypes::Fixed32),
@ -206,13 +205,11 @@ bool QProtobufSerializerImpl::serializeEnum(QVariant &value,
if (fieldFlags.testFlag(QtProtobufPrivate::FieldFlag::NonPacked)) {
const auto header = encodeHeader(fieldInfo.fieldNumber(),
QtProtobuf::WireTypes::Varint);
m_result.append(QProtobufSerializerPrivate::serializeNonPackedList<
QtProtobuf::int64>(listValue, header));
m_result.append(serializeNonPackedList<QtProtobuf::int64>(listValue, header));
} else {
m_result.append(encodeHeader(fieldInfo.fieldNumber(),
QtProtobuf::WireTypes::LengthDelimited));
m_result.append(QProtobufSerializerPrivate::serializeListType<
QtProtobuf::int64>(listValue));
m_result.append(serializeListType<QtProtobuf::int64>(listValue));
}
} else {
if (!value.canConvert<QtProtobuf::int64>())
@ -224,8 +221,7 @@ bool QProtobufSerializerImpl::serializeEnum(QVariant &value,
}
m_result.append(encodeHeader(fieldInfo.fieldNumber(), QtProtobuf::WireTypes::Varint));
m_result.append(QProtobufSerializerPrivate::serializeBasic<
QtProtobuf::int64>(value.value<QtProtobuf::int64>()));
m_result.append(serializeBasic<QtProtobuf::int64>(value.value<QtProtobuf::int64>()));
}
return true;
}
@ -264,7 +260,7 @@ void QProtobufSerializerImpl::serializeMessageFieldEnd(const QProtobufMessage *m
QByteArray last = m_state.takeLast();
last.append(encodeHeader(fieldInfo.fieldNumber(), QtProtobuf::WireTypes::LengthDelimited));
last.append(QProtobufSerializerPrivate::serializeVarintCommon<uint32_t>(m_result.size()));
last.append(serializeVarintCommon<uint32_t>(m_result.size()));
last.append(m_result);
m_result = last;
}
@ -284,7 +280,7 @@ QByteArray QProtobufSerializerImpl::encodeHeader(int fieldIndex, QtProtobuf::Wir
// Returns a varint-encoded fieldIndex and wireType
uint32_t header = (fieldIndex << 3) | int(wireType);
return QProtobufSerializerPrivate::serializeVarintCommon<uint32_t>(header);
return serializeVarintCommon<uint32_t>(header);
}
QProtobufDeserializerImpl::QProtobufDeserializerImpl(QProtobufSerializerPrivate *parent)
@ -318,16 +314,15 @@ bool QProtobufDeserializerImpl::deserializeEnum(QVariant &value,
value.convert(QMetaType::fromType<QList<QtProtobuf::int64>>());
bool result = false;
if (m_wireType == QtProtobuf::WireTypes::Varint) {
result = QProtobufSerializerPrivate::deserializeNonPackedList<QtProtobuf::int64>(m_it,
value);
result = deserializeNonPackedList<QtProtobuf::int64>(m_it, value);
} else if (m_wireType == QtProtobuf::WireTypes::LengthDelimited) {
result = QProtobufSerializerPrivate::deserializeList<QtProtobuf::int64>(m_it, value);
result = deserializeList<QtProtobuf::int64>(m_it, value);
}
value.convert(metaType);
return result;
}
return QProtobufSerializerPrivate::deserializeBasic<QtProtobuf::int64>(m_it, value);
return deserializeBasic<QtProtobuf::int64>(m_it, value);
}
int QProtobufDeserializerImpl::nextFieldIndex(QProtobufMessage *message)
@ -364,8 +359,7 @@ int QProtobufDeserializerImpl::nextFieldIndex(QProtobufMessage *message)
if (ordering->fieldFlags(index).testAnyFlags({ QtProtobufPrivate::FieldFlag::Message,
QtProtobufPrivate::FieldFlag::Map })) {
auto
opt = QProtobufSerializerPrivate::deserializeVarintCommon<QtProtobuf::uint64>(m_it);
auto opt = deserializeVarintCommon<QtProtobuf::uint64>(m_it);
if (!opt) {
setUnexpectedEndOfStreamError();
return -1;
@ -473,7 +467,7 @@ void QProtobufDeserializerImpl::skipVarint()
void QProtobufDeserializerImpl::skipLengthDelimited()
{
//Get length of length-delimited field
auto opt = QProtobufSerializerPrivate::deserializeVarintCommon<QtProtobuf::uint64>(m_it);
auto opt = deserializeVarintCommon<QtProtobuf::uint64>(m_it);
if (!opt) {
m_it += m_it.bytesLeft() + 1;
return;
@ -493,7 +487,7 @@ bool QProtobufDeserializerImpl::decodeHeader(QProtobufSelfcheckIterator &it, int
{
if (it.bytesLeft() == 0)
return false;
auto opt = QProtobufSerializerPrivate::deserializeVarintCommon<uint32_t>(it);
auto opt = deserializeVarintCommon<uint32_t>(it);
if (!opt)
return false;
uint32_t header = opt.value();

View File

@ -34,9 +34,6 @@
#include <QtCore/qhash.h>
#include <QtCore/qvariant.h>
#include <optional>
#include <type_traits>
QT_BEGIN_NAMESPACE
class QProtobufSerializerImpl final : public QProtobufSerializerBase
@ -105,93 +102,6 @@ private:
class QProtobufSerializerPrivate
{
// The below type trait structures help to determine the required encoding method for protobuf
// types.
// See https://protobuf.dev/programming-guides/encoding for details.
// Tests if V is a Varint-compatible unsigned integer type.
// uint32 | uint64
template<typename V>
struct IsUnsignedInt
: std::integral_constant<bool, std::is_integral<V>::value && std::is_unsigned<V>::value>
{};
// Tests if V is a Varint-compatible signed integer type.
// int32 | int64
template<typename V>
struct IsSignedInt
: std::integral_constant<bool,
std::is_same<V, QtProtobuf::int32>::value
|| std::is_same<V, QtProtobuf::int64>::value>
{};
// Tests if V is a Varint-compatible signed integer type, with ZigZag encoding support.
// sint32 | sint64
template<typename V>
struct IsZigZagInt
: std::integral_constant<bool, std::is_integral<V>::value && std::is_signed<V>::value>
{};
// Tests if V is a Varint-compatible integer type.
// int32 | int64 | uint32 | uint64 | sint32 | sint64
template<typename V>
struct IsInt : std::integral_constant<bool, std::is_integral<V>::value || IsSignedInt<V>::value>
{};
// Tests if V is a 32-bit integer without the need for encoding.
// sfixed32 | fixed32
template<typename V>
struct IsFixed32Int
: std::integral_constant<bool,
std::is_same<V, QtProtobuf::fixed32>::value
|| std::is_same<V, QtProtobuf::sfixed32>::value>
{};
// Tests if V is a 64-bit integer without the need for encoding.
// sfixed64 | fixed64
template<typename V>
struct IsFixed64Int
: std::integral_constant<bool,
std::is_same<V, QtProtobuf::fixed64>::value
|| std::is_same<V, QtProtobuf::sfixed64>::value>
{};
// Tests if V is an integer without the need for encoding.
// sfixed32 | fixed32 | sfixed64 | fixed64
template<typename V>
struct IsFixedInt
: std::integral_constant<bool, IsFixed32Int<V>::value || IsFixed64Int<V>::value>
{};
// Tests if V is a 32-bit type without the need for encoding.
// sfixed32 | fixed32 | float
template<typename V>
struct IsI32
: std::integral_constant<bool, IsFixed32Int<V>::value || std::is_same<V, float>::value>
{};
// Tests if V is a 64-bit type without the need for encoding.
// sfixed64 | fixed64 | double
template<typename V>
struct IsI64
: std::integral_constant<bool, IsFixed64Int<V>::value || std::is_same<V, double>::value>
{};
// Tests if V is a type without the need for encoding.
// sfixed32 | fixed32 | float | sfixed64 | fixed64 | double
template<typename V>
struct IsI32OrI64 : std::integral_constant<bool, IsI64<V>::value || IsI32<V>::value>
{};
// Tests if V is the length-delimited non-message, non-list type.
// QString | QByteArray
template<typename V>
struct IsLengthDelimited
: std::integral_constant<
bool, std::is_same<V, QString>::value || std::is_same<V, QByteArray>::value>
{
};
public:
// Serializer is interface function for serialize method
using Serializer = QByteArray (*)(const QVariant &, const QByteArray &);
@ -212,326 +122,6 @@ public:
QProtobufSerializerPrivate();
~QProtobufSerializerPrivate() = default;
// ###########################################################################
// Serializers
// ###########################################################################
template<typename V, typename std::enable_if_t<IsUnsignedInt<V>::value, int> = 0>
static QByteArray serializeVarintCommon(const V &value)
{
if (value == 0)
return QByteArray(1, char(0));
qProtoDebug() << "value" << value;
quint64 varint = value;
QByteArray result;
while (varint != 0) {
// Put 7 bits to result buffer and mark as "not last" (0b10000000)
result.append((varint & 0b01111111) | 0b10000000);
// Divide values to chunks of 7 bits and move to next chunk
varint >>= 7;
}
result.data()[result.size() - 1] &= ~0b10000000;
return result;
}
//---------------Integral and floating point types serializers---------------
/*
Serialization of fixed-length primitive types
Natural layout of bits is used: value is encoded in a byte array same way as it is located in
memory
value: Value to serialize
returns a byte array with 'value' encoded
*/
template<typename V, typename std::enable_if_t<IsI32OrI64<V>::value, int> = 0>
static QByteArray serializeBasic(const V &value)
{
qProtoDebug() << "value" << value;
// Reserve required number of bytes
QByteArray result(sizeof(V), Qt::Uninitialized);
qToUnaligned(qToLittleEndian(value), result.data());
return result;
}
/*
Serialization of signed integral types
Uses ZigZag encoding[0] first then apply serialization as for unsigned integral types
[0]: https://protobuf.dev/programming-guides/encoding
value: Value to serialize
Returns a byte array with 'value' encoded
*/
template<typename V, typename std::enable_if_t<IsZigZagInt<V>::value, int> = 0>
static QByteArray serializeBasic(const V &value)
{
qProtoDebug() << "value" << value;
using UV = std::make_unsigned_t<V>;
UV uValue = 0;
// Use ZigZag convertion first and apply unsigned variant next
V zigZagValue = (value << 1) ^ (value >> (sizeof(UV) * 8 - 1));
uValue = static_cast<UV>(zigZagValue);
return serializeBasic(uValue);
}
template<typename V, typename std::enable_if_t<IsSignedInt<V>::value, int> = 0>
static QByteArray serializeBasic(const V &value)
{
qProtoDebug() << "value" << value;
// Non-ZigZag signed integers should always be (de)serialized as the
// QtProtobuf::uint64
return serializeBasic(static_cast<QtProtobuf::uint64>(value));
}
/*!
Serialization of unsigned integral types
Use Varint encoding[0]:
"Varints are a method of serializing integers using one or more bytes. Smaller numbers
[regardless its type] take a smaller number of bytes."
[0]: https://protobuf.dev/programming-guides/encoding
value: Value to serialize
Returns a byte array with 'value' encoded
*/
template<typename V, typename std::enable_if_t<IsUnsignedInt<V>::value, int> = 0>
static QByteArray serializeBasic(const V &value)
{
qProtoDebug() << "value" << value;
return serializeVarintCommon(value);
}
//------------------QString and QByteArray types serializers-----------------
template<typename V, typename std::enable_if_t<IsLengthDelimited<V>::value, int> = 0>
static QByteArray serializeBasic(const V &value)
{
qProtoDebug("data.size: %" PRIdQSIZETYPE, value.size());
// Varint serialize field size and apply result as starting point
if constexpr (std::is_same<V, QString>::value)
return prependLengthDelimitedSize(value.toUtf8());
else
return prependLengthDelimitedSize(value);
}
//--------------------------List types serializers---------------------------
template<typename V, typename std::enable_if_t<!IsLengthDelimited<V>::value, int> = 0>
static QByteArray serializeListType(const QList<V> &listValue)
{
qProtoDebug("listValue.count %" PRIdQSIZETYPE, listValue.count());
if (listValue.isEmpty()) {
return {};
}
QByteArray serializedList;
for (auto &value : listValue) {
serializedList.append(serializeBasic<V>(value));
}
// If internal field type is not LengthDelimited, exact amount of fields to be specified
serializedList = prependLengthDelimitedSize(serializedList);
return serializedList;
}
template<typename V,
typename std::enable_if_t<
IsInt<V>::value || IsI32OrI64<V>::value || IsLengthDelimited<V>::value, int> =
0>
static QByteArray serializeNonPackedList(const QList<V> &listValue, const QByteArray &header)
{
qProtoDebug("listValue.count %" PRIdQSIZETYPE, listValue.count());
QByteArray serializedList;
for (auto &value : listValue) {
serializedList.append(header);
serializedList.append(serializeBasic<V>(value));
}
return serializedList;
}
// ###########################################################################
// Deserializers
// ###########################################################################
template<typename V, typename std::enable_if_t<IsUnsignedInt<V>::value, int> = 0>
[[nodiscard]] static std::optional<V>
deserializeVarintCommon(QProtobufSelfcheckIterator &it)
{
qProtoDebug("currentByte: 0x%x", *it);
quint64 value = 0;
int k = 0;
while (true) {
if (it.bytesLeft() == 0)
return std::nullopt;
quint64 byte = quint64(static_cast<unsigned char>(*it));
value += (byte & 0b01111111) << k;
k += 7;
if (((*it) & 0b10000000) == 0)
break;
++it;
}
++it;
return { V(value) };
}
//-------------Integral and floating point types deserializers---------------
template<typename V, typename std::enable_if_t<IsI32OrI64<V>::value, int> = 0>
[[nodiscard]] static bool deserializeBasic(QProtobufSelfcheckIterator &it,
QVariant &variantValue)
{
qsizetype size = sizeof(V);
if (it.bytesLeft() < size)
return false;
qProtoDebug("currentByte: 0x%x", *it);
variantValue = QVariant::fromValue(qFromLittleEndian(qFromUnaligned<V>(it.data())));
it += size;
return true;
}
template<typename V, typename std::enable_if_t<IsUnsignedInt<V>::value, int> = 0>
[[nodiscard]] static bool deserializeBasic(QProtobufSelfcheckIterator &it,
QVariant &variantValue)
{
qProtoDebug("currentByte: 0x%x", *it);
auto opt = deserializeVarintCommon<V>(it);
if (!opt)
return false;
variantValue = QVariant::fromValue(opt.value());
return true;
}
template<typename V, typename std::enable_if_t<IsZigZagInt<V>::value, int> = 0>
[[nodiscard]] static bool deserializeBasic(QProtobufSelfcheckIterator &it,
QVariant &variantValue)
{
qProtoDebug("currentByte: 0x%x", *it);
using UV = std::make_unsigned_t<V>;
auto opt = deserializeVarintCommon<UV>(it);
if (!opt)
return false;
UV unsignedValue = opt.value();
V value = (unsignedValue >> 1) ^ (-1 * (unsignedValue & 1));
variantValue = QVariant::fromValue<V>(value);
return true;
}
template<typename V, typename std::enable_if_t<IsSignedInt<V>::value, int> = 0>
[[nodiscard]] static bool deserializeBasic(QProtobufSelfcheckIterator &it,
QVariant &variantValue)
{
qProtoDebug("currentByte: 0x%x", *it);
// Non-ZigZag signed integers should always be (de)serialized as the
// QtProtobuf::uint64
auto opt = deserializeVarintCommon<QtProtobuf::uint64>(it);
if (!opt)
return false;
QtProtobuf::uint64 unsignedValue = opt.value();
V value = static_cast<V>(unsignedValue);
variantValue = QVariant::fromValue(value);
return true;
}
//-----------------QString and QByteArray types deserializers----------------
template<typename V, typename std::enable_if_t<IsLengthDelimited<V>::value, int> = 0>
[[nodiscard]] static bool deserializeBasic(QProtobufSelfcheckIterator &it,
QVariant &variantValue)
{
std::optional<QByteArray> result = deserializeLengthDelimited(it);
if (!result) {
variantValue = QVariant();
return false;
}
if constexpr (std::is_same<QString, V>::value)
variantValue = QVariant::fromValue(QString::fromUtf8(*result));
else
variantValue = QVariant::fromValue(*result);
return true;
}
//-------------------------List types deserializers--------------------------
template<typename V>
[[nodiscard]] static bool deserializeList(QProtobufSelfcheckIterator &it,
QVariant &previousValue)
{
qProtoDebug("currentByte: 0x%x", *it);
QList<V> out;
auto opt = deserializeVarintCommon<QtProtobuf::uint64>(it);
if (!opt)
return false;
quint64 count = opt.value();
if (count > quint64(std::numeric_limits<qsizetype>::max()))
return false;
QProtobufSelfcheckIterator lastVarint = it + count;
if (!lastVarint.isValid())
return false;
while (it != lastVarint) {
QVariant variantValue;
if (!deserializeBasic<V>(it, variantValue))
return false;
out.append(variantValue.value<V>());
}
previousValue.setValue(out);
return true;
}
template<typename V>
[[nodiscard]] static bool deserializeNonPackedList(QProtobufSelfcheckIterator &it,
QVariant &previousValue)
{
qProtoDebug("currentByte: 0x%x", *it);
Q_ASSERT_X(previousValue.metaType() == QMetaType::fromType<QList<V>>()
&& previousValue.data(),
"QProtobufSerializerPrivate::deserializeNonPackedList",
"Previous value metatype doesn't match the list metatype");
QVariant variantValue;
if (deserializeBasic<V>(it, variantValue)) {
QList<V> *out = reinterpret_cast<QList<V> *>(previousValue.data());
out->append(variantValue.value<V>());
return true;
}
return false;
}
// ###########################################################################
// Common functions
// ###########################################################################
static std::optional<QByteArray> deserializeLengthDelimited(QProtobufSelfcheckIterator &it)
{
if (it.bytesLeft() == 0)
return std::nullopt;
qProtoDebug("currentByte: 0x%x", *it);
auto opt = deserializeVarintCommon<QtProtobuf::uint64>(it);
if (!opt)
return std::nullopt;
quint64 length = opt.value();
if (!it.isValid() || quint64(it.bytesLeft()) < length
|| length > quint64(QByteArray::maxSize())) {
return std::nullopt;
}
QByteArray result(it.data(), qsizetype(length));
it += length;
return { result };
}
/*!
Gets length of a byte-array and prepends to it its serialized length value
using the appropriate serialization algorithm
Returns 'data' with its length prepended
*/
static QByteArray prependLengthDelimitedSize(const QByteArray &data)
{
return serializeVarintCommon<uint32_t>(data.size()) + data;
}
template<typename T, QByteArray (*s)(const T &)>
static QByteArray serializeWrapper(const QVariant &variantValue, const QByteArray &header)