// 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 #include #include #include 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 using is_unsigned_int = std::conjunction, std::is_unsigned>; template using if_unsigned_int = std::enable_if_t::value, bool>; // Tests if V is a Varint-compatible signed integer type. // int32 | int64 template constexpr inline bool IsSignedInt = false; template <> constexpr inline bool IsSignedInt = true; template <> constexpr inline bool IsSignedInt = true; template using is_signed_int = std::integral_constant>; template using if_signed_int = std::enable_if_t::value, bool>; // Tests if V is a Varint-compatible signed integer type, with ZigZag encoding support. // sint32 | sint64 template using is_zigzag_int = std::conjunction, std::is_signed>; template using if_zigzag_int = std::enable_if_t::value, bool>; // Tests if V is a Varint-compatible integer type. // int32 | int64 | uint32 | uint64 | sint32 | sint64 template using is_int = std::disjunction, is_signed_int>; // Tests if V is a 32-bit integer without the need for encoding. // sfixed32 | fixed32 template constexpr inline bool IsFixed32Int = false; template <> constexpr inline bool IsFixed32Int = true; template <> constexpr inline bool IsFixed32Int = true; template using is_fixed32_int = std::integral_constant>; // Tests if V is a 64-bit integer without the need for encoding. // sfixed64 | fixed64 template constexpr inline bool IsFixed64Int = false; template <> constexpr inline bool IsFixed64Int = true; template <> constexpr inline bool IsFixed64Int = true; template using is_fixed64_int = std::integral_constant>; // Tests if V is a 32-bit type without the need for encoding. // sfixed32 | fixed32 | float template using is_i32 = std::disjunction, std::is_same>; // Tests if V is a 64-bit type without the need for encoding. // sfixed64 | fixed64 | double template using is_i64 = std::disjunction, std::is_same>; // Tests if V is a type without the need for encoding. // sfixed32 | fixed32 | float | sfixed64 | fixed64 | double template using is_i32_or_i64 = std::disjunction, is_i64>; template using if_i32_or_i64 = std::enable_if_t::value, bool>; // Tests if V is the length-delimited non-message, non-list type. // QString | QByteArray template constexpr inline bool IsLengthDelimited = false; template <> constexpr inline bool IsLengthDelimited = true; template <> constexpr inline bool IsLengthDelimited = true; template using is_length_delimited = std::integral_constant>; template using if_length_delimited = std::enable_if_t::value, bool>; template using if_not_length_delimited = std::enable_if_t>, bool>; // Test if V can be stored in non-packed repeated field template using is_non_packed_compatible = std::disjunction, is_i32_or_i64, is_length_delimited>; template using if_non_packed_compatible = std::enable_if_t::value, bool>; [[nodiscard]] QByteArray prependLengthDelimitedSize(const QByteArray &data); [[nodiscard]] std::optional deserializeLengthDelimited(QProtobufSelfcheckIterator &it); // ########################################################################### // Serializers // ########################################################################### [[nodiscard]] QByteArray serializeVarintCommonImpl(quint64 value); template = true> [[nodiscard]] QByteArray serializeVarintCommon(V value) { return serializeVarintCommonImpl(quint64(value)); } //---------------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 = 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 = 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 = true> [[nodiscard]] QByteArray serializeBasic(const V &value) { using UV = std::make_unsigned_t; // Use ZigZag convertion first and apply unsigned variant next V zigZagValue = (value << 1) ^ (value >> (sizeof(UV) * 8 - 1)); return serializeBasic(static_cast(zigZagValue)); } template = true> [[nodiscard]] QByteArray serializeBasic(const V &value) { // Non-ZigZag signed integers should always be (de)serialized as the // QtProtobuf::uint64 return serializeBasic(static_cast(value)); } //------------------QString and QByteArray types serializers----------------- template = true> [[nodiscard]] QByteArray serializeBasic(const V &value) { // Varint serialize field size and apply result as starting point if constexpr (std::is_same::value) return prependLengthDelimitedSize(value.toUtf8()); else return prependLengthDelimitedSize(value); } //--------------------------List types serializers--------------------------- template = true> [[nodiscard]] QByteArray serializeListType(const QList &listValue) { QByteArray serializedList; if (listValue.isEmpty()) return serializedList; for (auto &value : listValue) serializedList.append(serializeBasic(value)); // If internal field type is not LengthDelimited, exact amount of fields to be specified serializedList = prependLengthDelimitedSize(serializedList); return serializedList; } template = true> [[nodiscard]] QByteArray serializeNonPackedList(const QList &listValue, const QByteArray &header) { QByteArray serializedList; for (auto &value : listValue) { serializedList.append(header); serializedList.append(serializeBasic(value)); } return serializedList; } // ########################################################################### // Deserializers // ########################################################################### template = true> [[nodiscard]] std::optional deserializeVarintCommon(QProtobufSelfcheckIterator &it) { quint64 value = 0; int k = 0; while (true) { if (it.bytesLeft() == 0) return std::nullopt; quint64 byte = quint64(static_cast(*it) & 0b01111111); value += byte << k; k += 7; if (((*it++) & 0b10000000) == 0) break; } return { V(value) }; } //-------------Integral and floating point types deserializers--------------- template = false> [[nodiscard]] bool deserializeBasic(QProtobufSelfcheckIterator &it, QVariant &variantValue) { constexpr qsizetype size = sizeof(V); if (it.bytesLeft() < size) return false; variantValue = QVariant::fromValue(qFromLittleEndian(qFromUnaligned(it.data()))); it += size; return true; } template = false> [[nodiscard]] bool deserializeBasic(QProtobufSelfcheckIterator &it, QVariant &variantValue) { auto opt = deserializeVarintCommon(it); if (!opt) return false; variantValue = QVariant::fromValue(*opt); return true; } template = false> [[nodiscard]] bool deserializeBasic(QProtobufSelfcheckIterator &it, QVariant &variantValue) { using UV = std::make_unsigned_t; auto opt = deserializeVarintCommon(it); if (!opt) return false; UV unsignedValue = *opt; V value = (unsignedValue >> 1) ^ (-1 * (unsignedValue & 1)); variantValue = QVariant::fromValue(value); return true; } template = false> [[nodiscard]] bool deserializeBasic(QProtobufSelfcheckIterator &it, QVariant &variantValue) { // Non-ZigZag signed integers should always be (de)serialized as the // QtProtobuf::uint64 auto opt = deserializeVarintCommon(it); if (!opt) return false; variantValue = QVariant::fromValue(static_cast(*opt)); return true; } //-----------------QString and QByteArray types deserializers---------------- template = false> [[nodiscard]] bool deserializeBasic(QProtobufSelfcheckIterator &it, QVariant &variantValue) { std::optional result = deserializeLengthDelimited(it); if (!result) { variantValue = QVariant(); return false; } if constexpr (std::is_same::value) variantValue = QVariant::fromValue(QString::fromUtf8(*result)); else variantValue = QVariant::fromValue(*result); return true; } //-------------------------List types deserializers-------------------------- template [[nodiscard]] bool deserializeList(QProtobufSelfcheckIterator &it, QVariant &previousValue) { QList out; auto opt = deserializeVarintCommon(it); if (!opt) return false; quint64 count = *opt; if (count > quint64(std::numeric_limits::max())) return false; QProtobufSelfcheckIterator lastVarint = it + count; if (!lastVarint.isValid()) return false; while (it != lastVarint) { QVariant variantValue; if (!deserializeBasic(it, variantValue)) return false; out.append(variantValue.value()); } previousValue.setValue(out); return true; } template [[nodiscard]] bool deserializeNonPackedList(QProtobufSelfcheckIterator &it, QVariant &previousValue) { Q_ASSERT_X(previousValue.metaType() == QMetaType::fromType>() && previousValue.data(), "QProtobufSerializerPrivate::deserializeNonPackedList", "Previous value metatype doesn't match the list metatype"); QVariant variantValue; if (deserializeBasic(it, variantValue)) { QList *out = reinterpret_cast *>(previousValue.data()); out->append(variantValue.value()); return true; } return false; } } // namespace ProtobufScalarSerializers QT_END_NAMESPACE #endif // PROTOBUFSCALARSERIALIZERS_P_H