/* * MIT License * * Copyright (c) 2019 Alexey Edelev * * 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 #include #include #include #include #include #include #include #include #include #include #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: using ListSerializer = std::function; using ListDeserializer = std::function; struct SerializationHandlers { ListSerializer serializer; ListDeserializer deserializer; }; using SerializerRegistry = std::unordered_map; static SerializerRegistry serializers; public: explicit ProtobufObjectPrivate(QObject *parent = nullptr) : QObject(parent) {} virtual QByteArray serializePrivate() const = 0; virtual void deserializePrivate(const QByteArray &data) = 0; inline static unsigned char encodeHeaderByte(int fieldIndex, WireTypes wireType); inline static bool decodeHeaderByte(unsigned char typeByte, int &fieldIndex, WireTypes &wireType); QByteArray serializeValue(const QVariant& propertyValue, int fieldIndex, const QLatin1Literal& typeName) const; QByteArray serializeUserType(const QVariant &propertyValue, int &fieldIndex) const; void deserializeProperty(WireTypes wireType, const QMetaProperty &metaProperty, QByteArray::const_iterator &it); void deserializeUserType(const QMetaProperty &metaType, QByteArray::const_iterator& it, QVariant &newValue); QByteArray serializeLengthDelimited(const QByteArray &data) const { qProtoDebug() << __func__ << "data.size" << data.size() << "data" << data.toHex(); //Varint serialize field size and apply result as starting point QByteArray result = serializeVarintZero(static_cast(data.size())); result.append(data); return result; } template::value, int> = 0> QByteArray serializeListType(const QList &listValue, int &outFieldIndex) const { qProtoDebug() << __func__ << "listValue.count" << listValue.count() << "outFiledIndex" << outFieldIndex; if (listValue.count() <= 0) { outFieldIndex = NotUsedFieldIndex; return QByteArray(); } QByteArray serializedList; for(auto& value : listValue) { serializedList.append(serializeVarintZigZag(value)); } //If internal field type is not LengthDelimited, exact amount of fields to be specified serializedList.prepend(serializeVarintZero(static_cast(serializedList.size()))); return serializedList; } template::value, int> = 0> QByteArray serializeListType(const QList &listValue, int &outFieldIndex) const { qProtoDebug() << __func__ << "listValue.count" << listValue.count() << "outFiledIndex" << outFieldIndex; if (listValue.count() <= 0) { outFieldIndex = NotUsedFieldIndex; return QByteArray(); } QByteArray serializedList; for(auto& value : listValue) { serializedList.append(serializeFixed(value)); } //If internal field type is not LengthDelimited, exact amount of fields to be specified serializedList.prepend(serializeVarintZero(static_cast(serializedList.size()))); return serializedList; } template::value || std::is_same::value, int> = 0> QByteArray serializeListType(const QList &listValue, int &outFieldIndex) const { qProtoDebug() << __func__ << "listValue.count" << listValue.count() << "outFiledIndex" << outFieldIndex; if (listValue.count() <= 0) { outFieldIndex = NotUsedFieldIndex; return QByteArray(); } QByteArray serializedList; for(auto& value : listValue) { serializedList.append(serializeValue(value, outFieldIndex, QLatin1Literal())); } outFieldIndex = NotUsedFieldIndex; return serializedList; } template::value, int> = 0> QByteArray serializeListType(const QList &listValue, int &outFieldIndex) const { qProtoDebug() << __func__ << "listValue.count" << listValue.count() << "outFiledIndex" << outFieldIndex; if (listValue.count() <= 0) { outFieldIndex = NotUsedFieldIndex; return QByteArray(); } QByteArray serializedList; for(auto& value : listValue) { QByteArray serializedValue = serializeLengthDelimited(value.serialize()); serializedValue.prepend(encodeHeaderByte(outFieldIndex, LengthDelimited)); serializedList.append(serializedValue); } outFieldIndex = NotUsedFieldIndex; return serializedList; } template ::value || std::is_same::value || std::is_same::value || std::is_same::value || std::is_same::value, int> = 0> QByteArray serializeFixed(V value) const { qProtoDebug() << __func__ << "value" << value; //Reserve required amount of bytes QByteArray result(sizeof(V), '\0'); *(V*)(result.data()) = value; return result; } template ::type, typename std::enable_if_t::value, int> = 0> QByteArray serializeVarint(V value) const { qProtoDebug() << __func__ << "value" << value; return serializeVarint(static_cast(value)); } template ::type, typename std::enable_if_t::value, int> = 0> QByteArray serializeVarintZigZag(V value) const { qProtoDebug() << __func__ << "value" << value; UV uValue = 0; //Use ZigZag convertion first and apply unsigned variant next value = (value << 1) ^ (value >> (sizeof(UV) * 8 - 1)); uValue = *(UV *)&value; return serializeVarint(uValue); } template ::value, int> = 0> QByteArray serializeVarint(V value) const { qProtoDebug() << __func__ << "value" << value; QByteArray result; //Reserve maximum required amount of bytes result.reserve(sizeof(V)); while (value > 0) { //Put first 7 bits to result buffer and mark as not last result.append((value & 0x7F) | 0x80); //Devide values to chunks of 7 bits, move to next chunk value >>= 7; } //TODO: Zero case. //Aligned to reference cpp implementation. Where 0 ignored. //if (result.isEmpty()) { // result.append('\0'); //} //Mark last chunk as last result.data()[result.size() - 1] &= ~0x80; return result; } template ::value, int> = 0> QByteArray serializeVarintZero(V value) const { qProtoDebug() << __func__ << "value" << value; QByteArray result = serializeVarint(value); //Zero case. if (result.isEmpty()) { result.append('\0'); } return result; } //##################################################################### // Deserialization //##################################################################### template ::value || std::is_same::value || std::is_same::value || std::is_same::value || std::is_same::value, int> = 0> QVariant deserializeFixed(QByteArray::const_iterator &it) { qProtoDebug() << __func__ << "currentByte:" << QString::number((*it), 16); QVariant newPropertyValue(QVariant::fromValue(*(V*)it)); it += sizeof(V); return newPropertyValue; } template ::value, int> = 0> QVariant deserializeVarint(QByteArray::const_iterator &it) { qProtoDebug() << __func__ << "currentByte:" << QString::number((*it), 16); return QVariant::fromValue(deserializeVarintCommon(it)); } template ::type, typename std::enable_if_t::value, int> = 0> QVariant deserializeVarintZigZag(QByteArray::const_iterator &it) { qProtoDebug() << __func__ << "currentByte:" << QString::number((*it), 16); UV unsignedValue = deserializeVarintCommon(it); V value = (unsignedValue >> 1) ^ (-(unsignedValue & 1)); return QVariant::fromValue(value); } template ::type, typename std::enable_if_t::value, int> = 0> QVariant deserializeVarint(QByteArray::const_iterator &it) { qProtoDebug() << __func__ << "currentByte:" << QString::number((*it), 16); UV unsignedValue = deserializeVarintCommon(it); V value = static_cast(unsignedValue); return QVariant::fromValue(value); } template V deserializeVarintCommon(QByteArray::const_iterator &it) { qProtoDebug() << __func__ << "currentByte:" << QString::number((*it), 16); V value = 0; int k = 0; while(true) { uint64_t byte = static_cast(*it); value += (byte & 0x7f) << k; k += 7; if (((*it) & 0x80) == 0) { break; } ++it; } ++it; return value; } QByteArray deserializeLengthDelimited(QByteArray::const_iterator &it) { qProtoDebug() << __func__ << "currentByte:" << QString::number((*it), 16); unsigned int length = deserializeVarint(it).toUInt(); QByteArray result(it, length); it += length; return result; } QVariant deserializeProtobufObjectType(int userType, QByteArray::const_iterator &it) { auto value = reinterpret_cast(QMetaType::create(userType)); value->deserializePrivate(deserializeLengthDelimited(it)); return QVariant(userType, value); } template ::value || std::is_same::value, int> = 0> QByteArray deserializeListType(QByteArray::const_iterator& it) { qProtoDebug() << __func__ << "currentByte:" << QString::number((*it), 16); return deserializeLengthDelimited(it); } template ::value, int> = 0> QVariant deserializeListType(QByteArray::const_iterator& it) { qProtoDebug() << __func__ << "currentByte:" << QString::number((*it), 16); return deserializeProtobufObjectType(qMetaTypeId(), it); } template ::value || std::is_same::value || std::is_same::value, int> = 0> QVariant deserializeListType(QByteArray::const_iterator& it) { qProtoDebug() << __func__ << "currentByte:" << QString::number((*it), 16); QList out; unsigned int count = deserializeVarint(it).toUInt() / sizeof(V); for (unsigned int i = 0; i < count; i++) { QVariant variant = deserializeFixed(it); out.append(variant.value()); } return QVariant::fromValue(out); } template QVariant deserializeVarintListType(QByteArray::const_iterator& it) { qProtoDebug() << __func__ << "currentByte:" << QString::number((*it), 16); QList out; unsigned int count = deserializeVarint(it).toUInt(); QByteArray::const_iterator lastVarint = it + count; while (it != lastVarint) { QVariant variant = deserializeVarint(it); out.append(variant.value()); } return QVariant::fromValue(out); } template QVariant deserializeVarintListTypeZigZag(QByteArray::const_iterator& it) { qProtoDebug() << __func__ << "currentByte:" << QString::number((*it), 16); QList out; unsigned int count = deserializeVarint(it).toUInt(); QByteArray::const_iterator lastVarint = it + count; while (it != lastVarint) { QVariant variant = deserializeVarintZigZag(it); out.append(variant.value()); } return QVariant::fromValue(out); } }; /* Header byte * bits | 7 6 5 4 3 | 2 1 0 * ----------------------------------- * meaning | Field index | Type */ unsigned char ProtobufObjectPrivate::encodeHeaderByte(int fieldIndex, WireTypes wireType) { unsigned char header = (fieldIndex << 3) | wireType; return *(char *)&header; } bool ProtobufObjectPrivate::decodeHeaderByte(unsigned char typeByte, int &fieldIndex, WireTypes &wireType) { wireType = static_cast(typeByte & 0x07); fieldIndex = typeByte >> 3; return fieldIndex < 128 && fieldIndex > 0 && (wireType == Varint || wireType == Fixed64 || wireType == Fixed32 || wireType == LengthDelimited); } }