1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#ifndef PROTOBUFSCALARSERIALIZERS_P_H
5#define PROTOBUFSCALARSERIALIZERS_P_H
6//
7// W A R N I N G
8// -------------
9//
10// This file is not part of the Qt API. It exists purely as an
11// implementation detail. This header file may change from version to
12// version without notice, or even be removed.
13//
14// We mean it.
15//
16
17#include <QtProtobuf/private/qprotobufselfcheckiterator_p.h>
18#include <QtProtobuf/qtprotobuftypes.h>
19
20#include <QtCore/qtconfigmacros.h>
21
22#include <type_traits>
23
24QT_BEGIN_NAMESPACE
25
26namespace ProtobufScalarSerializers {
27// The below type trait structures help to determine the required encoding method for protobuf
28// types.
29// See https://protobuf.dev/programming-guides/encoding for details.
30
31// Tests if V is a Varint-compatible unsigned integer type.
32// uint32 | uint64
33template <typename V>
34using is_unsigned_int = std::conjunction<std::is_integral<V>, std::is_unsigned<V>>;
35
36template <typename V>
37using if_unsigned_int = std::enable_if_t<is_unsigned_int<V>::value, bool>;
38
39// Tests if V is a Varint-compatible signed integer type.
40// int32 | int64
41template <typename V>
42constexpr inline bool IsSignedInt = false;
43template <>
44constexpr inline bool IsSignedInt<QtProtobuf::int32> = true;
45template <>
46constexpr inline bool IsSignedInt<QtProtobuf::int64> = true;
47
48template <typename V>
49using is_signed_int = std::integral_constant<bool, IsSignedInt<V>>;
50
51template <typename V>
52using if_signed_int = std::enable_if_t<is_signed_int<V>::value, bool>;
53
54// Tests if V is a Varint-compatible signed integer type, with ZigZag encoding support.
55// sint32 | sint64
56template <typename V>
57using is_zigzag_int = std::conjunction<std::is_integral<V>, std::is_signed<V>>;
58
59template <typename V>
60using if_zigzag_int = std::enable_if_t<is_zigzag_int<V>::value, bool>;
61
62// Tests if V is a Varint-compatible integer type.
63// int32 | int64 | uint32 | uint64 | sint32 | sint64
64template <typename V>
65using is_int = std::disjunction<std::is_integral<V>, is_signed_int<V>>;
66
67// Tests if V is a 32-bit integer without the need for encoding.
68// sfixed32 | fixed32
69template <typename V>
70constexpr inline bool IsFixed32Int = false;
71template <>
72constexpr inline bool IsFixed32Int<QtProtobuf::fixed32> = true;
73template <>
74constexpr inline bool IsFixed32Int<QtProtobuf::sfixed32> = true;
75
76template <typename V>
77using is_fixed32_int = std::integral_constant<bool, IsFixed32Int<V>>;
78
79// Tests if V is a 64-bit integer without the need for encoding.
80// sfixed64 | fixed64
81template <typename V>
82constexpr inline bool IsFixed64Int = false;
83template <>
84constexpr inline bool IsFixed64Int<QtProtobuf::fixed64> = true;
85template <>
86constexpr inline bool IsFixed64Int<QtProtobuf::sfixed64> = true;
87
88template <typename V>
89using is_fixed64_int = std::integral_constant<bool, IsFixed64Int<V>>;
90
91// Tests if V is a 32-bit type without the need for encoding.
92// sfixed32 | fixed32 | float
93template <typename V>
94using is_i32 = std::disjunction<is_fixed32_int<V>, std::is_same<V, float>>;
95
96// Tests if V is a 64-bit type without the need for encoding.
97// sfixed64 | fixed64 | double
98template <typename V>
99using is_i64 = std::disjunction<is_fixed64_int<V>, std::is_same<V, double>>;
100
101// Tests if V is a type without the need for encoding.
102// sfixed32 | fixed32 | float | sfixed64 | fixed64 | double
103template <typename V>
104using is_i32_or_i64 = std::disjunction<is_i32<V>, is_i64<V>>;
105
106template <typename V>
107using if_i32_or_i64 = std::enable_if_t<is_i32_or_i64<V>::value, bool>;
108
109// Tests if V is the length-delimited non-message, non-list type.
110// QString | QByteArray
111template <typename V>
112constexpr inline bool IsLengthDelimited = false;
113template <>
114constexpr inline bool IsLengthDelimited<QByteArray> = true;
115template <>
116constexpr inline bool IsLengthDelimited<QString> = true;
117
118template <typename V>
119using is_length_delimited = std::integral_constant<bool, IsLengthDelimited<V>>;
120
121template <typename V>
122using if_length_delimited = std::enable_if_t<is_length_delimited<V>::value, bool>;
123
124template <typename V>
125using if_not_length_delimited = std::enable_if_t<std::negation_v<is_length_delimited<V>>, bool>;
126
127// Test if V can be stored in non-packed repeated field
128template <typename V>
129using is_non_packed_compatible = std::disjunction<is_int<V>, is_i32_or_i64<V>,
130 is_length_delimited<V>>;
131
132template <typename V>
133using if_non_packed_compatible = std::enable_if_t<is_non_packed_compatible<V>::value, bool>;
134
135[[nodiscard]] QByteArray prependLengthDelimitedSize(const QByteArray &data);
136[[nodiscard]] std::optional<QByteArray> deserializeLengthDelimited(QProtobufSelfcheckIterator &it);
137
138// ###########################################################################
139// Serializers
140// ###########################################################################
141
142[[nodiscard]] QByteArray serializeVarintCommonImpl(quint64 value);
143
144template <typename V, if_unsigned_int<V> = true>
145[[nodiscard]] QByteArray serializeVarintCommon(V value)
146{
147 return serializeVarintCommonImpl(value: quint64(value));
148}
149
150//---------------Integral and floating point types serializers---------------
151/*
152 Serialization of unsigned integral types
153
154 Use Varint encoding[0]:
155 "Varints are a method of serializing integers using one or more bytes. Smaller numbers
156 [regardless its type] take a smaller number of bytes."
157
158 [0]: https://protobuf.dev/programming-guides/encoding
159
160 value: Value to serialize
161 Returns a byte array with 'value' encoded
162*/
163template <typename V, if_unsigned_int<V> = true>
164[[nodiscard]] QByteArray serializeBasic(const V &value)
165{
166 return serializeVarintCommon(value);
167}
168
169/*
170 Serialization of fixed-length primitive types
171
172 Natural layout of bits is used: value is encoded in a byte array same way as it is located in
173 memory
174
175 value: Value to serialize
176 returns a byte array with 'value' encoded
177 */
178template <typename V, if_i32_or_i64<V> = true>
179[[nodiscard]] QByteArray serializeBasic(const V &value)
180{
181 // Reserve required number of bytes
182 QByteArray result(sizeof(V), Qt::Uninitialized);
183 qToUnaligned(qToLittleEndian(value), result.data());
184 return result;
185}
186
187/*
188 Serialization of signed integral types
189 Uses ZigZag encoding[0] first then apply serialization as for unsigned integral types
190
191 [0]: https://protobuf.dev/programming-guides/encoding
192
193 value: Value to serialize
194 Returns a byte array with 'value' encoded
195 */
196template <typename V, if_zigzag_int<V> = true>
197[[nodiscard]] QByteArray serializeBasic(const V &value)
198{
199 using UV = std::make_unsigned_t<V>;
200
201 // Use ZigZag convertion first and apply unsigned variant next
202 V zigZagValue = (value << 1) ^ (value >> (sizeof(UV) * 8 - 1));
203 return serializeBasic(static_cast<UV>(zigZagValue));
204}
205
206template <typename V, if_signed_int<V> = true>
207[[nodiscard]] QByteArray serializeBasic(const V &value)
208{
209 // Non-ZigZag signed integers should always be (de)serialized as the
210 // QtProtobuf::uint64
211 return serializeBasic(value: static_cast<QtProtobuf::uint64>(value));
212}
213
214//------------------QString and QByteArray types serializers-----------------
215template <typename V, if_length_delimited<V> = true>
216[[nodiscard]] QByteArray serializeBasic(const V &value)
217{
218 // Varint serialize field size and apply result as starting point
219 if constexpr (std::is_same<V, QString>::value)
220 return prependLengthDelimitedSize(value.toUtf8());
221 else
222 return prependLengthDelimitedSize(value);
223}
224
225//--------------------------List types serializers---------------------------
226template <typename V, if_not_length_delimited<V> = true>
227[[nodiscard]] QByteArray serializeListType(const QList<V> &listValue)
228{
229 QByteArray serializedList;
230 if (listValue.isEmpty())
231 return serializedList;
232
233 for (auto &value : listValue)
234 serializedList.append(serializeBasic<V>(value));
235
236 // If internal field type is not LengthDelimited, exact amount of fields to be specified
237 serializedList = prependLengthDelimitedSize(data: serializedList);
238 return serializedList;
239}
240
241template <typename V, if_non_packed_compatible<V> = true>
242[[nodiscard]] QByteArray serializeNonPackedList(const QList<V> &listValue, const QByteArray &header)
243{
244 QByteArray serializedList;
245 for (auto &value : listValue) {
246 serializedList.append(a: header);
247 serializedList.append(serializeBasic<V>(value));
248 }
249 return serializedList;
250}
251
252// ###########################################################################
253// Deserializers
254// ###########################################################################
255
256template <typename V, if_unsigned_int<V> = true>
257[[nodiscard]] std::optional<V> deserializeVarintCommon(QProtobufSelfcheckIterator &it)
258{
259 quint64 value = 0;
260 int k = 0;
261 while (true) {
262 if (it.bytesLeft() == 0)
263 return std::nullopt;
264 quint64 byte = quint64(static_cast<unsigned char>(*it) & 0b01111111);
265 value += byte << k;
266 k += 7;
267 if (((*it++) & 0b10000000) == 0)
268 break;
269 }
270 return { V(value) };
271}
272
273//-------------Integral and floating point types deserializers---------------
274template <typename V, if_i32_or_i64<V> = false>
275[[nodiscard]] bool deserializeBasic(QProtobufSelfcheckIterator &it, QVariant &variantValue)
276{
277 constexpr qsizetype size = sizeof(V);
278 if (it.bytesLeft() < size)
279 return false;
280 variantValue = QVariant::fromValue(qFromLittleEndian(qFromUnaligned<V>(it.data())));
281 it += size;
282 return true;
283}
284
285template <typename V, if_unsigned_int<V> = false>
286[[nodiscard]] bool deserializeBasic(QProtobufSelfcheckIterator &it, QVariant &variantValue)
287{
288 auto opt = deserializeVarintCommon<V>(it);
289 if (!opt)
290 return false;
291 variantValue = QVariant::fromValue(*opt);
292 return true;
293}
294
295template <typename V, if_zigzag_int<V> = false>
296[[nodiscard]] bool deserializeBasic(QProtobufSelfcheckIterator &it, QVariant &variantValue)
297{
298 using UV = std::make_unsigned_t<V>;
299 auto opt = deserializeVarintCommon<UV>(it);
300 if (!opt)
301 return false;
302 UV unsignedValue = *opt;
303 V value = (unsignedValue >> 1) ^ (-1 * (unsignedValue & 1));
304 variantValue = QVariant::fromValue<V>(value);
305 return true;
306}
307
308template <typename V, if_signed_int<V> = false>
309[[nodiscard]] bool deserializeBasic(QProtobufSelfcheckIterator &it, QVariant &variantValue)
310{
311 // Non-ZigZag signed integers should always be (de)serialized as the
312 // QtProtobuf::uint64
313 auto opt = deserializeVarintCommon<QtProtobuf::uint64>(it);
314 if (!opt)
315 return false;
316 variantValue = QVariant::fromValue(static_cast<V>(*opt));
317 return true;
318}
319
320//-----------------QString and QByteArray types deserializers----------------
321template <typename V, if_length_delimited<V> = false>
322[[nodiscard]] bool deserializeBasic(QProtobufSelfcheckIterator &it, QVariant &variantValue)
323{
324 std::optional<QByteArray> result = deserializeLengthDelimited(it);
325 if (!result) {
326 variantValue = QVariant();
327 return false;
328 }
329 if constexpr (std::is_same<QString, V>::value)
330 variantValue = QVariant::fromValue(value: QString::fromUtf8(ba: *result));
331 else
332 variantValue = QVariant::fromValue(value: *result);
333 return true;
334}
335
336//-------------------------List types deserializers--------------------------
337template <typename V>
338[[nodiscard]] bool deserializeList(QProtobufSelfcheckIterator &it, QVariant &previousValue)
339{
340 QList<V> out;
341 auto opt = deserializeVarintCommon<QtProtobuf::uint64>(it);
342 if (!opt)
343 return false;
344 quint64 count = *opt;
345 if (count > quint64(std::numeric_limits<qsizetype>::max()))
346 return false;
347 QProtobufSelfcheckIterator lastVarint = it + count;
348 if (!lastVarint.isValid())
349 return false;
350 while (it != lastVarint) {
351 QVariant variantValue;
352 if (!deserializeBasic<V>(it, variantValue))
353 return false;
354 out.append(variantValue.value<V>());
355 }
356 previousValue.setValue(out);
357 return true;
358}
359
360template <typename V>
361[[nodiscard]] bool deserializeNonPackedList(QProtobufSelfcheckIterator &it, QVariant &previousValue)
362{
363 Q_ASSERT_X(previousValue.metaType() == QMetaType::fromType<QList<V>>() && previousValue.data(),
364 "QProtobufSerializerPrivate::deserializeNonPackedList",
365 "Previous value metatype doesn't match the list metatype");
366 QVariant variantValue;
367 if (deserializeBasic<V>(it, variantValue)) {
368 QList<V> *out = reinterpret_cast<QList<V> *>(previousValue.data());
369 out->append(variantValue.value<V>());
370 return true;
371 }
372 return false;
373}
374
375} // namespace ProtobufScalarSerializers
376
377QT_END_NAMESPACE
378
379#endif // PROTOBUFSCALARSERIALIZERS_P_H
380

source code of qtgrpc/src/protobuf/protobufscalarserializers_p.h