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 | |
19 | namespace { |
20 | /*! |
21 | \internal |
22 | The ProtobufOrderingRegistry stores the mapping between |
23 | QtProtobufPrivate::QProtobufPropertyOrdering and QMetaType. |
24 | */ |
25 | struct 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 | |
66 | private: |
67 | mutable QReadWriteLock m_lock; |
68 | QHash<QString, ProtobufOrderingRegistryRecord> m_registry; |
69 | }; |
70 | |
71 | Q_GLOBAL_STATIC(ProtobufOrderingRegistry, orderingRegistry) |
72 | } |
73 | |
74 | QT_BEGIN_NAMESPACE |
75 | |
76 | namespace QtProtobufPrivate { |
77 | |
78 | static_assert(std::is_trivially_destructible_v<QProtobufPropertyOrdering>); |
79 | static_assert(std::is_same_v<std::underlying_type_t<QtProtobufPrivate::FieldFlag>, uint>); |
80 | |
81 | constexpr uint jsonNameOffsetsOffset = 0; |
82 | // Use this constant to make the +/- 1 more easily readable |
83 | constexpr int NullTerminator = 1; |
84 | QUtf8StringView QProtobufPropertyOrdering::messageFullName() const |
85 | { |
86 | Q_ASSERT(data); |
87 | const char *name = char_data(); |
88 | return { name, qsizetype(data->fullPackageNameSize) }; |
89 | } |
90 | |
91 | QUtf8StringView 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 | |
104 | int 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 | |
115 | int 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 | |
126 | int 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 | |
138 | FieldFlags 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 | |
146 | uint *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 | |
162 | const uint *QProtobufPropertyOrdering::uint_data() const |
163 | { |
164 | return uint_data(: NonConstTag{}); |
165 | } |
166 | |
167 | char *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 | |
180 | const char *QProtobufPropertyOrdering::char_data() const |
181 | { |
182 | return char_data(: NonConstTag{}); |
183 | } |
184 | |
185 | const 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 | |
194 | void registerOrdering(QMetaType type, QProtobufPropertyOrdering ordering) |
195 | { |
196 | orderingRegistry->registerOrdering(type, ordering); |
197 | } |
198 | |
199 | QProtobufPropertyOrdering getOrderingByMetaType(QMetaType type) |
200 | { |
201 | return orderingRegistry->getOrdering(type); |
202 | } |
203 | |
204 | QProtobufMessagePointer 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 | |
221 | class QProtobufPropertyOrderingBuilderPrivate |
222 | { |
223 | public: |
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 | |
236 | QProtobufPropertyOrderingBuilder::QProtobufPropertyOrderingBuilder(QByteArray packageName) |
237 | : d_ptr(new QProtobufPropertyOrderingBuilderPrivate()) |
238 | { |
239 | |
240 | d_ptr->packageName = std::move(packageName); |
241 | } |
242 | |
243 | QProtobufPropertyOrderingBuilder::~QProtobufPropertyOrderingBuilder() |
244 | { |
245 | delete d_ptr; |
246 | } |
247 | |
248 | void 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 | */ |
266 | QProtobufPropertyOrdering::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 | |
334 | QT_END_NAMESPACE |
335 | |