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#include <QtProtobuf/qprotobufpropertyordering.h>
6
7#include <QtProtobuf/private/qprotobufpropertyorderingbuilder_p.h>
8#include <QtProtobuf/private/qtprotobufdefs_p.h>
9#include <QtProtobuf/private/qtprotobuflogging_p.h>
10#include <QtProtobuf/qprotobufregistration.h>
11
12#include <QtCore/q20utility.h>
13#include <QtCore/qhash.h>
14#include <QtCore/qmetatype.h>
15#include <QtCore/qpair.h>
16#include <QtCore/qreadwritelock.h>
17#include <QtCore/qstring.h>
18
19#include <limits>
20
21namespace {
22/*!
23 \internal
24 The ProtobufOrderingRegistry stores the mapping between
25 QtProtobufPrivate::QProtobufPropertyOrdering and QMetaType.
26*/
27struct ProtobufOrderingRegistry
28{
29 using ProtobufOrderingRegistryRecord =
30 QPair<QMetaType, QtProtobufPrivate::QProtobufPropertyOrdering>;
31
32 void registerOrdering(QMetaType type, QtProtobufPrivate::QProtobufPropertyOrdering ordering)
33 {
34 QWriteLocker locker(&m_lock);
35 m_registry[ordering.messageFullName().toString()] =
36 ProtobufOrderingRegistryRecord(type, ordering);
37 }
38
39 QtProtobufPrivate::QProtobufPropertyOrdering getOrdering(QMetaType type) const
40 {
41 QtProtobufPrivate::QProtobufPropertyOrdering ordering{ .data: nullptr };
42
43 QReadLocker locker(&m_lock);
44 for (auto it = m_registry.constBegin(); it != m_registry.constEnd(); ++it) {
45 if (type == it.value().first) {
46 ordering = it.value().second;
47 break;
48 }
49 }
50
51 return ordering;
52 }
53
54 ProtobufOrderingRegistryRecord
55 findMessageByName(const QString &messageName) const
56 {
57 ProtobufOrderingRegistryRecord record = {
58 QMetaType(QMetaType::UnknownType), { .data: nullptr }
59 };
60
61 QReadLocker locker(&m_lock);
62 auto it = m_registry.constFind(key: messageName);
63 if (it != m_registry.constEnd())
64 record = it.value();
65 return record;
66 }
67
68private:
69 mutable QReadWriteLock m_lock;
70 QHash<QString, ProtobufOrderingRegistryRecord> m_registry;
71};
72
73Q_GLOBAL_STATIC(ProtobufOrderingRegistry, orderingRegistry)
74
75} // namespace
76
77QT_BEGIN_NAMESPACE
78
79namespace QtProtobufPrivate {
80
81static_assert(std::is_trivially_destructible_v<QProtobufPropertyOrdering>);
82static_assert(std::is_same_v<std::underlying_type_t<QtProtobufPrivate::FieldFlag>, uint>);
83
84constexpr uint jsonNameOffsetsOffset = 0;
85// Use this constant to make the +/- 1 more easily readable
86constexpr int NullTerminator = 1;
87QUtf8StringView QProtobufPropertyOrdering::messageFullName() const
88{
89 Q_ASSERT(data);
90 const char *name = char_data();
91 return { name, qsizetype(data->fullPackageNameSize) };
92}
93
94QUtf8StringView QProtobufPropertyOrdering::jsonName(int index) const
95{
96 Q_ASSERT(data);
97 if (Q_UNLIKELY(index < 0 || index >= fieldCount()))
98 return {}; // Invalid index (out of bounds)
99 const uint stringOffset = uint_dataForIndex(index, offset: jsonNameOffsetsOffset);
100 const char *name = char_data() + stringOffset;
101 // This is fine because we store an extra offset for end-of-string
102 const uint nameLength =
103 uint_dataForIndex(index: index + 1, offset: jsonNameOffsetsOffset) - NullTerminator - stringOffset;
104 return { name, qsizetype(nameLength) };
105}
106
107int QProtobufPropertyOrdering::fieldNumber(int index) const
108{
109 Q_ASSERT(data);
110 if (Q_UNLIKELY(index < 0 || index >= fieldCount()))
111 return -1; // Invalid index (out of bounds)
112 uint fieldNumber = uint_dataForIndex(index, offset: data->fieldNumberOffset);
113 if (Q_UNLIKELY(fieldNumber > uint(std::numeric_limits<int>::max())))
114 return -1;
115 return int(fieldNumber);
116}
117
118int QProtobufPropertyOrdering::propertyIndex(int index) const
119{
120 Q_ASSERT(data);
121 if (Q_UNLIKELY(index < 0 || index >= fieldCount()))
122 return -1; // Invalid index (out of bounds)
123 uint propertyIndex = uint_dataForIndex(index, offset: data->propertyIndexOffset);
124 if (Q_UNLIKELY(propertyIndex > uint(std::numeric_limits<int>::max())))
125 return -1;
126 return int(propertyIndex);
127}
128
129int QProtobufPropertyOrdering::indexOfFieldNumber(int fieldNumber) const
130{
131 Q_ASSERT(data);
132 if (Q_LIKELY(fieldNumber > 0)) {
133 for (int i = 0; i < fieldCount(); ++i) {
134 if (uint_dataForIndex(index: i, offset: data->fieldNumberOffset) == static_cast<uint>(fieldNumber))
135 return i;
136 }
137 }
138 return -1;
139}
140
141FieldFlags QProtobufPropertyOrdering::fieldFlags(int index) const
142{
143 Q_ASSERT(data);
144 if (Q_UNLIKELY(index < 0 || index >= fieldCount()))
145 return {}; // Invalid index (out of bounds)
146 return FieldFlags { int(uint_dataForIndex(index, offset: data->flagsOffset)) };
147}
148
149/*!
150 \internal
151 Access the generated uint* metadata of message properties.
152 [ Data, uint_data , char_data ] > Message_metadata
153 [ . . , ^ x x x x , . . . . . ]
154*/
155uint *QProtobufPropertyOrdering::uint_data(NonConstTag /*unused*/) const
156{
157 Q_ASSERT(data);
158 Q_ASSERT(data->version == 0);
159 quintptr dataPtr = quintptr(data);
160 dataPtr += sizeof(Data);
161#if 0 // if Data is ever not just a bunch of uints, remove the #if 0
162 if (dataPtr % alignof(uint) != 0)
163 dataPtr += alignof(uint) - (dataPtr % alignof(uint));
164#else
165 static_assert(alignof(Data) == alignof(uint));
166#endif
167 return reinterpret_cast<uint *>(dataPtr);
168}
169
170const uint *QProtobufPropertyOrdering::uint_data() const
171{
172 return uint_data(NonConstTag{});
173}
174
175/*!
176 \internal
177 Access the generated char* metadata of message properties.
178 [ Data, uint_data , char_data ] > Message_metadata
179 [ . . , . . . . . , ^ x x x x ]
180*/
181char *QProtobufPropertyOrdering::char_data(NonConstTag /*unused*/) const
182{
183 Q_ASSERT(data);
184 uint* uintDataPtr = uint_data(NonConstTag{});
185 // char_data starts after the last field flag (flagsOffset + numFields)
186 uint charDataOffset = data->flagsOffset + data->numFields;
187 return reinterpret_cast<char *>(uintDataPtr + charDataOffset);
188}
189
190const char *QProtobufPropertyOrdering::char_data() const
191{
192 return char_data(NonConstTag{});
193}
194
195const uint &QProtobufPropertyOrdering::uint_dataForIndex(int index, uint offset) const
196{
197 Q_ASSERT(data);
198 Q_ASSERT(index >= 0);
199 Q_ASSERT(uint(index) < data->numFields
200 || (offset == jsonNameOffsetsOffset && uint(index) == data->numFields));
201 return *(uint_data() + offset + index);
202}
203
204void registerOrdering(QMetaType type, QProtobufPropertyOrdering ordering)
205{
206 orderingRegistry->registerOrdering(type, ordering);
207}
208
209QProtobufPropertyOrdering getOrderingByMetaType(QMetaType type)
210{
211 return orderingRegistry->getOrdering(type);
212}
213
214QProtobufMessagePointer constructMessageByName(const QString &messageType)
215{
216 qRegisterProtobufTypes();
217 ProtobufOrderingRegistry::ProtobufOrderingRegistryRecord messageOrderingRecord =
218 orderingRegistry->findMessageByName(messageName: messageType);
219 QMetaType type = messageOrderingRecord.first;
220 QProtobufMessagePointer pointer;
221 if (type.id() != QMetaType::UnknownType) {
222 void *msg = type.create();
223 pointer.reset(p: static_cast<QProtobufMessage *>(msg));
224 return pointer;
225 }
226 qProtoWarning() << "Unable to find protobuf message with name" << messageType
227 << ". Message is not registered.";
228 return pointer;
229}
230
231class QProtobufPropertyOrderingBuilderPrivate
232{
233public:
234 struct FieldDefinition
235 {
236 QByteArray jsonName;
237 uint fieldNumber;
238 uint propertyIndex;
239 FieldFlags flags;
240 };
241
242 std::vector<FieldDefinition> fields;
243 QByteArray packageName;
244};
245
246QProtobufPropertyOrderingBuilder::QProtobufPropertyOrderingBuilder(QByteArray packageName)
247 : d_ptr(new QProtobufPropertyOrderingBuilderPrivate())
248{
249
250 d_ptr->packageName = std::move(packageName);
251}
252
253QProtobufPropertyOrderingBuilder::~QProtobufPropertyOrderingBuilder()
254{
255 delete d_ptr;
256}
257
258void QProtobufPropertyOrderingBuilder::addV0Field(QByteArray jsonName, uint fieldNumber,
259 uint propertyIndex, FieldFlags flags)
260{
261 Q_D(QProtobufPropertyOrderingBuilder);
262 Q_ASSERT_X(fieldNumber <= ProtobufFieldNumMax && fieldNumber >= ProtobufFieldNumMin,
263 "QProtobufPropertyOrderingBuilder::addV0Field",
264 "Field number is out of the valid field number range.");
265 Q_ASSERT(d->fields.size() < ProtobufFieldMaxCount);
266 d->fields.push_back(x: { .jsonName: std::move(jsonName), .fieldNumber: fieldNumber, .propertyIndex: propertyIndex, .flags: flags });
267}
268
269/*!
270 \internal
271 Builds the QProtobufPropertyOrdering object from the builder data.
272
273 The Data struct must be manually destructed (var->~Data()) and then
274 free()d by the caller.
275*/
276QProtobufPropertyOrdering::Data *QProtobufPropertyOrderingBuilder::build() const
277{
278 Q_D(const QProtobufPropertyOrderingBuilder);
279
280 if (d->fields.size() > ProtobufFieldMaxCount)
281 return nullptr;
282
283 qsizetype charSpaceNeeded = NullTerminator + d->packageName.size() + NullTerminator;
284 for (const auto &field : d->fields)
285 charSpaceNeeded += field.jsonName.size() + NullTerminator;
286
287 const size_t uintSpaceNeeded = sizeof(QProtobufPropertyOrdering::Data)
288 + d->fields.size() * sizeof(uint) * 4 + sizeof(uint) /* eos marker offset */;
289
290 static_assert(sizeof(QProtobufPropertyOrdering::Data) == 24,
291 "Data size changed, update builder code");
292
293 const size_t spaceNeeded = uintSpaceNeeded + charSpaceNeeded;
294
295 auto *storage = static_cast<char *>(calloc(nmemb: 1, size: spaceNeeded));
296 auto *data = new (storage) QProtobufPropertyOrdering::Data();
297 auto raii = qScopeGuard(f: [data] { data->~Data(); free(ptr: data); });
298
299 data->version = 0u;
300 data->numFields = uint(d->fields.size());
301 data->fieldNumberOffset = uint(d->fields.size() * 1 + 1);
302 data->propertyIndexOffset = uint(d->fields.size() * 2 + 1);
303 data->flagsOffset = uint(d->fields.size() * 3 + 1);
304 data->fullPackageNameSize = uint(d->packageName.size());
305
306 using NonConstTag = QProtobufPropertyOrdering::NonConstTag;
307 QProtobufPropertyOrdering ordering{.data: data};
308
309 uint *uintData = ordering.uint_data(NonConstTag{});
310 uint jsonArrayOffset = data->fullPackageNameSize + NullTerminator;
311 for (uint i = 0; i < data->numFields; ++i) {
312 const auto &field = d->fields[i];
313 if (field.fieldNumber > ProtobufFieldNumMax || field.fieldNumber < ProtobufFieldNumMin)
314 return nullptr;
315
316 uintData[i] = jsonArrayOffset;
317 jsonArrayOffset += field.jsonName.size() + NullTerminator;
318 uintData[i + data->fieldNumberOffset] = field.fieldNumber;
319 uintData[i + data->propertyIndexOffset] = field.propertyIndex;
320 uintData[i + data->flagsOffset] = uint(field.flags.toInt());
321 }
322 uintData[d->fields.size()] = jsonArrayOffset;
323 Q_ASSERT(q20::cmp_equal(jsonArrayOffset + NullTerminator, charSpaceNeeded));
324
325 char *charData = ordering.char_data(NonConstTag{});
326 [[maybe_unused]]
327 char * const charStart = charData;
328 charData = std::copy_n(first: d->packageName.constData(), n: d->packageName.size() + NullTerminator,
329 result: charData);
330 for (auto &field : d->fields) {
331 charData = std::copy_n(first: field.jsonName.constData(), n: field.jsonName.size() + NullTerminator,
332 result: charData);
333 }
334 charData[0] = '\0'; // Empty string at the end of the char array
335 ++charData; // Trailing null terminator
336 Q_ASSERT(std::distance(charStart, charData) == charSpaceNeeded);
337 Q_ASSERT(quintptr(charData) - quintptr(data) == quintptr(spaceNeeded));
338
339 raii.dismiss();
340 return data;
341}
342} // namespace QtProtobufPrivate
343
344QT_END_NAMESPACE
345

source code of qtgrpc/src/protobuf/qprotobufpropertyordering.cpp