// Copyright (C) 2017 Ford Motor Company // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include #include #include "qremoteobjectcontainers_p.h" #include "qremoteobjectpendingcall.h" #include "qremoteobjectsource.h" #include "qremoteobjectsource_p.h" #include "qremoteobjectpacket_p.h" #include "qconnectionfactories.h" #include "qconnectionfactories_p.h" #include //#define QTRO_VERBOSE_PROTOCOL QT_BEGIN_NAMESPACE // Add methods so we can use QMetaEnum in a set // Note for both functions we are skipping string comparisons/hashes. Since the // metaObjects are the same, we can just use the address of the string. inline bool operator==(const QMetaEnum e1, const QMetaEnum e2) { return e1.enclosingMetaObject() == e2.enclosingMetaObject() && e1.name() == e2.name() && e1.enumName() == e2.enumName() && e1.scope() == e2.scope(); } inline size_t qHash(const QMetaEnum &key, size_t seed=0) Q_DECL_NOTHROW { return qHash(key.enclosingMetaObject(), seed) ^ qHash(static_cast(key.name()), seed) ^ qHash(static_cast(key.enumName()), seed) ^ qHash(static_cast(key.scope()), seed); } static bool isSequentialGadgetType(QMetaType metaType) { if (QMetaType::canConvert(metaType, QMetaType::fromType())) { static QHash lookup; if (!lookup.contains(metaType.id())) { auto stubVariant = QVariant(metaType, nullptr); auto asIterable = stubVariant.value(); auto valueMetaType = asIterable.metaContainer().valueMetaType(); lookup[metaType.id()] = valueMetaType.flags().testFlag(QMetaType::IsGadget); } return lookup[metaType.id()]; } return false; } static bool isAssociativeGadgetType(QMetaType metaType) { if (QMetaType::canConvert(metaType, QMetaType::fromType())) { static QHash lookup; if (!lookup.contains(metaType.id())) { auto stubVariant = QVariant(metaType, nullptr); auto asIterable = stubVariant.value(); auto valueMetaType = asIterable.metaContainer().mappedMetaType(); lookup[metaType.id()] = valueMetaType.flags().testFlag(QMetaType::IsGadget); } return lookup[metaType.id()]; } return false; } using namespace QtRemoteObjects; namespace QRemoteObjectPackets { QMetaType transferTypeForEnum(QMetaType enumType) { const auto size = enumType.sizeOf(); switch (size) { case 1: return QMetaType::fromType(); case 2: return QMetaType::fromType(); case 4: return QMetaType::fromType(); // Qt currently only supports enum values of 4 or less bytes (QMetaEnum value(index) returns int) // case 8: args.push_back(QVariant(QMetaType::Int, argv[i + 1])); break; default: qCWarning(QT_REMOTEOBJECT_IO) << "Invalid enum detected (Dynamic Replica)" << enumType.name() << "with size" << size; return QMetaType::fromType(); } } // QDataStream sends QVariants of custom types by sending their typename, allowing decode // on the receiving side. For QtRO and enums, this won't work, as the enums have different // scopes. E.g., the examples have ParentClassSource::MyEnum and ParentClassReplica::MyEnum. // Dynamic types will be created as ParentClass::MyEnum. So instead, we change the variants // to integers (encodeVariant) when sending them. On the receive side, the we know the // types of properties and the signatures for methods, so we can use that information to // decode the integer variant into an enum variant (via decodeVariant). QVariant encodeVariant(const QVariant &value) { const auto metaType = value.metaType(); if (metaType.flags().testFlag(QMetaType::IsEnumeration)) { auto converted = QVariant(value); auto transferType = transferTypeForEnum(metaType); converted.convert(transferType); #ifdef QTRO_VERBOSE_PROTOCOL qDebug() << "Converting from enum to integer type" << transferType.sizeOf() << converted << value; #endif return converted; } if (isSequentialGadgetType(metaType)) { // Doesn't include QtROSequentialContainer // TODO Way to create the QVariant without copying the QSQ_? QSQ_ sequence(value); #ifdef QTRO_VERBOSE_PROTOCOL qDebug() << "Encoding sequential container" << metaType.name() << "to QSQ_ to transmit"; #endif return QVariant::fromValue(sequence); } if (metaType == QMetaType::fromType()) { QSQ_ sequence(value); #ifdef QTRO_VERBOSE_PROTOCOL qDebug() << "Encoding QtROSequentialContainer container to QSQ_ to transmit"; #endif return QVariant::fromValue(sequence); } if (isAssociativeGadgetType(metaType)) { // Doesn't include QtROAssociativeContainer QAS_ map(value); #ifdef QTRO_VERBOSE_PROTOCOL qDebug() << "Encoding associative container" << metaType.name() << "to QAS_ to transmit"; #endif return QVariant::fromValue(map); } if (metaType == QMetaType::fromType()) { QAS_ map(value); #ifdef QTRO_VERBOSE_PROTOCOL qDebug() << "Encoding QtROAssociativeContainer container to QAS_ to transmit"; #endif return QVariant::fromValue(map); } return value; } QVariant decodeVariant(QVariant &&value, QMetaType metaType) { if (metaType.flags().testFlag(QMetaType::IsEnumeration)) { #ifdef QTRO_VERBOSE_PROTOCOL QVariant encoded(value); #endif value.convert(metaType); #ifdef QTRO_VERBOSE_PROTOCOL qDebug() << "Converting to enum from integer type" << value << encoded; #endif } else if (value.metaType() == QMetaType::fromType()) { const auto *qsq_ = static_cast(value.constData()); QDataStream in(qsq_->values); auto containerType = QMetaType::fromName(qsq_->typeName.constData()); bool isRegistered = containerType.isRegistered(); if (isRegistered) { QVariant seq{containerType, nullptr}; if (!seq.canView()) { qWarning() << "Unsupported container" << qsq_->typeName.constData() << "(not viewable)"; return QVariant(); } QSequentialIterable seqIter = seq.view(); if (!seqIter.metaContainer().canAddValue()) { qWarning() << "Unsupported container" << qsq_->typeName.constData() << "(Unable to add values)"; return QVariant(); } QByteArray valueTypeName; quint32 count; in >> valueTypeName; in >> count; QMetaType valueType = QMetaType::fromName(valueTypeName.constData()); QVariant tmp{valueType, nullptr}; for (quint32 i = 0; i < count; i++) { if (!valueType.load(in, tmp.data())) { if (seqIter.metaContainer().canRemoveValue() || i == 0) { for (quint32 ii = 0; ii < i; ii++) seqIter.removeValue(); qWarning("QSQ_: unable to load type '%s', returning an empty list.", valueTypeName.constData()); } else { qWarning("QSQ_: unable to load type '%s', returning a partial list.", valueTypeName.constData()); } break; } seqIter.addValue(tmp); } value = seq; #ifdef QTRO_VERBOSE_PROTOCOL qDebug() << "Decoding QSQ_ to sequential container" << containerType.name() << valueTypeName; #endif } else { QtROSequentialContainer container{}; in >> container; container.m_typeName = qsq_->typeName; value = QVariant(QMetaType::fromType(), &container); #ifdef QTRO_VERBOSE_PROTOCOL qDebug() << "Decoding QSQ_ to QtROSequentialContainer of" << container.m_valueTypeName; #endif } } else if (value.metaType() == QMetaType::fromType()) { const auto *qas_ = static_cast(value.constData()); QDataStream in(qas_->values); auto containerType = QMetaType::fromName(qas_->typeName.constData()); bool isRegistered = containerType.isRegistered(); if (isRegistered) { QVariant map{containerType, nullptr}; if (!map.canView()) { qWarning() << "Unsupported container" << qas_->typeName.constData() << "(not viewable)"; return QVariant(); } QAssociativeIterable mapIter = map.view(); if (!mapIter.metaContainer().canSetMappedAtKey()) { qWarning() << "Unsupported container" << qas_->typeName.constData() << "(Unable to insert values)"; return QVariant(); } QByteArray keyTypeName, valueTypeName; quint32 count; in >> keyTypeName; QMetaType keyType = QMetaType::fromName(keyTypeName.constData()); if (!keyType.isValid()) { // This happens for class enums, where the passed keyType is :: // For a compiled replica, the keyType is Replica:: // Since the full typename is registered, we can pull the keyType from there keyType = mapIter.metaContainer().keyMetaType(); } QMetaType transferType = keyType; if (keyType.flags().testFlag(QMetaType::IsEnumeration)) transferType = transferTypeForEnum(keyType); QVariant key{transferType, nullptr}; in >> valueTypeName; QMetaType valueType = QMetaType::fromName(valueTypeName.constData()); QVariant val{valueType, nullptr}; in >> count; for (quint32 i = 0; i < count; i++) { if (!transferType.load(in, key.data())) { map = QVariant{containerType, nullptr}; qWarning("QAS_: unable to load key of type '%s', returning an empty map.", keyTypeName.constData()); break; } if (!valueType.load(in, val.data())) { map = QVariant{containerType, nullptr}; qWarning("QAS_: unable to load value of type '%s', returning an empty map.", valueTypeName.constData()); break; } if (transferType != keyType) { QVariant enumKey(key); enumKey.convert(keyType); mapIter.setValue(enumKey, val); } else { mapIter.setValue(key, val); } } value = map; #ifdef QTRO_VERBOSE_PROTOCOL qDebug() << "Decoding QAS_ to associative container" << containerType.name() << valueTypeName << keyTypeName << count << mapIter.size() << map; #endif } else { QtROAssociativeContainer container{}; in >> container; container.m_typeName = qas_->typeName; value = QVariant(QMetaType::fromType(), &container); #ifdef QTRO_VERBOSE_PROTOCOL qDebug() << "Decoding QAS_ to QtROAssociativeContainer of" << container.m_valueTypeName; #endif } } return std::move(value); } void QDataStreamCodec::serializeProperty(const QRemoteObjectSourceBase *source, int internalIndex) { serializeProperty(m_packet, source, internalIndex); } void QDataStreamCodec::serializeProperty(QDataStream &ds, const QRemoteObjectSourceBase *source, int internalIndex) { const int propertyIndex = source->m_api->sourcePropertyIndex(internalIndex); Q_ASSERT (propertyIndex >= 0); const auto target = source->m_api->isAdapterProperty(internalIndex) ? source->m_adapter : source->m_object; const auto property = target->metaObject()->property(propertyIndex); const QVariant value = property.read(target); if (property.metaType().flags().testFlag(QMetaType::PointerToQObject)) { auto const childSource = source->m_children.value(internalIndex); auto valueAsPointerToQObject = qvariant_cast(value); if (childSource->m_object != valueAsPointerToQObject) childSource->resetObject(valueAsPointerToQObject); QRO_ qro(childSource); if (source->d->isDynamic && qro.type == ObjectType::CLASS && childSource->m_object && !source->d->sentTypes.contains(qro.typeName)) { QDataStream classDef(&qro.classDefinition, QIODevice::WriteOnly); serializeDefinition(classDef, childSource); source->d->sentTypes.insert(qro.typeName); } ds << QVariant::fromValue(qro); if (qro.isNull) return; const int propertyCount = childSource->m_api->propertyCount(); // Put the properties in a buffer, the receiver may not know how to // interpret the types until it registers new ones. QDataStream params(&qro.parameters, QIODevice::WriteOnly); params << propertyCount; for (int internalIndex = 0; internalIndex < propertyCount; ++internalIndex) serializeProperty(params, childSource, internalIndex); ds << qro.parameters; return; } if (source->d->isDynamic && property.userType() == QMetaType::QVariant && value.metaType().flags().testFlag(QMetaType::IsGadget)) { const auto typeName = QString::fromLatin1(value.metaType().name()); if (!source->d->sentTypes.contains(typeName)) { QRO_ qro(value); ds << QVariant::fromValue(qro); ds << qro.parameters; source->d->sentTypes.insert(typeName); return; } } ds << encodeVariant(value); } void QDataStreamCodec::serializeHandshakePacket() { m_packet.setId(Handshake); m_packet << QString(protocolVersion); m_packet.finishPacket(); } void QDataStreamCodec::serializeInitPacket(const QRemoteObjectRootSource *source) { m_packet.setId(InitPacket); m_packet << source->name(); serializeProperties(source); m_packet.finishPacket(); } void QDataStreamCodec::serializeProperties(const QRemoteObjectSourceBase *source) { const SourceApiMap *api = source->m_api; //Now copy the property data const int numProperties = api->propertyCount(); m_packet << quint32(numProperties); //Number of properties for (int internalIndex = 0; internalIndex < numProperties; ++internalIndex) serializeProperty(source, internalIndex); } bool deserializeQVariantList(QDataStream &s, QVariantList &l) { // note: optimized version of: QDataStream operator>>(QDataStream& s, QList& l) quint32 c; s >> c; const qsizetype count = static_cast(c); const qsizetype listSize = l.size(); if (listSize < count) l.reserve(count); else if (listSize > count) l.resize(count); for (int i = 0; i < l.size(); ++i) { if (s.atEnd()) return false; s >> l[i]; } for (auto i = l.size(); i < count; ++i) { if (s.atEnd()) return false; s >> l.emplace_back(); } return true; } void QDataStreamCodec::deserializeInitPacket(QDataStream &in, QVariantList &values) { const bool success = deserializeQVariantList(in, values); Q_ASSERT(success); Q_UNUSED(success) } void QDataStreamCodec::serializeInitDynamicPacket(const QRemoteObjectRootSource *source) { m_packet.setId(InitDynamicPacket); m_packet << source->name(); serializeDefinition(m_packet, source); serializeProperties(source); m_packet.finishPacket(); } static ObjectType getObjectType(const QString &typeName) { if (typeName == QLatin1String("QAbstractItemModelAdapter")) return ObjectType::MODEL; auto tid = QMetaType::fromName(typeName.toUtf8()).id(); if (tid == QMetaType::UnknownType) return ObjectType::CLASS; QMetaType type(tid); auto mo = type.metaObject(); if (mo && mo->inherits(&QAbstractItemModel::staticMetaObject)) return ObjectType::MODEL; return ObjectType::CLASS; } static QByteArrayView resolveEnumName(QMetaType t, bool &isFlag) { // Takes types like `MyPOD::Position` or `QFlags` and returns 'Position` QByteArrayView enumName(t.name()); isFlag = enumName.startsWith("QFlags<"); auto lastColon = enumName.lastIndexOf(':'); if (lastColon >= 0) enumName = QByteArrayView(t.name() + lastColon + 1); if (isFlag) enumName.chop(1); return enumName; } static QMetaEnum metaEnumFromType(QMetaType t) { if (t.flags().testFlag(QMetaType::IsEnumeration)) { if (const QMetaObject *m = t.metaObject()) { bool isFlag; auto enumName = resolveEnumName(t, isFlag); if (isFlag) { for (int i = m->enumeratorOffset(); i < m->enumeratorCount(); i++) { auto testType = m->enumerator(i); if (testType.isFlag() && enumName.compare(QByteArrayView(testType.enumName())) == 0) return testType; } } return m->enumerator(m->indexOfEnumerator(enumName.data())); } } return QMetaEnum(); } static bool checkEnum(QMetaType metaType, QSet &enums) { if (metaType.flags().testFlag(QMetaType::IsEnumeration)) { QMetaEnum meta = metaEnumFromType(metaType); enums.insert(meta); return true; } return false; } static void recurseMetaobject(const QMetaObject *mo, QSet &gadgets, QSet &enums) { if (!mo || gadgets.contains(mo)) return; gadgets.insert(mo); const int numProperties = mo->propertyCount(); for (int i = 0; i < numProperties; ++i) { const auto property = mo->property(i); if (checkEnum(property.metaType(), enums)) continue; if (property.metaType().flags().testFlag(QMetaType::IsGadget)) recurseMetaobject(property.metaType().metaObject(), gadgets, enums); } } // A Source may only use a subset of the metaobjects properties/signals/slots, so we only search // the ones in the API. For nested pointer types, we will have another api to limit the search. // For nested PODs/enums, we search the entire qobject (using the recurseMetaobject call()). void recurseForGadgets(QSet &gadgets, QSet &enums, const QRemoteObjectSourceBase *source) { const SourceApiMap *api = source->m_api; const int numSignals = api->signalCount(); const int numMethods = api->methodCount(); const int numProperties = api->propertyCount(); for (int si = 0; si < numSignals; ++si) { const int params = api->signalParameterCount(si); for (int pi = 0; pi < params; ++pi) { const int type = api->signalParameterType(si, pi); const auto metaType = QMetaType(type); if (checkEnum(metaType, enums)) continue; if (!metaType.flags().testFlag(QMetaType::IsGadget)) continue; const auto mo = metaType.metaObject(); if (source->d->sentTypes.contains(QLatin1String(mo->className()))) continue; recurseMetaobject(mo, gadgets, enums); source->d->sentTypes.insert(QLatin1String(mo->className())); } } for (int mi = 0; mi < numMethods; ++mi) { const int params = api->methodParameterCount(mi); for (int pi = 0; pi < params; ++pi) { const int type = api->methodParameterType(mi, pi); const auto metaType = QMetaType(type); if (checkEnum(metaType, enums)) continue; if (!metaType.flags().testFlag(QMetaType::IsGadget)) continue; const auto mo = metaType.metaObject(); if (source->d->sentTypes.contains(QLatin1String(mo->className()))) continue; recurseMetaobject(mo, gadgets, enums); source->d->sentTypes.insert(QLatin1String(mo->className())); } } for (int pi = 0; pi < numProperties; ++pi) { const int index = api->sourcePropertyIndex(pi); Q_ASSERT(index >= 0); const auto target = api->isAdapterProperty(pi) ? source->m_adapter : source->m_object; const auto metaProperty = target->metaObject()->property(index); const auto metaType = metaProperty.metaType(); if (checkEnum(metaType, enums)) continue; if (metaType.flags().testFlag(QMetaType::PointerToQObject)) { auto const objectType = getObjectType(QString::fromLatin1(metaProperty.typeName())); if (objectType == ObjectType::CLASS) { auto const childSource = source->m_children.value(pi); if (childSource->m_object) recurseForGadgets(gadgets, enums, childSource); } } if (!metaType.flags().testFlag(QMetaType::IsGadget)) continue; const auto mo = metaType.metaObject(); if (source->d->sentTypes.contains(QLatin1String(mo->className()))) continue; recurseMetaobject(mo, gadgets, enums); source->d->sentTypes.insert(QLatin1String(mo->className())); } } static bool checkForEnumsInSource(const QMetaObject *meta, const QRemoteObjectSourceBase *source) { if (source->m_object->inherits(meta->className())) return true; for (const auto &child : source->m_children) { if (child->m_object && checkForEnumsInSource(meta, child)) return true; } return false; } static void serializeEnum(QDataStream &ds, const QMetaEnum &enumerator) { ds << QByteArray::fromRawData(enumerator.name(), qsizetype(qstrlen(enumerator.name()))); ds << enumerator.isFlag(); ds << enumerator.isScoped(); const auto typeName = QByteArray(enumerator.scope()).append("::").append(enumerator.name()); const quint32 size = quint32(QMetaType::fromName(typeName.constData()).sizeOf()); ds << size; #ifdef QTRO_VERBOSE_PROTOCOL qDebug(" Enum (name = %s, size = %d, isFlag = %s, isScoped = %s):", enumerator.name(), size, enumerator.isFlag() ? "true" : "false", enumerator.isScoped() ? "true" : "false"); #endif const int keyCount = enumerator.keyCount(); ds << keyCount; for (int k = 0; k < keyCount; ++k) { ds << QByteArray::fromRawData(enumerator.key(k), qsizetype(qstrlen(enumerator.key(k)))); ds << enumerator.value(k); #ifdef QTRO_VERBOSE_PROTOCOL qDebug(" Key %d (name = %s, value = %d):", k, enumerator.key(k), enumerator.value(k)); #endif } } static void serializeGadgets(QDataStream &ds, const QSet &gadgets, const QSet &enums, const QRemoteObjectSourceBase *source=nullptr) { // Determine how to handle the enums found QSet qtEnums; QSet dynamicEnumMetaObjects; for (const auto &metaEnum : enums) { auto const metaObject = metaEnum.enclosingMetaObject(); if (gadgets.contains(metaObject)) // Part of a gadget will we serialize continue; // This checks if the enum is defined in our object heirarchy, in which case it will // already have been serialized. if (source && checkForEnumsInSource(metaObject, source->d->root)) continue; // qtEnums are enumerations already known by Qt, so we only need register them. // We don't need to send all of the key/value data. if (metaObject == &Qt::staticMetaObject) // Are the other Qt metaclasses for enums? qtEnums.insert(metaEnum); else dynamicEnumMetaObjects.insert(metaEnum.enclosingMetaObject()); } ds << quint32(qtEnums.size()); for (const auto &metaEnum : qtEnums) { QByteArray enumName(metaEnum.scope()); enumName.append("::", 2).append(metaEnum.name()); ds << enumName; } const auto allMetaObjects = gadgets + dynamicEnumMetaObjects; ds << quint32(allMetaObjects.size()); #ifdef QTRO_VERBOSE_PROTOCOL qDebug() << " Found" << gadgets.size() << "gadget/pod and" << (allMetaObjects.size() - gadgets.size()) << "enum types"; int i = 0; #endif // There isn't an easy way to update a metaobject incrementally, so we // send all of the metaobject's enums, but no properties, when an external // enum is requested. for (auto const meta : allMetaObjects) { ds << QByteArray::fromRawData(meta->className(), qsizetype(qstrlen(meta->className()))); int propertyCount = gadgets.contains(meta) ? meta->propertyCount() : 0; ds << quint32(propertyCount); #ifdef QTRO_VERBOSE_PROTOCOL qDebug(" Gadget %d (name = %s, # properties = %d, # enums = %d):", i++, meta->className(), propertyCount, meta->enumeratorCount() - meta->enumeratorOffset()); #endif for (int j = 0; j < propertyCount; j++) { auto prop = meta->property(j); #ifdef QTRO_VERBOSE_PROTOCOL qDebug(" Data member %d (name = %s, type = %s):", j, prop.name(), prop.typeName()); #endif ds << QByteArray::fromRawData(prop.name(), qsizetype(qstrlen(prop.name()))); ds << QByteArray::fromRawData(prop.typeName(), qsizetype(qstrlen(prop.typeName()))); } int enumCount = meta->enumeratorCount() - meta->enumeratorOffset(); ds << quint32(enumCount); for (int j = meta->enumeratorOffset(); j < meta->enumeratorCount(); j++) { auto const enumMeta = meta->enumerator(j); serializeEnum(ds, enumMeta); } } } void QDataStreamCodec::serializeDefinition(QDataStream &ds, const QRemoteObjectSourceBase *source) { const SourceApiMap *api = source->m_api; const QByteArray desiredClassName(api->typeName().toLatin1()); const QByteArray originalClassName = api->className(); // The dynamic class will be called typeName on the receiving side of this definition // However, there are types like enums that have the QObject's class name. Replace() // will convert a parameter such as "ParentClassSource::MyEnum" to "ParentClass::MyEnum" // so the type can be properly resolved and registered. auto replace = [&originalClassName, &desiredClassName](QByteArray &name) { name.replace(originalClassName, desiredClassName); }; ds << source->m_api->typeName(); #ifdef QTRO_VERBOSE_PROTOCOL qDebug() << "Serializing definition for" << source->m_api->typeName(); #endif //Now copy the property data const int numEnums = api->enumCount(); const auto metaObject = source->m_object->metaObject(); ds << quint32(numEnums); //Number of Enums #ifdef QTRO_VERBOSE_PROTOCOL qDebug() << " Found" << numEnums << "enumeration types"; #endif for (int i = 0; i < numEnums; ++i) { auto enumerator = metaObject->enumerator(api->sourceEnumIndex(i)); Q_ASSERT(enumerator.isValid()); serializeEnum(ds, enumerator); } QSet gadgets; QSet enums; recurseForGadgets(gadgets, enums, source); serializeGadgets(ds, gadgets, enums, source); const int numSignals = api->signalCount(); ds << quint32(numSignals); //Number of signals for (int i = 0; i < numSignals; ++i) { const int index = api->sourceSignalIndex(i); Q_ASSERT(index >= 0); auto signature = api->signalSignature(i); replace(signature); const int count = api->signalParameterCount(i); for (int pi = 0; pi < count; ++pi) { const auto metaType = QMetaType(api->signalParameterType(i, pi)); if (isSequentialGadgetType(metaType)) signature.replace(metaType.name(), "QtROSequentialContainer"); else if (isAssociativeGadgetType(metaType)) signature.replace(metaType.name(), "QtROAssociativeContainer"); } #ifdef QTRO_VERBOSE_PROTOCOL qDebug() << " Signal" << i << "(signature =" << signature << "parameter names =" << api->signalParameterNames(i) << ")"; #endif ds << signature; ds << api->signalParameterNames(i); } const int numMethods = api->methodCount(); ds << quint32(numMethods); //Number of methods for (int i = 0; i < numMethods; ++i) { const int index = api->sourceMethodIndex(i); Q_ASSERT(index >= 0); auto signature = api->methodSignature(i); replace(signature); const int count = api->methodParameterCount(i); for (int pi = 0; pi < count; ++pi) { const auto metaType = QMetaType(api->methodParameterType(i, pi)); if (isSequentialGadgetType(metaType)) signature.replace(metaType.name(), "QtROSequentialContainer"); else if (isAssociativeGadgetType(metaType)) signature.replace(metaType.name(), "QtROAssociativeContainer"); } auto typeName = api->typeName(i); replace(typeName); #ifdef QTRO_VERBOSE_PROTOCOL qDebug() << " Slot" << i << "(signature =" << signature << "parameter names =" << api->methodParameterNames(i) << "return type =" << typeName << ")"; #endif ds << signature; ds << typeName; ds << api->methodParameterNames(i); } const int numProperties = api->propertyCount(); ds << quint32(numProperties); //Number of properties for (int i = 0; i < numProperties; ++i) { const int index = api->sourcePropertyIndex(i); Q_ASSERT(index >= 0); const auto target = api->isAdapterProperty(i) ? source->m_adapter : source->m_object; const auto metaProperty = target->metaObject()->property(index); ds << metaProperty.name(); #ifdef QTRO_VERBOSE_PROTOCOL qDebug() << " Property" << i << "name =" << metaProperty.name(); #endif if (metaProperty.metaType().flags().testFlag(QMetaType::PointerToQObject)) { auto objectType = getObjectType(QLatin1String(metaProperty.typeName())); ds << (objectType == ObjectType::CLASS ? "QObject*" : "QAbstractItemModel*"); #ifdef QTRO_VERBOSE_PROTOCOL qDebug() << " Type:" << (objectType == ObjectType::CLASS ? "QObject*" : "QAbstractItemModel*"); #endif } else { if (isSequentialGadgetType(metaProperty.metaType())) { ds << "QtROSequentialContainer"; #ifdef QTRO_VERBOSE_PROTOCOL qDebug() << " Type:" << "QtROSequentialContainer"; #endif } else if (isAssociativeGadgetType(metaProperty.metaType())) { ds << "QtROAssociativeContainer"; #ifdef QTRO_VERBOSE_PROTOCOL qDebug() << " Type:" << "QtROAssociativeContainer"; #endif } else { ds << metaProperty.typeName(); #ifdef QTRO_VERBOSE_PROTOCOL qDebug() << " Type:" << metaProperty.typeName(); #endif } } if (metaProperty.notifySignalIndex() == -1) { ds << QByteArray(); #ifdef QTRO_VERBOSE_PROTOCOL qDebug() << " Notification signal: None"; #endif } else { auto signature = metaProperty.notifySignal().methodSignature(); replace(signature); ds << signature; #ifdef QTRO_VERBOSE_PROTOCOL qDebug() << " Notification signal:" << signature; #endif } } } void QDataStreamCodec::serializeAddObjectPacket(const QString &name, bool isDynamic) { m_packet.setId(AddObject); m_packet << name; m_packet << isDynamic; m_packet.finishPacket(); } void QDataStreamCodec::deserializeAddObjectPacket(QDataStream &ds, bool &isDynamic) { ds >> isDynamic; } void QDataStreamCodec::serializeRemoveObjectPacket(const QString &name) { m_packet.setId(RemoveObject); m_packet << name; m_packet.finishPacket(); } //There is no deserializeRemoveObjectPacket - no parameters other than id and name void QDataStreamCodec::serializeInvokePacket(const QString &name, int call, int index, const QVariantList &args, int serialId, int propertyIndex) { m_packet.setId(InvokePacket); m_packet << name; m_packet << call; m_packet << index; m_packet << quint32(args.size()); for (const auto &arg : args) m_packet << encodeVariant(arg); m_packet << serialId; m_packet << propertyIndex; m_packet.finishPacket(); } void QDataStreamCodec::deserializeInvokePacket(QDataStream& in, int &call, int &index, QVariantList &args, int &serialId, int &propertyIndex) { in >> call; in >> index; const bool success = deserializeQVariantList(in, args); Q_ASSERT(success); Q_UNUSED(success) in >> serialId; in >> propertyIndex; } void QDataStreamCodec::serializeInvokeReplyPacket(const QString &name, int ackedSerialId, const QVariant &value) { m_packet.setId(InvokeReplyPacket); m_packet << name; m_packet << ackedSerialId; m_packet << value; m_packet.finishPacket(); } void QDataStreamCodec::deserializeInvokeReplyPacket(QDataStream& in, int &ackedSerialId, QVariant &value){ in >> ackedSerialId; in >> value; } void QDataStreamCodec::serializePropertyChangePacket(QRemoteObjectSourceBase *source, int signalIndex) { int internalIndex = source->m_api->propertyRawIndexFromSignal(signalIndex); m_packet.setId(PropertyChangePacket); m_packet << source->name(); m_packet << internalIndex; serializeProperty(source, internalIndex); m_packet.finishPacket(); } void QDataStreamCodec::deserializePropertyChangePacket(QDataStream& in, int &index, QVariant &value) { in >> index; in >> value; } void QDataStreamCodec::serializeObjectListPacket(const ObjectInfoList &objects) { m_packet.setId(ObjectList); m_packet << objects; m_packet.finishPacket(); } void QDataStreamCodec::deserializeObjectListPacket(QDataStream &in, ObjectInfoList &objects) { in >> objects; } void QDataStreamCodec::serializePingPacket(const QString &name) { m_packet.setId(Ping); m_packet << name; m_packet.finishPacket(); } void QDataStreamCodec::serializePongPacket(const QString &name) { m_packet.setId(Pong); m_packet << name; m_packet.finishPacket(); } QRO_::QRO_(QRemoteObjectSourceBase *source) : name(source->name()) , typeName(source->m_api->typeName()) , type(source->m_adapter ? ObjectType::MODEL : getObjectType(typeName)) , isNull(source->m_object == nullptr) , classDefinition() , parameters() {} QRO_::QRO_(const QVariant &value) : type(ObjectType::GADGET) , isNull(false) { const auto metaType = value.metaType(); auto meta = metaType.metaObject(); QDataStream out(&classDefinition, QIODevice::WriteOnly); const int numProperties = meta->propertyCount(); const auto name = metaType.name(); const auto typeName = QByteArray::fromRawData(name, qsizetype(qstrlen(name))); out << quint32(0) << quint32(1); out << typeName; out << numProperties; #ifdef QTRO_VERBOSE_PROTOCOL qDebug("Serializing POD definition to QRO_ (name = %s)", typeName.constData()); #endif for (int i = 0; i < numProperties; ++i) { const auto property = meta->property(i); #ifdef QTRO_VERBOSE_PROTOCOL qDebug(" Data member %d (name = %s, type = %s):", i, property.name(), property.typeName()); #endif out << QByteArray::fromRawData(property.name(), qsizetype(qstrlen(property.name()))); out << QByteArray::fromRawData(property.typeName(), qsizetype(qstrlen(property.typeName()))); } int enumCount = meta->enumeratorCount() - meta->enumeratorOffset(); out << quint32(enumCount); for (int j = meta->enumeratorOffset(); j < meta->enumeratorCount(); j++) { auto const enumMeta = meta->enumerator(j); serializeEnum(out, enumMeta); } QDataStream ds(¶meters, QIODevice::WriteOnly); ds << value; #ifdef QTRO_VERBOSE_PROTOCOL qDebug() << " Value:" << value; #endif } QDataStream &operator<<(QDataStream &stream, const QRO_ &info) { stream << info.name << info.typeName << quint8(info.type) << info.classDefinition << info.isNull; qCDebug(QT_REMOTEOBJECT) << "Serializing " << info; // info.parameters will be filled in by serializeProperty return stream; } QDataStream &operator>>(QDataStream &stream, QRO_ &info) { quint8 tmpType; stream >> info.name >> info.typeName >> tmpType >> info.classDefinition >> info.isNull; info.type = static_cast(tmpType); qCDebug(QT_REMOTEOBJECT) << "Deserializing " << info; if (!info.isNull) stream >> info.parameters; return stream; } QSQ_::QSQ_(const QVariant &variant) { QSequentialIterable sequence; QMetaType valueType; if (variant.metaType() == QMetaType::fromType()) { auto container = static_cast(variant.constData()); typeName = container->m_typeName; valueType = container->m_valueType; valueTypeName = container->m_valueTypeName; sequence = QSequentialIterable(reinterpret_cast(variant.constData())); } else { sequence = variant.value(); typeName = QByteArray(variant.metaType().name()); valueType = sequence.metaContainer().valueMetaType(); valueTypeName = QByteArray(valueType.name()); } #ifdef QTRO_VERBOSE_PROTOCOL qDebug("Serializing POD sequence to QSQ_ (type = %s, valueType = %s) with size = %lld", typeName.constData(), valueTypeName.constData(), sequence.size()); #endif QDataStream ds(&values, QIODevice::WriteOnly); ds << valueTypeName; auto pos = ds.device()->pos(); ds << quint32(sequence.size()); for (const auto &v : sequence) { if (!valueType.save(ds, v.data())) { ds.device()->seek(pos); ds.resetStatus(); ds << quint32(0); values.resize(ds.device()->pos()); qWarning("QSQ_: unable to save type '%s', sending empty list.", valueType.name()); break; } } } QDataStream &operator<<(QDataStream &stream, const QSQ_ &sequence) { stream << sequence.typeName << sequence.valueTypeName << sequence.values; qCDebug(QT_REMOTEOBJECT) << "Serializing " << sequence; return stream; } QDataStream &operator>>(QDataStream &stream, QSQ_ &sequence) { stream >> sequence.typeName >> sequence.valueTypeName >> sequence.values; qCDebug(QT_REMOTEOBJECT) << "Deserializing " << sequence; return stream; } QAS_::QAS_(const QVariant &variant) { QAssociativeIterable map; QMetaType keyType, transferType, valueType; const QtROAssociativeContainer *container = nullptr; if (variant.metaType() == QMetaType::fromType()) { container = static_cast(variant.constData()); typeName = container->m_typeName; keyType = container->m_keyType; keyTypeName = container->m_keyTypeName; valueType = container->m_valueType; valueTypeName = container->m_valueTypeName; map = QAssociativeIterable(reinterpret_cast(variant.constData())); } else { map = variant.value(); typeName = QByteArray(variant.metaType().name()); keyType = map.metaContainer().keyMetaType(); keyTypeName = QByteArray(keyType.name()); valueType = map.metaContainer().mappedMetaType(); valueTypeName = QByteArray(valueType.name()); } // Special handling for enums... transferType = keyType; if (keyType.flags().testFlag(QMetaType::IsEnumeration)) { transferType = transferTypeForEnum(keyType); auto meta = keyType.metaObject(); // If we are the source, make sure the typeName is converted for any downstream replicas // the `meta` variable will be non-null when the enum is a class enum if (meta && !container) { const int ind = meta->indexOfClassInfo(QCLASSINFO_REMOTEOBJECT_TYPE); if (ind >= 0) { bool isFlag = keyTypeName.startsWith("QFlags<"); if (isFlag || keyTypeName.startsWith(meta->className())) { #ifdef QTRO_VERBOSE_PROTOCOL QByteArray orig(keyTypeName); #endif if (isFlag) { // Q_DECLARE_FLAGS(Flags, Enum) -> `typedef QFlags Flags;` // We know we have an enum for `Flags` because we sent the enums // from the source, so we just need to look up the alias. keyTypeName = keyTypeName.mid(7); keyTypeName.chop(1); // Remove trailing '>' int index = keyTypeName.lastIndexOf(':'); for (int i = meta->enumeratorOffset(); i < meta->enumeratorCount(); i++) { auto en = meta->enumerator(i); auto name = keyTypeName.data() + index + 1; if (en.isFlag() && qstrcmp(en.enumName(), name) == 0) keyTypeName.replace(index + 1, qstrlen(en.enumName()), en.name()); } } keyTypeName.replace(meta->className(), meta->classInfo(ind).value()); QByteArray repName(meta->classInfo(ind).value()); repName.append("Replica"); typeName.replace(meta->className(), repName); #ifdef QTRO_VERBOSE_PROTOCOL qDebug() << "Converted map key typename from" << orig << "to" << keyTypeName; #endif } } } } #ifdef QTRO_VERBOSE_PROTOCOL qDebug("Serializing POD map to QAS_ (type = %s, keyType = %s, valueType = %s), size = %lld", typeName.constData(), keyTypeName.constData(), valueTypeName.constData(), map.size()); #endif QDataStream ds(&values, QIODevice::WriteOnly); ds << keyTypeName; ds << valueTypeName; auto pos = ds.device()->pos(); ds << quint32(map.size()); QAssociativeIterable::const_iterator iter = map.begin(); for (int i = 0; i < map.size(); i++) { QVariant key(container ? container->m_keys.at(i) : iter.key()); if (transferType != keyType) key.convert(transferType); if (!transferType.save(ds, key.data())) { ds.device()->seek(pos); ds.resetStatus(); ds << quint32(0); values.resize(ds.device()->pos()); qWarning("QAS_: unable to save key '%s', sending empty map.", keyType.name()); break; } if (!valueType.save(ds, iter.value().data())) { ds.device()->seek(pos); ds.resetStatus(); ds << quint32(0); values.resize(ds.device()->pos()); qWarning("QAS_: unable to save value '%s', sending empty map.", valueType.name()); break; } iter++; } } QDataStream &operator<<(QDataStream &stream, const QAS_ &map) { stream << map.typeName << map.keyTypeName << map.valueTypeName << map.values; qCDebug(QT_REMOTEOBJECT) << "Serializing " << map; return stream; } QDataStream &operator>>(QDataStream &stream, QAS_ &map) { stream >> map.typeName >> map.keyTypeName >> map.valueTypeName >> map.values; qCDebug(QT_REMOTEOBJECT) << "Deserializing " << map; return stream; } DataStreamPacket::DataStreamPacket(quint16 id) : QDataStream(&array, QIODevice::WriteOnly), baseAddress(0), size(0) { this->setVersion(QtRemoteObjects::dataStreamVersion); this->setByteOrder(QDataStream::LittleEndian); *this << quint32(0); *this << id; } void CodecBase::send(const QSet &connections) { const auto bytearray = getPayload(); for (auto conn : connections) conn->write(bytearray); reset(); } void CodecBase::send(const QList &connections) { const auto bytearray = getPayload(); for (auto conn : connections) conn->write(bytearray); reset(); } void CodecBase::send(QtROIoDeviceBase *connection) { const auto bytearray = getPayload(); connection->write(bytearray); reset(); } } // namespace QRemoteObjectPackets QT_IMPL_METATYPE_EXTERN_TAGGED(QRemoteObjectPackets::QRO_, QRemoteObjectPackets__QRO_) QT_END_NAMESPACE