Prechádzať zdrojové kódy

Implement repeated fields serialization

- Refactor serialization methods
- Implement variantlist types serialization
- Fix few serialization issues
- Implement Repeated Fields serialization tests
Alexey Edelev 6 rokov pred
rodič
commit
8350fc4e0c

+ 121 - 86
src/lib/protobufobject.h

@@ -35,17 +35,20 @@
 #include <memory>
 #include <type_traits>
 
-#define ASSERT_FIELD_NUMBER(X) Q_ASSERT_X(X < 128 && X > 0, T::staticMetaObject.className(), "fieldIndex is out of range")
+#define ASSERT_FIELD_NUMBER(X) Q_ASSERT_X(X < 128 && X > 0 && X != NotUsedFieldIndex, T::staticMetaObject.className(), "fieldIndex is out of range")
 
 namespace qtprotobuf {
 
 enum WireTypes {
+    UnknownWireType = -1,
     Varint = 0,
     Fixed64 = 1,
     LengthDelimited = 2,
     Fixed32 = 5
 };
 
+constexpr int NotUsedFieldIndex = -1;
+
 class ProtobufObjectPrivate : public QObject {
 protected:
     explicit ProtobufObjectPrivate(QObject *parent = nullptr) : QObject(parent) {}
@@ -60,118 +63,115 @@ protected:
         return *(char *)&header;
     }
 
-public:
-    virtual QByteArray serializePrivate() = 0;
-};
-
-template <typename T>
-class ProtobufObject : public ProtobufObjectPrivate
-{
-protected:
-    QByteArray serializePrivate() override {
-        return serialize();
-    }
-
-public:
-    explicit ProtobufObject(QObject *parent = nullptr) : ProtobufObjectPrivate(parent) {}
-
-    QByteArray serialize() {
+    inline QByteArray serializeValue(const QVariant& propertyValue, int fieldIndex) {
         QByteArray result;
-        T* instance = dynamic_cast<T*>(this);
-        for (auto field : T::propertyOrdering) {
-            int propertyIndex = field.second;
-            int fieldIndex = field.first;
-            QMetaProperty metaProperty = T::staticMetaObject.property(propertyIndex);
-            const char* propertyName = metaProperty.name();
-            const QVariant& propertyValue = instance->property(propertyName);
-            switch (metaProperty.type()) {
-            case QMetaType::Int:
-                result.append(serializeVarint(propertyValue.toInt(), fieldIndex));
-                break;
-            case QMetaType::Float:
-                result.append(serializeFixed(propertyValue.toFloat(), fieldIndex));
-                break;
-            case QMetaType::Double:
-                result.append(serializeFixed(propertyValue.toDouble(), fieldIndex));
-                break;
-            case QMetaType::QString:
-                result.append(serializeLengthDelimited(propertyValue.toString().toUtf8(), fieldIndex));
-                break;
-            case QMetaType::QByteArray:
-                result.append(serializeLengthDelimited(propertyValue.toByteArray(), fieldIndex));
-                break;
-            case QMetaType::QVariantList:
-                //TODO: implement lists serialization
-                break;
-            case QMetaType::User: {
-                int userType = metaProperty.userType();
-                const void *src = propertyValue.constData();
-                //TODO: each time huge objects will make own copies
-                //Probably generate fields reflection is better solution
-                auto value = std::unique_ptr<ProtobufObjectPrivate>(reinterpret_cast<ProtobufObjectPrivate *>(QMetaType::create(userType, src)));
-                result.append(serializeLengthDelimited(value->serializePrivate(),
-                                  fieldIndex));
-            }
-                break;
-            default:
-                Q_ASSERT_X(false, T::staticMetaObject.className(), "Serialization of unknown type is impossible");
-            }
+        WireTypes type = UnknownWireType;
+        switch (propertyValue.type()) {
+        case QMetaType::Int:
+            type = Varint;
+            result.append(serializeVarint(propertyValue.toInt()));
+            break;
+        case QMetaType::Float:
+            type = Fixed32;
+            result.append(serializeFixed(propertyValue.toFloat()));
+            break;
+        case QMetaType::Double:
+            type = Fixed64;
+            result.append(serializeFixed(propertyValue.toDouble()));
+            break;
+        case QMetaType::QString:
+            type = LengthDelimited;
+            result.append(serializeLengthDelimited(propertyValue.toString().toUtf8()));
+            break;
+        case QMetaType::QByteArray:
+            type = LengthDelimited;
+            result.append(serializeLengthDelimited(propertyValue.toByteArray()));
+            break;
+        //TODO: explicit QList<TypeName> is more user friendly and performance efficient
+        case QMetaType::QVariantList:
+            type = LengthDelimited;
+            result.append(serializeListType(propertyValue.toList(), fieldIndex));
+            break;
+        case QMetaType::User:
+            type = LengthDelimited;
+            result.append(serializeUserType(propertyValue));
+            break;
+        default:
+            Q_ASSERT_X(false, staticMetaObject.className(), "Serialization of unknown type is impossible");
+        }
+        if (fieldIndex != NotUsedFieldIndex
+                && type != UnknownWireType) {
+            result.prepend(getTypeByte(fieldIndex, type));
         }
-
         return result;
     }
 
-    void deserialize(const QByteArray& array) {
-        T* instance = dynamic_cast<T*>(this);
-        //TODO
-    }
-
-    QByteArray serializeLengthDelimited(const QByteArray& data, int fieldIndex) {
-        ASSERT_FIELD_NUMBER(fieldIndex);
+    QByteArray serializeLengthDelimited(const QByteArray& data) {
         //Varint serialize field size and apply result as starting point
-        QByteArray result = serializeVarint(static_cast<unsigned int>(data.size()), 1/*field number doesn't matter*/);
-        result[0] = getTypeByte(fieldIndex, LengthDelimited);
+        QByteArray result = serializeVarint(static_cast<unsigned int>(data.size()));
         result.append(data);
         return result;
     }
 
+    QByteArray serializeUserType(const QVariant& propertyValue) {
+        int userType = propertyValue.userType();
+        Q_ASSERT_X(QMetaType::UnknownType == userType, staticMetaObject.className(), "Serialization of unknown user type");
+        const void *src = propertyValue.constData();
+        //TODO: each time huge objects will make own copies
+        //Probably generate fields reflection is better solution
+        auto value = std::unique_ptr<ProtobufObjectPrivate>(reinterpret_cast<ProtobufObjectPrivate *>(QMetaType::create(userType, src)));
+        return serializeLengthDelimited(value->serializePrivate());
+    }
+
+    QByteArray serializeListType(const QVariantList& listValue, int &outFieldIndex)
+    {
+        if (listValue.count() <= 0) {
+            outFieldIndex = NotUsedFieldIndex;
+            return QByteArray();
+        }
+        int itemType = listValue.first().type();
+        //If internal serialized type is LengthDelimited, need to specify type for each field
+        int inFieldIndex = (itemType == QMetaType::QString ||
+                      itemType == QMetaType::QByteArray ||
+                      itemType == QMetaType::User) ? outFieldIndex : NotUsedFieldIndex;
+        QByteArray serializedList;
+        for(auto& value : listValue) {
+            serializedList.append(serializeValue(value, inFieldIndex));
+        }
+        //If internal field type is not LengthDelimited, exact amount of fields to be specified
+        if(inFieldIndex == NotUsedFieldIndex) {
+            serializedList.prepend(serializeVarint(static_cast<unsigned int>(serializedList.size())));
+        } else {
+            outFieldIndex = NotUsedFieldIndex;
+        }
+        return serializedList;
+    }
+
     template <typename V,
               typename std::enable_if_t<std::is_floating_point<V>::value, int> = 0>
-    QByteArray serializeFixed(V value, int fieldIndex) {
-        ASSERT_FIELD_NUMBER(fieldIndex);
-
+    QByteArray serializeFixed(V value) {
         //Reserve required amount of bytes
-        QByteArray result(sizeof(V) + 1, '\0');
-
-        //Undentify exact wiretype
-        constexpr WireTypes wireType = sizeof(V) == 4 ? Fixed32 : Fixed64;
-        result[0] = getTypeByte(fieldIndex, wireType);
-        *(V*)&(result.data()[1]) = value;
+        QByteArray result(sizeof(V), '\0');
+        *(V*)(result.data()) = value;
         return result;
     }
 
     template <typename V,
               typename std::enable_if_t<std::is_signed<V>::value, int> = 0>
-    QByteArray serializeVarint(V value, int fieldIndex) {
-        ASSERT_FIELD_NUMBER(fieldIndex);
-
+    QByteArray serializeVarint(V value) {
         using UV = typename std::make_unsigned<V>::type;
         //Use ZigZag convertion first and apply unsigned variant next
         value = (value << 1) ^ (value >> (sizeof(UV) * 8 - 1));
         UV uValue = *(UV *)&value;
-        return serializeVarint(uValue, fieldIndex);
+        return serializeVarint(uValue);
     }
 
     template <typename V,
               typename std::enable_if_t<std::is_unsigned<V>::value, int> = 0>
-    QByteArray serializeVarint(V value, int fieldIndex) {
-        ASSERT_FIELD_NUMBER(fieldIndex);
+    QByteArray serializeVarint(V value) {
         QByteArray result;
         //Reserve maximum required amount of bytes
-        result.reserve(sizeof(V) + 1);
-        //Put type byte at beginning
-        result.append(getTypeByte(fieldIndex, Varint));
-
+        result.reserve(sizeof(V));
         while (value > 0) {
             //Put first 7 bits to result buffer and mark as not last
             result.append(value & 0x7F | 0x80);
@@ -180,7 +180,7 @@ public:
         }
 
         //Zero case
-        if (result.size() == 1) {
+        if (result.isEmpty()) {
             result.append('\0');
         }
 
@@ -188,6 +188,41 @@ public:
         result.data()[result.size() - 1] &= ~0x80;
         return result;
     }
+public:
+    virtual QByteArray serializePrivate() = 0;
+};
+
+template <typename T>
+class ProtobufObject : public ProtobufObjectPrivate
+{
+protected:
+    QByteArray serializePrivate() override {
+        return serialize();
+    }
+
+public:
+    explicit ProtobufObject(QObject *parent = nullptr) : ProtobufObjectPrivate(parent) {}
+
+    QByteArray serialize() {
+        QByteArray result;
+        T* instance = dynamic_cast<T*>(this);
+        for (auto field : T::propertyOrdering) {
+            int propertyIndex = field.second;
+            int fieldIndex = field.first;
+            ASSERT_FIELD_NUMBER(fieldIndex);
+            QMetaProperty metaProperty = T::staticMetaObject.property(propertyIndex);
+            const char* propertyName = metaProperty.name();
+            const QVariant& propertyValue = instance->property(propertyName);
+            result.append(serializeValue(propertyValue, fieldIndex));
+        }
+
+        return result;
+    }
+
+    void deserialize(const QByteArray& array) {
+        T* instance = dynamic_cast<T*>(this);
+        //TODO
+    }
 };
 
 }

+ 9 - 1
tests/proto/simpletest.proto

@@ -28,7 +28,15 @@ message ComplexMessage {
 }
 
 message RepeatedIntMessage {
-    repeated int32 testRepeatedInt = 1;
+    repeated sint32 testRepeatedInt = 1;
+}
+
+message RepeatedStringMessage {
+    repeated string testRepeatedString = 1;
+}
+
+message RepeatedDoubleMessage {
+    repeated double testRepeatedDouble = 1;
 }
 
 enum TestEnum {

+ 44 - 0
tests/serializationtest.cpp

@@ -30,6 +30,9 @@
 #include "simpledoublemessage.h"
 #include "simplestringmessage.h"
 #include "complexmessage.h"
+#include "repeatedintmessage.h"
+#include "repeatedstringmessage.h"
+#include "repeateddoublemessage.h"
 
 using namespace qtprotobuf::tests;
 
@@ -334,3 +337,44 @@ TEST_F(SerializationTest, ComplexTypeSerializeTest)
     ASSERT_TRUE(result == QByteArray::fromHex("120832067177657274790859")
                 || result == QByteArray::fromHex("085912083206717765727479"));
 }
+
+TEST_F(SerializationTest, RepeatedIntMessageTest)
+{
+    RepeatedIntMessage test;
+    test.setTestRepeatedInt({1,400,3,4,5,6});
+    QByteArray result = test.serialize();
+//    qDebug() << "result " << result.toHex();
+    ASSERT_TRUE(result == QByteArray::fromHex("0a0702a00606080a0c"));
+
+    test.setTestRepeatedInt(QVariantList());
+    result = test.serialize();
+    ASSERT_TRUE(result.isEmpty());
+}
+
+TEST_F(SerializationTest, RepeatedStringMessage)
+{
+    RepeatedStringMessage test;
+    test.setTestRepeatedString({"aaaa","bbbbb","ccc","dddddd","eeeee"});
+    QByteArray result = test.serialize();
+//    qDebug() << "result " << result.toHex();
+    ASSERT_TRUE(result == QByteArray::fromHex("0a04616161610a0562626262620a036363630a066464646464640a056565656565"));
+
+    test.setTestRepeatedString(QVariantList());
+    result = test.serialize();
+    ASSERT_TRUE(result.isEmpty());
+}
+
+TEST_F(SerializationTest, RepeatedDoubleMessage)
+{
+    RepeatedDoubleMessage test;
+    test.setTestRepeatedDouble({QVariant::fromValue<double>(0.1), QVariant::fromValue<double>(0.2),
+                                QVariant::fromValue<double>(0.3), QVariant::fromValue<double>(0.4),
+                                QVariant::fromValue<double>(0.5)});
+    QByteArray result = test.serialize();
+//    qDebug() << "result " << result.toHex();
+    ASSERT_TRUE(result == QByteArray::fromHex("0a289a9999999999b93f9a9999999999c93f333333333333d33f9a9999999999d93f000000000000e03f"));
+
+    test.setTestRepeatedDouble(QVariantList());
+    result = test.serialize();
+    ASSERT_TRUE(result.isEmpty());
+}