qtgrpc/src/protobuf/qprotobufserializer.cpp

577 lines
24 KiB
C++

// Copyright (C) 2022 The Qt Company Ltd.
// Copyright (C) 2019 Alexey Edelev <semlanik@gmail.com>, Viktor Kopp <vifactor@gmail.com>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include <QtProtobuf/qprotobufserializer.h>
#include <QtProtobuf/private/protobufscalarserializers_p.h>
#include <QtProtobuf/private/qprotobufmessage_p.h>
#include <QtProtobuf/private/qprotobufregistration_p.h>
#include <QtProtobuf/private/qprotobufserializer_p.h>
#include <QtProtobuf/private/qprotobufserializerbase_p.h>
#include <QtProtobuf/private/qtprotobufdefs_p.h>
#include <QtProtobuf/private/qtprotobufserializerhelpers_p.h>
#include <QtProtobuf/qprotobufmessage.h>
#include <QtProtobuf/qtprotobuftypes.h>
#include <QtCore/qcoreapplication.h>
#include <QtCore/qlist.h>
#include <QtCore/qmetaobject.h>
#include <QtCore/qmetatype.h>
#include <QtCore/qreadwritelock.h>
QT_BEGIN_NAMESPACE
/*!
\class QProtobufSerializer
\inmodule QtProtobuf
\since 6.5
\brief The QProtobufSerializer class is interface that represents
basic functions for serialization/deserialization.
The QProtobufSerializer class registers serializers/deserializers for
classes implementing a protobuf message, inheriting \l QProtobufMessage. These
classes are generated automatically, based on a \c{.proto} file, using the CMake
function \l qt_add_protobuf or by running
\l {The qtprotobufgen Tool} {qtprotobufgen} directly.
*/
using namespace Qt::StringLiterals;
using namespace QtProtobufPrivate;
using namespace ProtobufScalarSerializers;
template<std::size_t N>
using SerializerRegistryType =
std::array<QProtobufSerializerPrivate::ProtobufSerializationHandler, N>;
namespace {
#define QT_CONSTRUCT_PROTOBUF_SERIALIZATION_HANDLER(Type, WireType) \
{ QMetaType::fromType<Type>(), \
QProtobufSerializerPrivate::serializeWrapper<Type, serializeBasic<Type>>, \
deserializeBasic<Type>, ProtobufFieldPresenceChecker::isPresent<Type>, WireType }
#define QT_CONSTRUCT_PROTOBUF_LIST_SERIALIZATION_HANDLER(ListType, Type) \
{ QMetaType::fromType<ListType>(), \
QProtobufSerializerPrivate::serializeWrapper<ListType, serializeListType<Type>>, \
deserializeList<Type>, ProtobufFieldPresenceChecker::isPresent<ListType>, \
QtProtobuf::WireTypes::LengthDelimited }
#define QT_CONSTRUCT_PROTOBUF_NON_PACKED_LIST_SERIALIZATION_HANDLER(ListType, Type, WireType) \
{ QMetaType::fromType<ListType>(), \
QProtobufSerializerPrivate::serializeNonPackedWrapper<ListType, \
serializeNonPackedList<Type>>, \
deserializeNonPackedList<Type>, ProtobufFieldPresenceChecker::isPresent<ListType>, \
WireType }
constexpr SerializerRegistryType<30> IntegratedTypesSerializers = { {
QT_CONSTRUCT_PROTOBUF_SERIALIZATION_HANDLER(float, QtProtobuf::WireTypes::Fixed32),
QT_CONSTRUCT_PROTOBUF_SERIALIZATION_HANDLER(double, QtProtobuf::WireTypes::Fixed64),
QT_CONSTRUCT_PROTOBUF_SERIALIZATION_HANDLER(QtProtobuf::int32,
QtProtobuf::WireTypes::Varint),
QT_CONSTRUCT_PROTOBUF_SERIALIZATION_HANDLER(QtProtobuf::int64,
QtProtobuf::WireTypes::Varint),
QT_CONSTRUCT_PROTOBUF_SERIALIZATION_HANDLER(QtProtobuf::uint32,
QtProtobuf::WireTypes::Varint),
QT_CONSTRUCT_PROTOBUF_SERIALIZATION_HANDLER(QtProtobuf::uint64,
QtProtobuf::WireTypes::Varint),
QT_CONSTRUCT_PROTOBUF_SERIALIZATION_HANDLER(QtProtobuf::sint32,
QtProtobuf::WireTypes::Varint),
QT_CONSTRUCT_PROTOBUF_SERIALIZATION_HANDLER(QtProtobuf::sint64,
QtProtobuf::WireTypes::Varint),
QT_CONSTRUCT_PROTOBUF_SERIALIZATION_HANDLER(QtProtobuf::fixed32,
QtProtobuf::WireTypes::Fixed32),
QT_CONSTRUCT_PROTOBUF_SERIALIZATION_HANDLER(QtProtobuf::fixed64,
QtProtobuf::WireTypes::Fixed64),
QT_CONSTRUCT_PROTOBUF_SERIALIZATION_HANDLER(QtProtobuf::sfixed32,
QtProtobuf::WireTypes::Fixed32),
QT_CONSTRUCT_PROTOBUF_SERIALIZATION_HANDLER(QtProtobuf::sfixed64,
QtProtobuf::WireTypes::Fixed64),
QT_CONSTRUCT_PROTOBUF_SERIALIZATION_HANDLER(QtProtobuf::boolean,
QtProtobuf::WireTypes::Varint),
QT_CONSTRUCT_PROTOBUF_SERIALIZATION_HANDLER(QString,
QtProtobuf::WireTypes::LengthDelimited),
QT_CONSTRUCT_PROTOBUF_SERIALIZATION_HANDLER(QByteArray,
QtProtobuf::WireTypes::LengthDelimited),
QT_CONSTRUCT_PROTOBUF_LIST_SERIALIZATION_HANDLER(QtProtobuf::floatList, float),
QT_CONSTRUCT_PROTOBUF_LIST_SERIALIZATION_HANDLER(QtProtobuf::doubleList, double),
QT_CONSTRUCT_PROTOBUF_LIST_SERIALIZATION_HANDLER(QtProtobuf::int32List, QtProtobuf::int32),
QT_CONSTRUCT_PROTOBUF_LIST_SERIALIZATION_HANDLER(QtProtobuf::int64List, QtProtobuf::int64),
QT_CONSTRUCT_PROTOBUF_LIST_SERIALIZATION_HANDLER(QtProtobuf::uint32List,
QtProtobuf::uint32),
QT_CONSTRUCT_PROTOBUF_LIST_SERIALIZATION_HANDLER(QtProtobuf::uint64List,
QtProtobuf::uint64),
QT_CONSTRUCT_PROTOBUF_LIST_SERIALIZATION_HANDLER(QtProtobuf::sint32List,
QtProtobuf::sint32),
QT_CONSTRUCT_PROTOBUF_LIST_SERIALIZATION_HANDLER(QtProtobuf::sint64List,
QtProtobuf::sint64),
QT_CONSTRUCT_PROTOBUF_LIST_SERIALIZATION_HANDLER(QtProtobuf::fixed32List,
QtProtobuf::fixed32),
QT_CONSTRUCT_PROTOBUF_LIST_SERIALIZATION_HANDLER(QtProtobuf::fixed64List,
QtProtobuf::fixed64),
QT_CONSTRUCT_PROTOBUF_LIST_SERIALIZATION_HANDLER(QtProtobuf::sfixed32List,
QtProtobuf::sfixed32),
QT_CONSTRUCT_PROTOBUF_LIST_SERIALIZATION_HANDLER(QtProtobuf::sfixed64List,
QtProtobuf::sfixed64),
QT_CONSTRUCT_PROTOBUF_LIST_SERIALIZATION_HANDLER(QtProtobuf::boolList, QtProtobuf::boolean),
QT_CONSTRUCT_PROTOBUF_NON_PACKED_LIST_SERIALIZATION_HANDLER(
QStringList, QString, QtProtobuf::WireTypes::LengthDelimited),
QT_CONSTRUCT_PROTOBUF_NON_PACKED_LIST_SERIALIZATION_HANDLER(
QByteArrayList, QByteArray, QtProtobuf::WireTypes::LengthDelimited),
} };
constexpr SerializerRegistryType<13> IntegratedNonPackedSerializers = { {
QT_CONSTRUCT_PROTOBUF_NON_PACKED_LIST_SERIALIZATION_HANDLER(QtProtobuf::floatList, float,
QtProtobuf::WireTypes::Fixed32),
QT_CONSTRUCT_PROTOBUF_NON_PACKED_LIST_SERIALIZATION_HANDLER(QtProtobuf::doubleList, double,
QtProtobuf::WireTypes::Fixed64),
QT_CONSTRUCT_PROTOBUF_NON_PACKED_LIST_SERIALIZATION_HANDLER(
QtProtobuf::int32List, QtProtobuf::int32, QtProtobuf::WireTypes::Varint),
QT_CONSTRUCT_PROTOBUF_NON_PACKED_LIST_SERIALIZATION_HANDLER(
QtProtobuf::int64List, QtProtobuf::int64, QtProtobuf::WireTypes::Varint),
QT_CONSTRUCT_PROTOBUF_NON_PACKED_LIST_SERIALIZATION_HANDLER(
QtProtobuf::uint32List, QtProtobuf::uint32, QtProtobuf::WireTypes::Varint),
QT_CONSTRUCT_PROTOBUF_NON_PACKED_LIST_SERIALIZATION_HANDLER(
QtProtobuf::uint64List, QtProtobuf::uint64, QtProtobuf::WireTypes::Varint),
QT_CONSTRUCT_PROTOBUF_NON_PACKED_LIST_SERIALIZATION_HANDLER(
QtProtobuf::sint32List, QtProtobuf::sint32, QtProtobuf::WireTypes::Varint),
QT_CONSTRUCT_PROTOBUF_NON_PACKED_LIST_SERIALIZATION_HANDLER(
QtProtobuf::sint64List, QtProtobuf::sint64, QtProtobuf::WireTypes::Varint),
QT_CONSTRUCT_PROTOBUF_NON_PACKED_LIST_SERIALIZATION_HANDLER(
QtProtobuf::fixed32List, QtProtobuf::fixed32, QtProtobuf::WireTypes::Fixed32),
QT_CONSTRUCT_PROTOBUF_NON_PACKED_LIST_SERIALIZATION_HANDLER(
QtProtobuf::fixed64List, QtProtobuf::fixed64, QtProtobuf::WireTypes::Fixed64),
QT_CONSTRUCT_PROTOBUF_NON_PACKED_LIST_SERIALIZATION_HANDLER(
QtProtobuf::sfixed32List, QtProtobuf::sfixed32, QtProtobuf::WireTypes::Fixed32),
QT_CONSTRUCT_PROTOBUF_NON_PACKED_LIST_SERIALIZATION_HANDLER(
QtProtobuf::sfixed64List, QtProtobuf::sfixed64, QtProtobuf::WireTypes::Fixed64),
QT_CONSTRUCT_PROTOBUF_NON_PACKED_LIST_SERIALIZATION_HANDLER(
QtProtobuf::boolList, QtProtobuf::boolean, QtProtobuf::WireTypes::Varint),
} };
template<std::size_t N>
std::optional<QProtobufSerializerPrivate::ProtobufSerializationHandler>
findIntegratedTypeHandlerImpl(QMetaType metaType, const SerializerRegistryType<N> &registry)
{
typename SerializerRegistryType<N>::const_iterator it = std::find_if(
registry.begin(), registry.end(),
[&metaType](const QProtobufSerializerPrivate::ProtobufSerializationHandler &handler) {
return handler.metaType == metaType;
});
if (it == registry.end())
return std::nullopt;
return { *it };
}
std::optional<QProtobufSerializerPrivate::ProtobufSerializationHandler>
findIntegratedTypeHandler(QMetaType metaType, bool nonPacked)
{
if (nonPacked)
return findIntegratedTypeHandlerImpl(metaType, IntegratedNonPackedSerializers);
return findIntegratedTypeHandlerImpl(metaType, IntegratedTypesSerializers);
}
} // namespace
QProtobufSerializerImpl::~QProtobufSerializerImpl() = default;
void QProtobufSerializerImpl::reset()
{
m_state.clear();
m_result = {};
}
void QProtobufSerializerImpl::serializeUnknownFields(const QProtobufMessage *message)
{
if (preserveUnknownFields) {
// Restore any unknown fields we have stored away:
for (const auto &fields :
std::as_const(QProtobufMessagePrivate::get(message)->unknownEntries)) {
m_result += fields.join();
}
}
}
bool QProtobufSerializerImpl::serializeEnum(QVariant &value,
const QtProtobufPrivate::QProtobufFieldInfo &fieldInfo)
{
const auto fieldFlags = fieldInfo.fieldFlags();
if (fieldFlags.testFlag(QtProtobufPrivate::FieldFlag::Repeated)) {
if (!value.canConvert<QList<QtProtobuf::int64>>())
return false;
QList<QtProtobuf::int64> listValue = value.value<QList<QtProtobuf::int64>>();
if (listValue.isEmpty())
return true;
if (fieldFlags.testFlag(QtProtobufPrivate::FieldFlag::NonPacked)) {
const auto header = encodeHeader(fieldInfo.fieldNumber(),
QtProtobuf::WireTypes::Varint);
m_result.append(serializeNonPackedList<QtProtobuf::int64>(listValue, header));
} else {
m_result.append(encodeHeader(fieldInfo.fieldNumber(),
QtProtobuf::WireTypes::LengthDelimited));
m_result.append(serializeListType<QtProtobuf::int64>(listValue));
}
} else {
if (!value.canConvert<QtProtobuf::int64>())
return false;
if (!ProtobufFieldPresenceChecker::isPresent<QtProtobuf::int64>(value)
&& !isOneofOrOptionalField(fieldInfo.fieldFlags())) {
return true;
}
m_result.append(encodeHeader(fieldInfo.fieldNumber(), QtProtobuf::WireTypes::Varint));
m_result.append(serializeBasic<QtProtobuf::int64>(value.value<QtProtobuf::int64>()));
}
return true;
}
bool QProtobufSerializerImpl::serializeScalarField(const QVariant &value,
const QProtobufFieldInfo &fieldInfo)
{
const QtProtobufPrivate::FieldFlags flags = fieldInfo.fieldFlags();
const bool isPacked = flags.testFlag(QtProtobufPrivate::FieldFlag::NonPacked);
auto basicHandler = findIntegratedTypeHandler(value.metaType(), isPacked);
// Is not a protobuf scalar value type
if (!basicHandler)
return false;
// Field is empty
if (!basicHandler->isPresent(value) && !isOneofOrOptionalField(flags))
return true;
const QByteArray header = encodeHeader(fieldInfo.fieldNumber(), basicHandler->wireType);
m_result.append(basicHandler->serializer(value, header));
return true;
}
void QProtobufSerializerImpl::serializeMessageFieldBegin()
{
m_state.emplaceBack(std::move(m_result));
m_result = {};
}
void QProtobufSerializerImpl::serializeMessageFieldEnd(const QProtobufMessage *message,
const QProtobufFieldInfo &fieldInfo)
{
Q_ASSERT(!m_state.isEmpty());
serializeUnknownFields(message);
QByteArray last = m_state.takeLast();
last.append(encodeHeader(fieldInfo.fieldNumber(), QtProtobuf::WireTypes::LengthDelimited));
last.append(serializeVarintCommon<uint32_t>(m_result.size()));
last.append(m_result);
m_result = last;
}
QByteArray QProtobufSerializerImpl::encodeHeader(int fieldIndex, QtProtobuf::WireTypes wireType)
{
// Encodes a property field index and its type into output bytes.
// Header byte
// Meaning | Field index | Type
// ---------- | ------------- | --------
// bit number | 7 6 5 4 3 | 2 1 0
// fieldIndex: The index of a property in parent object
// wireType: Serialization type used for the property with fieldIndex
// Returns a varint-encoded fieldIndex and wireType
uint32_t header = (fieldIndex << 3) | int(wireType);
return serializeVarintCommon<uint32_t>(header);
}
QProtobufDeserializerImpl::QProtobufDeserializerImpl(QProtobufSerializerPrivate *parent)
: m_parent(parent)
{
}
QProtobufDeserializerImpl::~QProtobufDeserializerImpl()
= default;
void QProtobufDeserializerImpl::reset(QByteArrayView data)
{
m_it = QProtobufSelfcheckIterator::fromView(data);
m_state.push_back(data.end());
clearCachedValue();
}
void QProtobufDeserializerImpl::setError(QAbstractProtobufSerializer::Error error,
QAnyStringView errorString)
{
m_parent->lastError = error;
m_parent->lastErrorString = errorString.toString();
}
bool QProtobufDeserializerImpl::deserializeEnum(QVariant &value,
const QProtobufFieldInfo &fieldInfo)
{
const auto fieldFlags = fieldInfo.fieldFlags();
if (fieldFlags.testFlag(QtProtobufPrivate::FieldFlag::Repeated)) {
QMetaType metaType = value.metaType();
value.convert(QMetaType::fromType<QList<QtProtobuf::int64>>());
bool result = false;
if (m_wireType == QtProtobuf::WireTypes::Varint) {
result = deserializeNonPackedList<QtProtobuf::int64>(m_it, value);
} else if (m_wireType == QtProtobuf::WireTypes::LengthDelimited) {
result = deserializeList<QtProtobuf::int64>(m_it, value);
}
value.convert(metaType);
return result;
}
return deserializeBasic<QtProtobuf::int64>(m_it, value);
}
int QProtobufDeserializerImpl::nextFieldIndex(QProtobufMessage *message)
{
Q_ASSERT(message);
const auto *ordering = message->propertyOrdering();
Q_ASSERT(ordering != nullptr);
while (m_it.isValid() && m_it != m_state.last()) {
// Each iteration we expect iterator is setup to beginning of next chunk
int fieldNumber = QtProtobuf::InvalidFieldNumber;
const QProtobufSelfcheckIterator fieldBegin = m_it; // copy this, we may need it later
if (!decodeHeader(m_it, fieldNumber, m_wireType)) {
setError(QAbstractProtobufSerializer::Error::InvalidHeader,
"Message received doesn't contain valid header byte.");
return -1;
}
int index = ordering->indexOfFieldNumber(fieldNumber);
if (index == -1) {
// This is an unknown field, it may have been added in a later revision
// of the Message we are currently deserializing. We must store the
// bytes for this field and re-emit them later if this message is
// serialized again.
if (auto length = skipField(fieldBegin); length < 0) {
return -1;
} else if (length > 0 && m_parent->preserveUnknownFields) {
QByteArrayView fieldData(fieldBegin.data(), length);
QProtobufMessagePrivate::storeUnknownEntry(message, fieldData, fieldNumber);
}
continue;
}
if (ordering->fieldFlags(index).testAnyFlags({ QtProtobufPrivate::FieldFlag::Message,
QtProtobufPrivate::FieldFlag::Map })) {
auto opt = deserializeVarintCommon<QtProtobuf::uint64>(m_it);
if (!opt) {
setUnexpectedEndOfStreamError();
return -1;
}
quint64 length = *opt;
if (!m_it.isValid() || quint64(m_it.bytesLeft()) < length
|| length > quint64(QByteArray::maxSize())) {
setUnexpectedEndOfStreamError();
return -1;
}
m_state.push_back(m_it.data() + length);
}
return index;
}
if (!m_it.isValid())
setUnexpectedEndOfStreamError();
m_state.pop_back();
return -1;
}
bool QProtobufDeserializerImpl::deserializeScalarField(QVariant &value,
const QtProtobufPrivate::QProtobufFieldInfo
&fieldInfo)
{
QMetaType metaType = value.metaType();
bool isNonPacked = fieldInfo.fieldFlags().testFlag(QtProtobufPrivate::FieldFlag::NonPacked);
auto basicHandler = findIntegratedTypeHandler(metaType, isNonPacked);
if (!basicHandler)
return false;
if (basicHandler->wireType != m_wireType) {
// If the handler wiretype mismatches the wiretype received from the
// wire that most probably means that we received the list in wrong
// format. This can happen because of mismatch of the field packed
// option in the protobuf schema on the wire ends. Invert the
// isNonPacked flag and try to find the handler one more time to make
// sure that we cover this exceptional case.
// See the conformance tests
// Required.Proto3.ProtobufInput.ValidDataRepeated.*.UnpackedInput
// for details.
basicHandler = findIntegratedTypeHandler(metaType, !isNonPacked);
if (!basicHandler || basicHandler->wireType != m_wireType) {
setError(QAbstractProtobufSerializer::Error::InvalidHeader,
QCoreApplication::translate("QtProtobuf",
"Invalid wiretype for the %1 "
"field number %1. Expected %2, received %3")
.arg(QString::fromUtf8(metaType.name()))
.arg(fieldInfo.fieldNumber())
.arg(basicHandler ? static_cast<int>(basicHandler->wireType) : -1)
.arg(static_cast<int>(m_wireType)));
value.clear();
return true;
}
}
if (!basicHandler->deserializer(m_it, value)) {
value.clear();
setUnexpectedEndOfStreamError();
}
return true;
}
qsizetype QProtobufDeserializerImpl::skipField(const QProtobufSelfcheckIterator &fieldBegin)
{
switch (m_wireType) {
case QtProtobuf::WireTypes::Varint:
skipVarint();
break;
case QtProtobuf::WireTypes::Fixed32:
m_it += sizeof(decltype(QtProtobuf::fixed32::t));
break;
case QtProtobuf::WireTypes::Fixed64:
m_it += sizeof(decltype(QtProtobuf::fixed64::t));
break;
case QtProtobuf::WireTypes::LengthDelimited:
skipLengthDelimited();
break;
case QtProtobuf::WireTypes::Unknown:
default:
Q_UNREACHABLE();
return 0;
}
if (!m_it.isValid()) {
setUnexpectedEndOfStreamError();
return -1;
}
return std::distance(fieldBegin, m_it);
}
void QProtobufDeserializerImpl::skipVarint()
{
while ((*m_it) & 0x80)
++m_it;
++m_it;
}
void QProtobufDeserializerImpl::skipLengthDelimited()
{
//Get length of length-delimited field
auto opt = deserializeVarintCommon<QtProtobuf::uint64>(m_it);
if (!opt) {
m_it += m_it.bytesLeft() + 1;
return;
}
QtProtobuf::uint64 length = opt.value();
m_it += length;
}
void QProtobufDeserializerImpl::setUnexpectedEndOfStreamError()
{
setError(QAbstractProtobufSerializer::Error::UnexpectedEndOfStream,
QCoreApplication::translate("QtProtobuf", "Unexpected end of stream"));
}
bool QProtobufDeserializerImpl::decodeHeader(QProtobufSelfcheckIterator &it, int &fieldIndex,
QtProtobuf::WireTypes &wireType)
{
if (it.bytesLeft() == 0)
return false;
auto opt = deserializeVarintCommon<uint32_t>(it);
if (!opt)
return false;
uint32_t header = opt.value();
wireType = static_cast<QtProtobuf::WireTypes>(header & 0b00000111);
fieldIndex = header >> 3;
constexpr int maxFieldIndex = (1 << 29) - 1;
return fieldIndex <= maxFieldIndex && fieldIndex > 0
&& (wireType == QtProtobuf::WireTypes::Varint || wireType == QtProtobuf::WireTypes::Fixed64
|| wireType == QtProtobuf::WireTypes::Fixed32
|| wireType == QtProtobuf::WireTypes::LengthDelimited);
}
QProtobufSerializerPrivate::QProtobufSerializerPrivate() : deserializer(this)
{
}
void QProtobufSerializerPrivate::clearError()
{
lastError = QAbstractProtobufSerializer::Error::None;
lastErrorString.clear();
}
/*!
Constructs a new serializer instance.
*/
QProtobufSerializer::QProtobufSerializer() : d_ptr(new QProtobufSerializerPrivate())
{
}
/*!
Destroys the serializer instance.
*/
QProtobufSerializer::~QProtobufSerializer() = default;
QByteArray QProtobufSerializer::serializeMessage(const QProtobufMessage *message) const
{
d_ptr->clearError();
d_ptr->serializer.reset();
d_ptr->serializer.serializeMessage(message);
d_ptr->serializer.serializeUnknownFields(message);
auto result = d_ptr->serializer.result();
d_ptr->serializer.reset();
return result;
}
bool QProtobufSerializer::deserializeMessage(QProtobufMessage *message, QByteArrayView data) const
{
d_ptr->clearError();
d_ptr->deserializer.reset(data);
d_ptr->deserializer.deserializeMessage(message);
d_ptr->deserializer.reset({});
return d_ptr->lastError == QAbstractProtobufSerializer::Error::None;
}
/*!
Returns the last deserialization error for the serializer instance.
\sa lastErrorString()
*/
QAbstractProtobufSerializer::Error QProtobufSerializer::lastError() const
{
return d_ptr->lastError;
}
/*!
Returns the last deserialization error string for the serializer instance.
\sa lastError()
*/
QString QProtobufSerializer::lastErrorString() const
{
return d_ptr->lastErrorString;
}
/*!
Controls whether the unknown fields received from the wire should be
stored in the resulting message or if it should be omitted, based
on \a preserveUnknownFields.
\since 6.7
*/
void QProtobufSerializer::shouldPreserveUnknownFields(bool preserveUnknownFields)
{
d_ptr->serializer.preserveUnknownFields = preserveUnknownFields;
d_ptr->preserveUnknownFields = preserveUnknownFields;
}
QT_END_NAMESPACE