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

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