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 | |
24 | QT_BEGIN_NAMESPACE |
25 | |
26 | namespace 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 |
33 | template <typename V> |
34 | using is_unsigned_int = std::conjunction<std::is_integral<V>, std::is_unsigned<V>>; |
35 | |
36 | template <typename V> |
37 | using 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 |
41 | template <typename V> |
42 | constexpr inline bool IsSignedInt = false; |
43 | template <> |
44 | constexpr inline bool IsSignedInt<QtProtobuf::int32> = true; |
45 | template <> |
46 | constexpr inline bool IsSignedInt<QtProtobuf::int64> = true; |
47 | |
48 | template <typename V> |
49 | using is_signed_int = std::integral_constant<bool, IsSignedInt<V>>; |
50 | |
51 | template <typename V> |
52 | using 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 |
56 | template <typename V> |
57 | using is_zigzag_int = std::conjunction<std::is_integral<V>, std::is_signed<V>>; |
58 | |
59 | template <typename V> |
60 | using 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 |
64 | template <typename V> |
65 | using 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 |
69 | template <typename V> |
70 | constexpr inline bool IsFixed32Int = false; |
71 | template <> |
72 | constexpr inline bool IsFixed32Int<QtProtobuf::fixed32> = true; |
73 | template <> |
74 | constexpr inline bool IsFixed32Int<QtProtobuf::sfixed32> = true; |
75 | |
76 | template <typename V> |
77 | using 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 |
81 | template <typename V> |
82 | constexpr inline bool IsFixed64Int = false; |
83 | template <> |
84 | constexpr inline bool IsFixed64Int<QtProtobuf::fixed64> = true; |
85 | template <> |
86 | constexpr inline bool IsFixed64Int<QtProtobuf::sfixed64> = true; |
87 | |
88 | template <typename V> |
89 | using 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 |
93 | template <typename V> |
94 | using 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 |
98 | template <typename V> |
99 | using 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 |
103 | template <typename V> |
104 | using is_i32_or_i64 = std::disjunction<is_i32<V>, is_i64<V>>; |
105 | |
106 | template <typename V> |
107 | using 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 |
111 | template <typename V> |
112 | constexpr inline bool IsLengthDelimited = false; |
113 | template <> |
114 | constexpr inline bool IsLengthDelimited<QByteArray> = true; |
115 | template <> |
116 | constexpr inline bool IsLengthDelimited<QString> = true; |
117 | |
118 | template <typename V> |
119 | using is_length_delimited = std::integral_constant<bool, IsLengthDelimited<V>>; |
120 | |
121 | template <typename V> |
122 | using if_length_delimited = std::enable_if_t<is_length_delimited<V>::value, bool>; |
123 | |
124 | template <typename V> |
125 | using 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 |
128 | template <typename V> |
129 | using is_non_packed_compatible = std::disjunction<is_int<V>, is_i32_or_i64<V>, |
130 | is_length_delimited<V>>; |
131 | |
132 | template <typename V> |
133 | using 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 | |
144 | template <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 | */ |
163 | template <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 | */ |
178 | template <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 | */ |
196 | template <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 | |
206 | template <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----------------- |
215 | template <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--------------------------- |
226 | template <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 | |
241 | template <typename V, if_non_packed_compatible<V> = true> |
242 | [[nodiscard]] QByteArray serializeNonPackedList(const QList<V> &listValue, const QByteArray &) |
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 | |
256 | template <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--------------- |
274 | template <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 | |
285 | template <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 | |
295 | template <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 | |
308 | template <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---------------- |
321 | template <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-------------------------- |
337 | template <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 | |
360 | template <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 | |
377 | QT_END_NAMESPACE |
378 | |
379 | #endif // PROTOBUFSCALARSERIALIZERS_P_H |
380 | |