// Copyright (C) 2024 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include #include #include #include #include #include #include #include #include #include #include #include namespace { /*! \internal The ProtobufOrderingRegistry stores the mapping between QtProtobufPrivate::QProtobufPropertyOrdering and QMetaType. */ struct ProtobufOrderingRegistry { using ProtobufOrderingRegistryRecord = QPair; void registerOrdering(QMetaType type, QtProtobufPrivate::QProtobufPropertyOrdering ordering) { QWriteLocker locker(&m_lock); m_registry[ordering.messageFullName().toString()] = ProtobufOrderingRegistryRecord(type, ordering); } QtProtobufPrivate::QProtobufPropertyOrdering getOrdering(QMetaType type) const { QtProtobufPrivate::QProtobufPropertyOrdering ordering{ nullptr }; QReadLocker locker(&m_lock); for (auto it = m_registry.constBegin(); it != m_registry.constEnd(); ++it) { if (type == it.value().first) { ordering = it.value().second; break; } } return ordering; } ProtobufOrderingRegistryRecord findMessageByName(const QString &messageName) const { ProtobufOrderingRegistryRecord record = { QMetaType(QMetaType::UnknownType), { nullptr } }; QReadLocker locker(&m_lock); auto it = m_registry.constFind(messageName); if (it != m_registry.constEnd()) record = it.value(); return record; } private: mutable QReadWriteLock m_lock; QHash m_registry; }; Q_GLOBAL_STATIC(ProtobufOrderingRegistry, orderingRegistry) } // namespace QT_BEGIN_NAMESPACE namespace QtProtobufPrivate { static_assert(std::is_trivially_destructible_v); static_assert(std::is_same_v, uint>); constexpr uint jsonNameOffsetsOffset = 0; // Use this constant to make the +/- 1 more easily readable constexpr int NullTerminator = 1; QUtf8StringView QProtobufPropertyOrdering::messageFullName() const { Q_ASSERT(data); const char *name = char_data(); return { name, qsizetype(data->fullPackageNameSize) }; } QUtf8StringView QProtobufPropertyOrdering::jsonName(int index) const { Q_ASSERT(data); if (Q_UNLIKELY(index < 0 || index >= fieldCount())) return {}; // Invalid index (out of bounds) const uint stringOffset = uint_dataForIndex(index, jsonNameOffsetsOffset); const char *name = char_data() + stringOffset; // This is fine because we store an extra offset for end-of-string const uint nameLength = uint_dataForIndex(index + 1, jsonNameOffsetsOffset) - NullTerminator - stringOffset; return { name, qsizetype(nameLength) }; } int QProtobufPropertyOrdering::fieldNumber(int index) const { Q_ASSERT(data); if (Q_UNLIKELY(index < 0 || index >= fieldCount())) return -1; // Invalid index (out of bounds) uint fieldNumber = uint_dataForIndex(index, data->fieldNumberOffset); if (Q_UNLIKELY(fieldNumber > uint(std::numeric_limits::max()))) return -1; return int(fieldNumber); } int QProtobufPropertyOrdering::propertyIndex(int index) const { Q_ASSERT(data); if (Q_UNLIKELY(index < 0 || index >= fieldCount())) return -1; // Invalid index (out of bounds) uint propertyIndex = uint_dataForIndex(index, data->propertyIndexOffset); if (Q_UNLIKELY(propertyIndex > uint(std::numeric_limits::max()))) return -1; return int(propertyIndex); } int QProtobufPropertyOrdering::indexOfFieldNumber(int fieldNumber) const { Q_ASSERT(data); if (Q_LIKELY(fieldNumber > 0)) { for (int i = 0; i < fieldCount(); ++i) { if (uint_dataForIndex(i, data->fieldNumberOffset) == static_cast(fieldNumber)) return i; } } return -1; } FieldFlags QProtobufPropertyOrdering::fieldFlags(int index) const { Q_ASSERT(data); if (Q_UNLIKELY(index < 0 || index >= fieldCount())) return {}; // Invalid index (out of bounds) return FieldFlags { int(uint_dataForIndex(index, data->flagsOffset)) }; } /*! \internal Access the generated uint* metadata of message properties. [ Data, uint_data , char_data ] > Message_metadata [ . . , ^ x x x x , . . . . . ] */ uint *QProtobufPropertyOrdering::uint_data(NonConstTag /*unused*/) const { Q_ASSERT(data); Q_ASSERT(data->version == 0); quintptr dataPtr = quintptr(data); dataPtr += sizeof(Data); #if 0 // if Data is ever not just a bunch of uints, remove the #if 0 if (dataPtr % alignof(uint) != 0) dataPtr += alignof(uint) - (dataPtr % alignof(uint)); #else static_assert(alignof(Data) == alignof(uint)); #endif return reinterpret_cast(dataPtr); } const uint *QProtobufPropertyOrdering::uint_data() const { return uint_data(NonConstTag{}); } /*! \internal Access the generated char* metadata of message properties. [ Data, uint_data , char_data ] > Message_metadata [ . . , . . . . . , ^ x x x x ] */ char *QProtobufPropertyOrdering::char_data(NonConstTag /*unused*/) const { Q_ASSERT(data); uint* uintDataPtr = uint_data(NonConstTag{}); // char_data starts after the last field flag (flagsOffset + numFields) uint charDataOffset = data->flagsOffset + data->numFields; return reinterpret_cast(uintDataPtr + charDataOffset); } const char *QProtobufPropertyOrdering::char_data() const { return char_data(NonConstTag{}); } const uint &QProtobufPropertyOrdering::uint_dataForIndex(int index, uint offset) const { Q_ASSERT(data); Q_ASSERT(index >= 0); Q_ASSERT(uint(index) < data->numFields || (offset == jsonNameOffsetsOffset && uint(index) == data->numFields)); return *(uint_data() + offset + index); } void registerOrdering(QMetaType type, QProtobufPropertyOrdering ordering) { orderingRegistry->registerOrdering(type, ordering); } QProtobufPropertyOrdering getOrderingByMetaType(QMetaType type) { return orderingRegistry->getOrdering(type); } QProtobufMessagePointer constructMessageByName(const QString &messageType) { qRegisterProtobufTypes(); ProtobufOrderingRegistry::ProtobufOrderingRegistryRecord messageOrderingRecord = orderingRegistry->findMessageByName(messageType); QMetaType type = messageOrderingRecord.first; QProtobufMessagePointer pointer; if (type.id() != QMetaType::UnknownType) { void *msg = type.create(); pointer.reset(static_cast(msg)); return pointer; } qProtoWarning() << "Unable to find protobuf message with name" << messageType << ". Message is not registered."; return pointer; } class QProtobufPropertyOrderingBuilderPrivate { public: struct FieldDefinition { QByteArray jsonName; uint fieldNumber; uint propertyIndex; FieldFlags flags; }; std::vector fields; QByteArray packageName; }; QProtobufPropertyOrderingBuilder::QProtobufPropertyOrderingBuilder(QByteArray packageName) : d_ptr(new QProtobufPropertyOrderingBuilderPrivate()) { d_ptr->packageName = std::move(packageName); } QProtobufPropertyOrderingBuilder::~QProtobufPropertyOrderingBuilder() { delete d_ptr; } void QProtobufPropertyOrderingBuilder::addV0Field(QByteArray jsonName, uint fieldNumber, uint propertyIndex, FieldFlags flags) { Q_D(QProtobufPropertyOrderingBuilder); Q_ASSERT_X(fieldNumber <= ProtobufFieldNumMax && fieldNumber >= ProtobufFieldNumMin, "QProtobufPropertyOrderingBuilder::addV0Field", "Field number is out of the valid field number range."); Q_ASSERT(d->fields.size() < ProtobufFieldMaxCount); d->fields.push_back({ std::move(jsonName), fieldNumber, propertyIndex, flags }); } /*! \internal Builds the QProtobufPropertyOrdering object from the builder data. The Data struct must be manually destructed (var->~Data()) and then free()d by the caller. */ QProtobufPropertyOrdering::Data *QProtobufPropertyOrderingBuilder::build() const { Q_D(const QProtobufPropertyOrderingBuilder); if (d->fields.size() > ProtobufFieldMaxCount) return nullptr; qsizetype charSpaceNeeded = NullTerminator + d->packageName.size() + NullTerminator; for (const auto &field : d->fields) charSpaceNeeded += field.jsonName.size() + NullTerminator; const size_t uintSpaceNeeded = sizeof(QProtobufPropertyOrdering::Data) + d->fields.size() * sizeof(uint) * 4 + sizeof(uint) /* eos marker offset */; static_assert(sizeof(QProtobufPropertyOrdering::Data) == 24, "Data size changed, update builder code"); const size_t spaceNeeded = uintSpaceNeeded + charSpaceNeeded; auto *storage = static_cast(calloc(1, spaceNeeded)); auto *data = new (storage) QProtobufPropertyOrdering::Data(); auto raii = qScopeGuard([data] { data->~Data(); free(data); }); data->version = 0u; data->numFields = uint(d->fields.size()); data->fieldNumberOffset = uint(d->fields.size() * 1 + 1); data->propertyIndexOffset = uint(d->fields.size() * 2 + 1); data->flagsOffset = uint(d->fields.size() * 3 + 1); data->fullPackageNameSize = uint(d->packageName.size()); using NonConstTag = QProtobufPropertyOrdering::NonConstTag; QProtobufPropertyOrdering ordering{data}; uint *uintData = ordering.uint_data(NonConstTag{}); uint jsonArrayOffset = data->fullPackageNameSize + NullTerminator; for (uint i = 0; i < data->numFields; ++i) { const auto &field = d->fields[i]; if (field.fieldNumber > ProtobufFieldNumMax || field.fieldNumber < ProtobufFieldNumMin) return nullptr; uintData[i] = jsonArrayOffset; jsonArrayOffset += field.jsonName.size() + NullTerminator; uintData[i + data->fieldNumberOffset] = field.fieldNumber; uintData[i + data->propertyIndexOffset] = field.propertyIndex; uintData[i + data->flagsOffset] = uint(field.flags.toInt()); } uintData[d->fields.size()] = jsonArrayOffset; Q_ASSERT(q20::cmp_equal(jsonArrayOffset + NullTerminator, charSpaceNeeded)); char *charData = ordering.char_data(NonConstTag{}); [[maybe_unused]] char * const charStart = charData; charData = std::copy_n(d->packageName.constData(), d->packageName.size() + NullTerminator, charData); for (auto &field : d->fields) { charData = std::copy_n(field.jsonName.constData(), field.jsonName.size() + NullTerminator, charData); } charData[0] = '\0'; // Empty string at the end of the char array ++charData; // Trailing null terminator Q_ASSERT(std::distance(charStart, charData) == charSpaceNeeded); Q_ASSERT(quintptr(charData) - quintptr(data) == quintptr(spaceNeeded)); raii.dismiss(); return data; } } // namespace QtProtobufPrivate QT_END_NAMESPACE