Browse Source

Implement deserialization for fixed int32/64 types

Alexey Edelev 6 years ago
parent
commit
44a8fc672c
4 changed files with 227 additions and 16 deletions
  1. 74 15
      src/lib/protobufobject.h
  2. 2 1
      tests/CMakeLists.txt
  3. 111 0
      tests/deserializationtest.cpp
  4. 40 0
      tests/deserializationtest.h

+ 74 - 15
src/lib/protobufobject.h

@@ -54,16 +54,26 @@ class ProtobufObjectPrivate : public QObject {
 protected:
     explicit ProtobufObjectPrivate(QObject *parent = nullptr) : QObject(parent) {}
 
-    inline static unsigned char getTypeByte(int fieldIndex, WireTypes wireType) {
-        /*  Header byte
-         *  bits    | 7  6  5  4  3 |  2  1  0
-         *  -----------------------------------
-         *  meaning |  Field index  |   Type
-         */
+    /*  Header byte
+     *  bits    | 7  6  5  4  3 |  2  1  0
+     *  -----------------------------------
+     *  meaning |  Field index  |   Type
+     */
+    inline static unsigned char encodeHeaderByte(int fieldIndex, WireTypes wireType) {
+
         unsigned char header = (fieldIndex << 3) | wireType;
         return *(char *)&header;
     }
 
+    inline static bool decodeHeaderByte(unsigned char typeByte, int &fieldIndex, WireTypes &wireType) {
+        wireType = static_cast<WireTypes>(typeByte & 0x07);
+        fieldIndex = typeByte >> 3;
+        return fieldIndex < 128 && fieldIndex > 0 && (wireType == Varint
+                                                      || wireType == Fixed64
+                                                      || wireType == Fixed32
+                                                      || wireType == LengthDelimited);
+    }
+
     QByteArray serializeValue(const QVariant& propertyValue, int fieldIndex, bool isFixed = false) {
         QByteArray result;
         WireTypes type = UnknownWireType;
@@ -126,19 +136,19 @@ protected:
         }
         if (fieldIndex != NotUsedFieldIndex
                 && type != UnknownWireType) {
-            result.prepend(getTypeByte(fieldIndex, type));
+            result.prepend(encodeHeaderByte(fieldIndex, type));
         }
         return result;
     }
 
-    QByteArray serializeLengthDelimited(const QByteArray& data) {
+    QByteArray serializeLengthDelimited(const QByteArray &data) {
         //Varint serialize field size and apply result as starting point
         QByteArray result = serializeVarint(static_cast<unsigned int>(data.size()));
         result.append(data);
         return result;
     }
 
-    QByteArray serializeUserType(const QVariant& propertyValue, int& fieldIndex) {
+    QByteArray serializeUserType(const QVariant &propertyValue, int &fieldIndex) {
         int userType = propertyValue.userType();
         if (userType == qMetaTypeId<IntList>()) {
             return serializeListType(propertyValue.value<IntList>(), fieldIndex);
@@ -265,6 +275,8 @@ protected:
     }
 public:
     virtual QByteArray serializePrivate() = 0;
+    virtual void deserializePrivate(QByteArray::iterator &it) = 0;
+
 };
 
 template <typename T>
@@ -275,19 +287,23 @@ protected:
         return serialize();
     }
 
+    void deserializePrivate(QByteArray::iterator &it) override {
+
+    }
+
 public:
     explicit ProtobufObject(QObject *parent = nullptr) : ProtobufObjectPrivate(parent) {}
 
     QByteArray serialize() {
         QByteArray result;
-        T* instance = dynamic_cast<T*>(this);
+        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);
+            const char *propertyName = metaProperty.name();
+            const QVariant &propertyValue = instance->property(propertyName);
             //TODO: flag isFixed looks ugly. Need to define more effective strategy
             //for type detection.
             result.append(serializeValue(propertyValue, fieldIndex, QString(metaProperty.typeName()).contains("Fixed")));
@@ -296,9 +312,52 @@ public:
         return result;
     }
 
-    void deserialize(const QByteArray& array) {
-        T* instance = dynamic_cast<T*>(this);
-        //TODO
+    void deserialize(const QByteArray &array) {
+        T *instance = dynamic_cast<T *>(this);
+
+        for(QByteArray::const_iterator it = array.begin(); it != array.end();) {
+            //Each iteration we expect iterator is setup to beginning of next chunk
+            int fieldNumber = NotUsedFieldIndex;
+            WireTypes wireType = UnknownWireType;
+            if (!decodeHeaderByte(*it, fieldNumber, wireType)) {
+                ++it;
+                qCritical() << "Message received doesn't contains valid header byte. "
+                               "Trying next, but seems stream is broken";
+                continue;
+            }
+
+            auto propertyNumberIt = T::propertyOrdering.find(fieldNumber);
+            if (propertyNumberIt == std::end(T::propertyOrdering)) {
+                ++it;
+                qCritical() << "Message received contains invalid field number. "
+                               "Trying next, but seems stream is broken";
+                continue;
+            }
+
+            int propertyIndex = propertyNumberIt->second;
+            QMetaProperty metaProperty = T::staticMetaObject.property(propertyIndex);
+
+            ++it;
+            deserializeProperty(metaProperty, it);
+        }
+    }
+
+    void deserializeProperty(const QMetaProperty &metaProperty, QByteArray::const_iterator &it)
+    {
+        QVariant newPropertyValue;
+        switch(metaProperty.userType()) {
+        case QMetaType::UInt:
+            //TODO: replace with template function for all fixed types
+            newPropertyValue.setValue(*(unsigned int*)it);
+            it += sizeof(unsigned int);
+            break;
+        case QMetaType::ULongLong:
+            //TODO: replace with template function for all fixed types
+            newPropertyValue.setValue(*(qulonglong*)it);
+            it += sizeof(qulonglong);
+            break;
+        }
+        setProperty(metaProperty.name(), newPropertyValue);
     }
 };
 

+ 2 - 1
tests/CMakeLists.txt

@@ -51,7 +51,8 @@ endif()
 file(GLOB HEADERS ${TESTS_OUT_DIR})
 file(GLOB SOURCES main.cpp
     simpletest.cpp
-    serializationtest.cpp)
+    serializationtest.cpp
+    deserializationtest.cpp)
 
 file(GLOB PROTOS proto/*.proto)
 

+ 111 - 0
tests/deserializationtest.cpp

@@ -0,0 +1,111 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 Alexey Edelev <semlanik@gmail.com>
+ *
+ * This file is part of qtprotobuf project https://git.semlanik.org/semlanik/qtprotobuf
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+ * to permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "deserializationtest.h"
+#include "qtprotobuf.h"
+#include "simplefixedint32message.h"
+#include "simplefixedint64message.h"
+
+using namespace qtprotobufnamespace::tests;
+using namespace qtprotobuf::tests;
+using namespace qtprotobuf;
+
+DeserializationTest::DeserializationTest()
+{
+    QtProtobuf::init();
+}
+
+TEST_F(DeserializationTest, FixedInt32MessageDeserializeTest)
+{
+    SimpleFixedInt32Message test;
+    test.deserialize(QByteArray::fromHex("0d0f000000"));
+    ASSERT_EQ(test.testFieldFixedInt32(), 15);
+
+    test.deserialize(QByteArray::fromHex("0d2c010000"));
+    ASSERT_EQ(test.testFieldFixedInt32(), 300);
+
+    test.deserialize(QByteArray::fromHex("0d09000100"));
+    ASSERT_EQ(test.testFieldFixedInt32(), 65545);
+
+    test.deserialize(QByteArray::fromHex("0d00000000"));
+    ASSERT_EQ(test.testFieldFixedInt32(), 0);
+
+    test.deserialize(QByteArray::fromHex("0d2c010000"));
+    ASSERT_EQ(test.testFieldFixedInt32(), 300);
+
+    test.deserialize(QByteArray::fromHex("0d00010000"));
+    ASSERT_EQ(test.testFieldFixedInt32(), UINT8_MAX + 1);
+
+    test.deserialize(QByteArray::fromHex("0d00000100"));
+    ASSERT_EQ(test.testFieldFixedInt32(), UINT16_MAX + 1);
+
+    test.deserialize(QByteArray::fromHex("0dff000000"));
+    ASSERT_EQ(test.testFieldFixedInt32(), UINT8_MAX);
+
+    test.deserialize(QByteArray::fromHex("0dffff0000"));
+    ASSERT_EQ(test.testFieldFixedInt32(), UINT16_MAX);
+
+    test.deserialize(QByteArray::fromHex("0dffffffff"));
+    ASSERT_EQ(test.testFieldFixedInt32(), UINT32_MAX);
+}
+
+TEST_F(DeserializationTest, FixedInt64MessageDeserializeTest)
+{
+    SimpleFixedInt64Message test;
+    test.deserialize(QByteArray::fromHex("090f00000000000000"));
+    ASSERT_EQ(test.testFieldFixedInt64(), 15);
+
+
+    test.deserialize(QByteArray::fromHex("092c01000000000000"));
+    ASSERT_EQ(test.testFieldFixedInt64(), 300);
+
+    test.deserialize(QByteArray::fromHex("090900010000000000"));
+    ASSERT_EQ(test.testFieldFixedInt64(), 65545);
+
+    test.deserialize(QByteArray::fromHex("090000000000000000"));
+    ASSERT_EQ(test.testFieldFixedInt64(), 0);
+
+    test.deserialize(QByteArray::fromHex("090001000000000000"));
+    ASSERT_EQ(test.testFieldFixedInt64(), UINT8_MAX + 1);
+
+    test.deserialize(QByteArray::fromHex("090000010000000000"));
+    ASSERT_EQ(test.testFieldFixedInt64(), UINT16_MAX + 1);
+
+    test.deserialize(QByteArray::fromHex("090000000001000000"));
+    ASSERT_EQ(test.testFieldFixedInt64(), (unsigned long long)(UINT32_MAX) + 1);
+
+    test.deserialize(QByteArray::fromHex("09ff00000000000000"));
+    ASSERT_EQ(test.testFieldFixedInt64(), UINT8_MAX);
+
+    test.deserialize(QByteArray::fromHex("09ffff000000000000"));
+    ASSERT_EQ(test.testFieldFixedInt64(), UINT16_MAX);
+
+    test.deserialize(QByteArray::fromHex("09ffffffff00000000"));
+    ASSERT_EQ(test.testFieldFixedInt64(), UINT32_MAX);
+
+    test.deserialize(QByteArray::fromHex("09ffffffffffffffff"));
+    ASSERT_EQ(test.testFieldFixedInt64(), UINT64_MAX);
+}
+

+ 40 - 0
tests/deserializationtest.h

@@ -0,0 +1,40 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 Alexey Edelev <semlanik@gmail.com>
+ *
+ * This file is part of qtprotobuf project https://git.semlanik.org/semlanik/qtprotobuf
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+ * to permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <gtest/gtest.h>
+
+namespace qtprotobuf {
+namespace tests {
+
+class DeserializationTest : public ::testing::Test
+{
+public:
+    DeserializationTest();
+};
+
+}
+}