diff --git a/.gitignore b/.gitignore index 3ff10e74..2b68e33b 100644 --- a/.gitignore +++ b/.gitignore @@ -137,7 +137,7 @@ CMakeCache.txt cmake_install.cmake CTestTestfile.cmake CMakeLists.txt.user -**build* +**/build/ # vscode specific .vscode/ diff --git a/src/protobuf/CMakeLists.txt b/src/protobuf/CMakeLists.txt index c4e243a4..ac413584 100644 --- a/src/protobuf/CMakeLists.txt +++ b/src/protobuf/CMakeLists.txt @@ -14,6 +14,7 @@ qt_internal_add_module(Protobuf qtprotobuflogging.cpp qtprotobuflogging_p.h qprotobufregistration.cpp qprotobufregistration.h qprotobufpropertyordering.cpp qprotobufpropertyordering.h + qprotobufpropertyorderingbuilder_p.h qtprotobuftypes.cpp qtprotobuftypes.h qprotobufoneof.cpp qprotobufoneof.h qtprotobufdefs_p.h diff --git a/src/protobuf/qprotobufpropertyordering.cpp b/src/protobuf/qprotobufpropertyordering.cpp index cf436b04..3194879d 100644 --- a/src/protobuf/qprotobufpropertyordering.cpp +++ b/src/protobuf/qprotobufpropertyordering.cpp @@ -2,7 +2,9 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qprotobufpropertyordering.h" +#include "qprotobufpropertyorderingbuilder_p.h" #include "qtprotobuflogging_p.h" +#include "qtprotobufdefs_p.h" #include "qprotobufregistration.h" @@ -212,6 +214,117 @@ QProtobufMessagePointer constructMessageByName(const QString &messageType) 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(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 diff --git a/src/protobuf/qprotobufpropertyordering.h b/src/protobuf/qprotobufpropertyordering.h index f66d1168..fb36b0db 100644 --- a/src/protobuf/qprotobufpropertyordering.h +++ b/src/protobuf/qprotobufpropertyordering.h @@ -33,6 +33,7 @@ enum FieldFlag : uint { Q_DECLARE_FLAGS(FieldFlags, FieldFlag) Q_DECLARE_OPERATORS_FOR_FLAGS(FieldFlags) +class QProtobufPropertyOrderingBuilder; struct QProtobufPropertyOrdering { const struct Data @@ -54,6 +55,7 @@ struct QProtobufPropertyOrdering int fieldCount() const { return int(data->numFields); } private: + friend class QProtobufPropertyOrderingBuilder; struct NonConstTag {}; uint *uint_data(NonConstTag) const; char *char_data(NonConstTag) const; diff --git a/src/protobuf/qprotobufpropertyorderingbuilder_p.h b/src/protobuf/qprotobufpropertyorderingbuilder_p.h new file mode 100644 index 00000000..538470d6 --- /dev/null +++ b/src/protobuf/qprotobufpropertyorderingbuilder_p.h @@ -0,0 +1,52 @@ +// 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 + +#ifndef QPROTOBUFPROPERTYORDERINGBUILDER_P_H +#define QPROTOBUFPROPERTYORDERINGBUILDER_P_H + +#include + +#include + +#include + +// Source is in qprotobufpropertyordering.cpp +// The intent is to share some of the code + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +QT_BEGIN_NAMESPACE + +namespace QtProtobufPrivate { + +class QProtobufPropertyOrderingBuilderPrivate; +class QProtobufPropertyOrderingBuilder +{ +public: + Q_PROTOBUF_EXPORT explicit QProtobufPropertyOrderingBuilder(QByteArray packageName); + Q_DISABLE_COPY_MOVE(QProtobufPropertyOrderingBuilder) + Q_PROTOBUF_EXPORT ~QProtobufPropertyOrderingBuilder(); + + Q_PROTOBUF_EXPORT void addV0Field(QByteArray jsonName, uint fieldNumber, uint propertyIndex, + FieldFlags flags); + Q_PROTOBUF_EXPORT QProtobufPropertyOrdering::Data *build() const; + +private: + QProtobufPropertyOrderingBuilderPrivate *d_ptr; + Q_DECLARE_PRIVATE(QProtobufPropertyOrderingBuilder) +}; + +} // namespace QtProtobufPrivate + +QT_END_NAMESPACE + +#endif // QPROTOBUFPROPERTYORDERINGBUILDER_P_H diff --git a/src/protobuf/qtprotobufdefs_p.h b/src/protobuf/qtprotobufdefs_p.h index 7136355f..64a3c9b4 100644 --- a/src/protobuf/qtprotobufdefs_p.h +++ b/src/protobuf/qtprotobufdefs_p.h @@ -19,6 +19,7 @@ QT_BEGIN_NAMESPACE constexpr int ProtobufFieldNumMin = 1; constexpr int ProtobufFieldNumMax = 536870911; +constexpr int ProtobufFieldMaxCount = ProtobufFieldNumMax - ProtobufFieldNumMin + 1; QT_END_NAMESPACE diff --git a/tests/auto/protobuf/CMakeLists.txt b/tests/auto/protobuf/CMakeLists.txt index d0816ed7..8fd19bd2 100644 --- a/tests/auto/protobuf/CMakeLists.txt +++ b/tests/auto/protobuf/CMakeLists.txt @@ -31,3 +31,4 @@ if(TARGET Qt6::qtprotobufgen) endif() add_subdirectory(qprotobuflazymessagepointer) add_subdirectory(qprotobufoneof) +add_subdirectory(qprotobufpropertyorderingbuilder) diff --git a/tests/auto/protobuf/qprotobufpropertyorderingbuilder/CMakeLists.txt b/tests/auto/protobuf/qprotobufpropertyorderingbuilder/CMakeLists.txt new file mode 100644 index 00000000..468c43d8 --- /dev/null +++ b/tests/auto/protobuf/qprotobufpropertyorderingbuilder/CMakeLists.txt @@ -0,0 +1,16 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qprotobufoneof LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qprotobufpropertyorderingbuilder + SOURCES + tst_qprotobufpropertyorderingbuilder.cpp + LIBRARIES + Qt::ProtobufPrivate +) + diff --git a/tests/auto/protobuf/qprotobufpropertyorderingbuilder/tst_qprotobufpropertyorderingbuilder.cpp b/tests/auto/protobuf/qprotobufpropertyorderingbuilder/tst_qprotobufpropertyorderingbuilder.cpp new file mode 100644 index 00000000..9f3e5adf --- /dev/null +++ b/tests/auto/protobuf/qprotobufpropertyorderingbuilder/tst_qprotobufpropertyorderingbuilder.cpp @@ -0,0 +1,63 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// Copyright (C) 2022 Alexey Edelev , Viktor Kopp +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include + +#include + +class tst_QProtobufPropertyOrderingBuilder : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void buildObject(); +}; + +struct DataDeleter { + void operator()(QtProtobufPrivate::QProtobufPropertyOrdering::Data *ptr) noexcept + { + free(ptr); + } +}; + +void tst_QProtobufPropertyOrderingBuilder::buildObject() +{ + using namespace QtProtobufPrivate; + QProtobufPropertyOrderingBuilder builder("TestMessage"); + builder.addV0Field("field1", 1, 0, FieldFlag::Optional); + builder.addV0Field("field6", 6, 1, FieldFlag::Repeated | FieldFlag::Enum); + builder.addV0Field("field30", 30, 60, FieldFlag::NoFlags); + std::unique_ptr data(builder.build()); + + QProtobufPropertyOrdering ordering{data.get()}; + QCOMPARE(ordering.getMessageFullName(), QByteArray("TestMessage")); + QCOMPARE(ordering.fieldCount(), 3); + + QCOMPARE(ordering.indexOfFieldNumber(1), 0); + QCOMPARE(ordering.indexOfFieldNumber(6), 1); + QCOMPARE(ordering.indexOfFieldNumber(30), 2); + QCOMPARE(ordering.indexOfFieldNumber(10), -1); + + QCOMPARE(ordering.getPropertyIndex(0), 0); + QCOMPARE(ordering.getPropertyIndex(1), 1); + QCOMPARE(ordering.getPropertyIndex(2), 60); + QCOMPARE(ordering.getPropertyIndex(3), -1); + + QCOMPARE(ordering.getFieldNumber(0), 1); + QCOMPARE(ordering.getFieldNumber(1), 6); + QCOMPARE(ordering.getFieldNumber(2), 30); + QCOMPARE(ordering.getFieldNumber(3), -1); + + QCOMPARE(ordering.getJsonName(0), QByteArray("field1")); + QCOMPARE(ordering.getJsonName(1), QByteArray("field6")); + QCOMPARE(ordering.getJsonName(2), QByteArray("field30")); + QCOMPARE(ordering.getJsonName(3), QByteArray()); + + QCOMPARE(ordering.getFieldFlags(0), FieldFlag::Optional); + QCOMPARE(ordering.getFieldFlags(1), FieldFlag::Repeated | FieldFlag::Enum); + QCOMPARE(ordering.getFieldFlags(2), FieldFlag::NoFlags); + QCOMPARE(ordering.getFieldFlags(3), FieldFlag::NoFlags); +} + +QTEST_MAIN(tst_QProtobufPropertyOrderingBuilder) +#include "tst_qprotobufpropertyorderingbuilder.moc"