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

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