1// Copyright (C) 2017 Ford Motor Company
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:data-parser
4
5#include <QtCore/qabstractitemmodel.h>
6#include <QtCore/qbytearrayview.h>
7
8#include "qremoteobjectcontainers_p.h"
9#include "qremoteobjectpendingcall.h"
10#include "qremoteobjectsource.h"
11#include "qremoteobjectsource_p.h"
12#include "qremoteobjectpacket_p.h"
13#include "qconnectionfactories.h"
14#include "qconnectionfactories_p.h"
15#include <cstring>
16
17//#define QTRO_VERBOSE_PROTOCOL
18QT_BEGIN_NAMESPACE
19
20
21// Add methods so we can use QMetaEnum in a set
22// Note for both functions we are skipping string comparisons/hashes. Since the
23// metaObjects are the same, we can just use the address of the string.
24inline bool operator==(const QMetaEnum e1, const QMetaEnum e2)
25{
26 return e1.enclosingMetaObject() == e2.enclosingMetaObject()
27 && e1.name() == e2.name()
28 && e1.enumName() == e2.enumName()
29 && e1.scope() == e2.scope();
30}
31
32inline size_t qHash(const QMetaEnum &key, size_t seed=0) Q_DECL_NOTHROW
33{
34 return qHash(key: key.enclosingMetaObject(), seed) ^ qHash(key: static_cast<const void *>(key.name()), seed)
35 ^ qHash(key: static_cast<const void *>(key.enumName()), seed) ^ qHash(key: static_cast<const void *>(key.scope()), seed);
36}
37
38static bool isSequentialGadgetType(QMetaType metaType)
39{
40 if (QMetaType::canConvert(fromType: metaType, toType: QMetaType::fromType<QSequentialIterable>())) {
41 static QHash<int, bool> lookup;
42 if (!lookup.contains(key: metaType.id())) {
43 auto stubVariant = QVariant(metaType, nullptr);
44 auto asIterable = stubVariant.value<QSequentialIterable>();
45 auto valueMetaType = asIterable.metaContainer().valueMetaType();
46 lookup[metaType.id()] = valueMetaType.flags().testFlag(flag: QMetaType::IsGadget);
47 }
48 return lookup[metaType.id()];
49 }
50 return false;
51}
52
53static bool isAssociativeGadgetType(QMetaType metaType)
54{
55 if (QMetaType::canConvert(fromType: metaType, toType: QMetaType::fromType<QAssociativeIterable>())) {
56 static QHash<int, bool> lookup;
57 if (!lookup.contains(key: metaType.id())) {
58 auto stubVariant = QVariant(metaType, nullptr);
59 auto asIterable = stubVariant.value<QAssociativeIterable>();
60 auto valueMetaType = asIterable.metaContainer().mappedMetaType();
61 lookup[metaType.id()] = valueMetaType.flags().testFlag(flag: QMetaType::IsGadget);
62 }
63 return lookup[metaType.id()];
64 }
65 return false;
66}
67
68using namespace QtRemoteObjects;
69
70namespace QRemoteObjectPackets {
71
72QMetaType transferTypeForEnum(QMetaType enumType)
73{
74 const auto size = enumType.sizeOf();
75 switch (size) {
76 case 1: return QMetaType::fromType<qint8>();
77 case 2: return QMetaType::fromType<qint16>();
78 case 4: return QMetaType::fromType<qint32>();
79 // Qt currently only supports enum values of 4 or less bytes (QMetaEnum value(index) returns int)
80// case 8: args.push_back(QVariant(QMetaType::Int, argv[i + 1])); break;
81 default:
82 qCWarning(QT_REMOTEOBJECT_IO) << "Invalid enum detected (Dynamic Replica)" << enumType.name() << "with size" << size;
83 return QMetaType::fromType<qint32>();
84 }
85}
86
87// QDataStream sends QVariants of custom types by sending their typename, allowing decode
88// on the receiving side. For QtRO and enums, this won't work, as the enums have different
89// scopes. E.g., the examples have ParentClassSource::MyEnum and ParentClassReplica::MyEnum.
90// Dynamic types will be created as ParentClass::MyEnum. So instead, we change the variants
91// to integers (encodeVariant) when sending them. On the receive side, the we know the
92// types of properties and the signatures for methods, so we can use that information to
93// decode the integer variant into an enum variant (via decodeVariant).
94QVariant encodeVariant(const QVariant &value)
95{
96 const auto metaType = value.metaType();
97 if (metaType.flags().testFlag(flag: QMetaType::IsEnumeration)) {
98 auto converted = QVariant(value);
99 auto transferType = transferTypeForEnum(enumType: metaType);
100 converted.convert(type: transferType);
101#ifdef QTRO_VERBOSE_PROTOCOL
102 qDebug() << "Converting from enum to integer type" << transferType.sizeOf() << converted << value;
103#endif
104 return converted;
105 }
106 if (isSequentialGadgetType(metaType)) { // Doesn't include QtROSequentialContainer
107 // TODO Way to create the QVariant without copying the QSQ_?
108 QSQ_ sequence(value);
109#ifdef QTRO_VERBOSE_PROTOCOL
110 qDebug() << "Encoding sequential container" << metaType.name() << "to QSQ_ to transmit";
111#endif
112 return QVariant::fromValue<QSQ_>(value: sequence);
113 }
114 if (metaType == QMetaType::fromType<QtROSequentialContainer>()) {
115 QSQ_ sequence(value);
116#ifdef QTRO_VERBOSE_PROTOCOL
117 qDebug() << "Encoding QtROSequentialContainer container to QSQ_ to transmit";
118#endif
119 return QVariant::fromValue<QSQ_>(value: sequence);
120 }
121 if (isAssociativeGadgetType(metaType)) { // Doesn't include QtROAssociativeContainer
122 QAS_ map(value);
123#ifdef QTRO_VERBOSE_PROTOCOL
124 qDebug() << "Encoding associative container" << metaType.name() << "to QAS_ to transmit";
125#endif
126 return QVariant::fromValue<QAS_>(value: map);
127 }
128 if (metaType == QMetaType::fromType<QtROAssociativeContainer>()) {
129 QAS_ map(value);
130#ifdef QTRO_VERBOSE_PROTOCOL
131 qDebug() << "Encoding QtROAssociativeContainer container to QAS_ to transmit";
132#endif
133 return QVariant::fromValue<QAS_>(value: map);
134 }
135 return value;
136}
137
138QVariant decodeVariant(QVariant &&value, QMetaType metaType)
139{
140 if (metaType.flags().testFlag(flag: QMetaType::IsEnumeration)) {
141#ifdef QTRO_VERBOSE_PROTOCOL
142 QVariant encoded(value);
143#endif
144 value.convert(type: metaType);
145#ifdef QTRO_VERBOSE_PROTOCOL
146 qDebug() << "Converting to enum from integer type" << value << encoded;
147#endif
148 } else if (value.metaType() == QMetaType::fromType<QRemoteObjectPackets::QSQ_>()) {
149 const auto *qsq_ = static_cast<const QRemoteObjectPackets::QSQ_ *>(value.constData());
150 QDataStream in(qsq_->values);
151 auto containerType = QMetaType::fromName(name: qsq_->typeName.constData());
152 bool isRegistered = containerType.isRegistered();
153 if (isRegistered) {
154 QVariant seq{containerType, nullptr};
155 if (!seq.canView<QSequentialIterable>()) {
156 qWarning() << "Unsupported container" << qsq_->typeName.constData()
157 << "(not viewable)";
158 return QVariant();
159 }
160 QSequentialIterable seqIter = seq.view<QSequentialIterable>();
161 if (!seqIter.metaContainer().canAddValue()) {
162 qWarning() << "Unsupported container" << qsq_->typeName.constData()
163 << "(Unable to add values)";
164 return QVariant();
165 }
166 QByteArray valueTypeName;
167 quint32 count;
168 in >> valueTypeName;
169 in >> count;
170 QMetaType valueType = QMetaType::fromName(name: valueTypeName.constData());
171 QVariant tmp{valueType, nullptr};
172 for (quint32 i = 0; i < count; i++) {
173 if (!valueType.load(stream&: in, data: tmp.data())) {
174 if (seqIter.metaContainer().canRemoveValue() || i == 0) {
175 for (quint32 ii = 0; ii < i; ii++)
176 seqIter.removeValue();
177 qWarning(msg: "QSQ_: unable to load type '%s', returning an empty list.", valueTypeName.constData());
178 } else {
179 qWarning(msg: "QSQ_: unable to load type '%s', returning a partial list.", valueTypeName.constData());
180 }
181 break;
182 }
183 seqIter.addValue(value: tmp);
184 }
185 value = seq;
186#ifdef QTRO_VERBOSE_PROTOCOL
187 qDebug() << "Decoding QSQ_ to sequential container" << containerType.name()
188 << valueTypeName;
189#endif
190 } else {
191 QtROSequentialContainer container{};
192 in >> container;
193 container.m_typeName = qsq_->typeName;
194 value = QVariant(QMetaType::fromType<QtROSequentialContainer>(), &container);
195#ifdef QTRO_VERBOSE_PROTOCOL
196 qDebug() << "Decoding QSQ_ to QtROSequentialContainer of"
197 << container.m_valueTypeName;
198#endif
199 }
200 } else if (value.metaType() == QMetaType::fromType<QRemoteObjectPackets::QAS_>()) {
201 const auto *qas_ = static_cast<const QRemoteObjectPackets::QAS_ *>(value.constData());
202 QDataStream in(qas_->values);
203 auto containerType = QMetaType::fromName(name: qas_->typeName.constData());
204 bool isRegistered = containerType.isRegistered();
205 if (isRegistered) {
206 QVariant map{containerType, nullptr};
207 if (!map.canView<QAssociativeIterable>()) {
208 qWarning() << "Unsupported container" << qas_->typeName.constData()
209 << "(not viewable)";
210 return QVariant();
211 }
212 QAssociativeIterable mapIter = map.view<QAssociativeIterable>();
213 if (!mapIter.metaContainer().canSetMappedAtKey()) {
214 qWarning() << "Unsupported container" << qas_->typeName.constData()
215 << "(Unable to insert values)";
216 return QVariant();
217 }
218 QByteArray keyTypeName, valueTypeName;
219 quint32 count;
220 in >> keyTypeName;
221 QMetaType keyType = QMetaType::fromName(name: keyTypeName.constData());
222 if (!keyType.isValid()) {
223 // This happens for class enums, where the passed keyType is <ClassName>::<enum>
224 // For a compiled replica, the keyType is <ClassName>Replica::<enum>
225 // Since the full typename is registered, we can pull the keyType from there
226 keyType = mapIter.metaContainer().keyMetaType();
227 }
228 QMetaType transferType = keyType;
229 if (keyType.flags().testFlag(flag: QMetaType::IsEnumeration))
230 transferType = transferTypeForEnum(enumType: keyType);
231 QVariant key{transferType, nullptr};
232 in >> valueTypeName;
233 QMetaType valueType = QMetaType::fromName(name: valueTypeName.constData());
234 QVariant val{valueType, nullptr};
235 in >> count;
236 for (quint32 i = 0; i < count; i++) {
237 if (!transferType.load(stream&: in, data: key.data())) {
238 map = QVariant{containerType, nullptr};
239 qWarning(msg: "QAS_: unable to load key of type '%s', returning an empty map.",
240 keyTypeName.constData());
241 break;
242 }
243 if (!valueType.load(stream&: in, data: val.data())) {
244 map = QVariant{containerType, nullptr};
245 qWarning(msg: "QAS_: unable to load value of type '%s', returning an empty map.",
246 valueTypeName.constData());
247 break;
248 }
249 if (transferType != keyType) {
250 QVariant enumKey(key);
251 enumKey.convert(type: keyType);
252 mapIter.setValue(key: enumKey, mapped: val);
253 } else {
254 mapIter.setValue(key, mapped: val);
255 }
256 }
257 value = map;
258#ifdef QTRO_VERBOSE_PROTOCOL
259 qDebug() << "Decoding QAS_ to associative container" << containerType.name()
260 << valueTypeName << keyTypeName << count << mapIter.size() << map;
261#endif
262 } else {
263 QtROAssociativeContainer container{};
264 in >> container;
265 container.m_typeName = qas_->typeName;
266 value = QVariant(QMetaType::fromType<QtROAssociativeContainer>(), &container);
267#ifdef QTRO_VERBOSE_PROTOCOL
268 qDebug() << "Decoding QAS_ to QtROAssociativeContainer of"
269 << container.m_valueTypeName;
270#endif
271 }
272 }
273 return std::move(value);
274}
275
276void QDataStreamCodec::serializeProperty(const QRemoteObjectSourceBase *source, int internalIndex)
277{
278 serializeProperty(ds&: m_packet, source, internalIndex);
279}
280
281void QDataStreamCodec::serializeProperty(QDataStream &ds, const QRemoteObjectSourceBase *source, int internalIndex)
282{
283 const int propertyIndex = source->m_api->sourcePropertyIndex(index: internalIndex);
284 Q_ASSERT (propertyIndex >= 0);
285 const auto target = source->m_api->isAdapterProperty(internalIndex) ? source->m_adapter : source->m_object;
286 const auto property = target->metaObject()->property(index: propertyIndex);
287 const QVariant value = property.read(obj: target);
288 if (property.metaType().flags().testFlag(flag: QMetaType::PointerToQObject)) {
289 auto const childSource = source->m_children.value(key: internalIndex);
290 auto valueAsPointerToQObject = qvariant_cast<QObject *>(v: value);
291 if (childSource->m_object != valueAsPointerToQObject)
292 childSource->resetObject(newObject: valueAsPointerToQObject);
293 QRO_ qro(childSource);
294 if (source->d->isDynamic && qro.type == ObjectType::CLASS && childSource->m_object && !source->d->sentTypes.contains(value: qro.typeName)) {
295 QDataStream classDef(&qro.classDefinition, QIODevice::WriteOnly);
296 serializeDefinition(classDef, childSource);
297 source->d->sentTypes.insert(value: qro.typeName);
298 }
299 ds << QVariant::fromValue<QRO_>(value: qro);
300 if (qro.isNull)
301 return;
302 const int propertyCount = childSource->m_api->propertyCount();
303 // Put the properties in a buffer, the receiver may not know how to
304 // interpret the types until it registers new ones.
305 QDataStream params(&qro.parameters, QIODevice::WriteOnly);
306 params << propertyCount;
307 for (int internalIndex = 0; internalIndex < propertyCount; ++internalIndex)
308 serializeProperty(ds&: params, source: childSource, internalIndex);
309 ds << qro.parameters;
310 return;
311 }
312 if (source->d->isDynamic && property.userType() == QMetaType::QVariant
313 && value.metaType().flags().testFlag(flag: QMetaType::IsGadget)) {
314 const auto typeName = QString::fromLatin1(ba: value.metaType().name());
315 if (!source->d->sentTypes.contains(value: typeName)) {
316 QRO_ qro(value);
317 ds << QVariant::fromValue<QRO_>(value: qro);
318 ds << qro.parameters;
319 source->d->sentTypes.insert(value: typeName);
320 return;
321 }
322 }
323 ds << encodeVariant(value);
324}
325
326void QDataStreamCodec::serializeHandshakePacket()
327{
328 m_packet.setId(Handshake);
329 m_packet << QString(protocolVersion);
330 m_packet.finishPacket();
331}
332
333void QDataStreamCodec::serializeInitPacket(const QRemoteObjectRootSource *source)
334{
335 m_packet.setId(InitPacket);
336 m_packet << source->name();
337 serializeProperties(source);
338 m_packet.finishPacket();
339}
340
341void QDataStreamCodec::serializeProperties(const QRemoteObjectSourceBase *source)
342{
343 const SourceApiMap *api = source->m_api;
344
345 //Now copy the property data
346 const int numProperties = api->propertyCount();
347 m_packet << quint32(numProperties); //Number of properties
348
349 for (int internalIndex = 0; internalIndex < numProperties; ++internalIndex)
350 serializeProperty(source, internalIndex);
351}
352
353bool deserializeQVariantList(QDataStream &s, QVariantList &l)
354{
355 // note: optimized version of: QDataStream operator>>(QDataStream& s, QList<T>& l)
356 quint32 c;
357 s >> c;
358
359 const qsizetype count = static_cast<qsizetype>(c);
360 const qsizetype listSize = l.size();
361 if (listSize < count)
362 l.reserve(asize: count);
363 else if (listSize > count)
364 l.resize(size: count);
365
366 for (int i = 0; i < l.size(); ++i)
367 {
368 if (s.atEnd())
369 return false;
370 s >> l[i];
371 }
372 for (auto i = l.size(); i < count; ++i)
373 {
374 if (s.atEnd())
375 return false;
376 s >> l.emplace_back();
377 }
378 return true;
379}
380
381void QDataStreamCodec::deserializeInitPacket(QDataStream &in, QVariantList &values)
382{
383 const bool success = deserializeQVariantList(s&: in, l&: values);
384 Q_ASSERT(success);
385 Q_UNUSED(success)
386}
387
388void QDataStreamCodec::serializeInitDynamicPacket(const QRemoteObjectRootSource *source)
389{
390 m_packet.setId(InitDynamicPacket);
391 m_packet << source->name();
392 serializeDefinition(m_packet, source);
393 serializeProperties(source);
394 m_packet.finishPacket();
395}
396
397static ObjectType getObjectType(const QString &typeName)
398{
399 if (typeName == QLatin1String("QAbstractItemModelAdapter"))
400 return ObjectType::MODEL;
401 auto tid = QMetaType::fromName(name: typeName.toUtf8()).id();
402 if (tid == QMetaType::UnknownType)
403 return ObjectType::CLASS;
404 QMetaType type(tid);
405 auto mo = type.metaObject();
406 if (mo && mo->inherits(metaObject: &QAbstractItemModel::staticMetaObject))
407 return ObjectType::MODEL;
408 return ObjectType::CLASS;
409}
410
411static QByteArrayView resolveEnumName(QMetaType t, bool &isFlag)
412{
413 // Takes types like `MyPOD::Position` or `QFlags<MyPOD::Position>` and returns 'Position`
414 QByteArrayView enumName(t.name());
415 isFlag = enumName.startsWith(other: "QFlags<");
416 auto lastColon = enumName.lastIndexOf(ch: ':');
417 if (lastColon >= 0)
418 enumName = QByteArrayView(t.name() + lastColon + 1);
419 if (isFlag)
420 enumName.chop(n: 1);
421 return enumName;
422}
423
424static QMetaEnum metaEnumFromType(QMetaType t)
425{
426 if (t.flags().testFlag(flag: QMetaType::IsEnumeration)) {
427 if (const QMetaObject *m = t.metaObject()) {
428 bool isFlag;
429 auto enumName = resolveEnumName(t, isFlag);
430 if (isFlag) {
431 for (int i = m->enumeratorOffset(); i < m->enumeratorCount(); i++) {
432 auto testType = m->enumerator(index: i);
433 if (testType.isFlag() &&
434 enumName.compare(a: QByteArrayView(testType.enumName())) == 0)
435 return testType;
436 }
437 }
438 return m->enumerator(index: m->indexOfEnumerator(name: enumName.data()));
439 }
440 }
441 return QMetaEnum();
442}
443
444static bool checkEnum(QMetaType metaType, QSet<QMetaEnum> &enums)
445{
446 if (metaType.flags().testFlag(flag: QMetaType::IsEnumeration)) {
447 QMetaEnum meta = metaEnumFromType(t: metaType);
448 enums.insert(value: meta);
449 return true;
450 }
451 return false;
452}
453
454static void recurseMetaobject(const QMetaObject *mo, QSet<const QMetaObject *> &gadgets, QSet<QMetaEnum> &enums)
455{
456 if (!mo || gadgets.contains(value: mo))
457 return;
458 gadgets.insert(value: mo);
459 const int numProperties = mo->propertyCount();
460 for (int i = 0; i < numProperties; ++i) {
461 const auto property = mo->property(index: i);
462 if (checkEnum(metaType: property.metaType(), enums))
463 continue;
464 if (property.metaType().flags().testFlag(flag: QMetaType::IsGadget))
465 recurseMetaobject(mo: property.metaType().metaObject(), gadgets, enums);
466 }
467}
468
469// A Source may only use a subset of the metaobjects properties/signals/slots, so we only search
470// the ones in the API. For nested pointer types, we will have another api to limit the search.
471// For nested PODs/enums, we search the entire qobject (using the recurseMetaobject call()).
472void recurseForGadgets(QSet<const QMetaObject *> &gadgets, QSet<QMetaEnum> &enums, const QRemoteObjectSourceBase *source)
473{
474 const SourceApiMap *api = source->m_api;
475
476 const int numSignals = api->signalCount();
477 const int numMethods = api->methodCount();
478 const int numProperties = api->propertyCount();
479
480 for (int si = 0; si < numSignals; ++si) {
481 const int params = api->signalParameterCount(index: si);
482 for (int pi = 0; pi < params; ++pi) {
483 const int type = api->signalParameterType(sigIndex: si, paramIndex: pi);
484 const auto metaType = QMetaType(type);
485 if (checkEnum(metaType, enums))
486 continue;
487 if (!metaType.flags().testFlag(flag: QMetaType::IsGadget))
488 continue;
489 const auto mo = metaType.metaObject();
490 if (source->d->sentTypes.contains(value: QLatin1String(mo->className())))
491 continue;
492 recurseMetaobject(mo, gadgets, enums);
493 source->d->sentTypes.insert(value: QLatin1String(mo->className()));
494 }
495 }
496
497 for (int mi = 0; mi < numMethods; ++mi) {
498 const int params = api->methodParameterCount(index: mi);
499 for (int pi = 0; pi < params; ++pi) {
500 const int type = api->methodParameterType(methodIndex: mi, paramIndex: pi);
501 const auto metaType = QMetaType(type);
502 if (checkEnum(metaType, enums))
503 continue;
504 if (!metaType.flags().testFlag(flag: QMetaType::IsGadget))
505 continue;
506 const auto mo = metaType.metaObject();
507 if (source->d->sentTypes.contains(value: QLatin1String(mo->className())))
508 continue;
509 recurseMetaobject(mo, gadgets, enums);
510 source->d->sentTypes.insert(value: QLatin1String(mo->className()));
511 }
512 }
513 for (int pi = 0; pi < numProperties; ++pi) {
514 const int index = api->sourcePropertyIndex(index: pi);
515 Q_ASSERT(index >= 0);
516 const auto target = api->isAdapterProperty(pi) ? source->m_adapter : source->m_object;
517 const auto metaProperty = target->metaObject()->property(index);
518 const auto metaType = metaProperty.metaType();
519 if (checkEnum(metaType, enums))
520 continue;
521 if (metaType.flags().testFlag(flag: QMetaType::PointerToQObject)) {
522 auto const objectType = getObjectType(typeName: QString::fromLatin1(ba: metaProperty.typeName()));
523 if (objectType == ObjectType::CLASS) {
524 auto const childSource = source->m_children.value(key: pi);
525 if (childSource->m_object)
526 recurseForGadgets(gadgets, enums, source: childSource);
527 }
528 }
529 if (!metaType.flags().testFlag(flag: QMetaType::IsGadget))
530 continue;
531 const auto mo = metaType.metaObject();
532 if (source->d->sentTypes.contains(value: QLatin1String(mo->className())))
533 continue;
534 recurseMetaobject(mo, gadgets, enums);
535 source->d->sentTypes.insert(value: QLatin1String(mo->className()));
536 }
537}
538
539static bool checkForEnumsInSource(const QMetaObject *meta, const QRemoteObjectSourceBase *source)
540{
541 if (source->m_object->inherits(classname: meta->className()))
542 return true;
543 for (const auto &child : source->m_children) {
544 if (child->m_object && checkForEnumsInSource(meta, source: child))
545 return true;
546 }
547 return false;
548}
549
550static void serializeEnum(QDataStream &ds, const QMetaEnum &enumerator)
551{
552 ds << QByteArray::fromRawData(data: enumerator.name(), size: qsizetype(qstrlen(str: enumerator.name())));
553 ds << enumerator.isFlag();
554 ds << enumerator.isScoped();
555 const auto typeName = QByteArray(enumerator.scope()).append(s: "::").append(s: enumerator.name());
556 const quint32 size = quint32(QMetaType::fromName(name: typeName.constData()).sizeOf());
557 ds << size;
558#ifdef QTRO_VERBOSE_PROTOCOL
559 qDebug(" Enum (name = %s, size = %d, isFlag = %s, isScoped = %s):", enumerator.name(), size, enumerator.isFlag() ? "true" : "false", enumerator.isScoped() ? "true" : "false");
560#endif
561 const int keyCount = enumerator.keyCount();
562 ds << keyCount;
563 for (int k = 0; k < keyCount; ++k) {
564 ds << QByteArray::fromRawData(data: enumerator.key(index: k), size: qsizetype(qstrlen(str: enumerator.key(index: k))));
565 ds << enumerator.value(index: k);
566#ifdef QTRO_VERBOSE_PROTOCOL
567 qDebug(" Key %d (name = %s, value = %d):", k, enumerator.key(k), enumerator.value(k));
568#endif
569 }
570}
571
572static void serializeGadgets(QDataStream &ds, const QSet<const QMetaObject *> &gadgets, const QSet<QMetaEnum> &enums, const QRemoteObjectSourceBase *source=nullptr)
573{
574 // Determine how to handle the enums found
575 QSet<QMetaEnum> qtEnums;
576 QSet<const QMetaObject *> dynamicEnumMetaObjects;
577 for (const auto &metaEnum : enums) {
578 auto const metaObject = metaEnum.enclosingMetaObject();
579 if (gadgets.contains(value: metaObject)) // Part of a gadget will we serialize
580 continue;
581 // This checks if the enum is defined in our object heirarchy, in which case it will
582 // already have been serialized.
583 if (source && checkForEnumsInSource(meta: metaObject, source: source->d->root))
584 continue;
585 // qtEnums are enumerations already known by Qt, so we only need register them.
586 // We don't need to send all of the key/value data.
587 if (metaObject == &Qt::staticMetaObject) // Are the other Qt metaclasses for enums?
588 qtEnums.insert(value: metaEnum);
589 else
590 dynamicEnumMetaObjects.insert(value: metaEnum.enclosingMetaObject());
591 }
592 ds << quint32(qtEnums.size());
593 for (const auto &metaEnum : qtEnums) {
594 QByteArray enumName(metaEnum.scope());
595 enumName.append(s: "::", len: 2).append(s: metaEnum.name());
596 ds << enumName;
597 }
598 const auto allMetaObjects = gadgets + dynamicEnumMetaObjects;
599 ds << quint32(allMetaObjects.size());
600#ifdef QTRO_VERBOSE_PROTOCOL
601 qDebug() << " Found" << gadgets.size() << "gadget/pod and" << (allMetaObjects.size() - gadgets.size()) << "enum types";
602 int i = 0;
603#endif
604 // There isn't an easy way to update a metaobject incrementally, so we
605 // send all of the metaobject's enums, but no properties, when an external
606 // enum is requested.
607 for (auto const meta : allMetaObjects) {
608 ds << QByteArray::fromRawData(data: meta->className(), size: qsizetype(qstrlen(str: meta->className())));
609 int propertyCount = gadgets.contains(value: meta) ? meta->propertyCount() : 0;
610 ds << quint32(propertyCount);
611#ifdef QTRO_VERBOSE_PROTOCOL
612 qDebug(" Gadget %d (name = %s, # properties = %d, # enums = %d):", i++, meta->className(), propertyCount, meta->enumeratorCount() - meta->enumeratorOffset());
613#endif
614 for (int j = 0; j < propertyCount; j++) {
615 auto prop = meta->property(index: j);
616#ifdef QTRO_VERBOSE_PROTOCOL
617 qDebug(" Data member %d (name = %s, type = %s):", j, prop.name(), prop.typeName());
618#endif
619 ds << QByteArray::fromRawData(data: prop.name(), size: qsizetype(qstrlen(str: prop.name())));
620 ds << QByteArray::fromRawData(data: prop.typeName(), size: qsizetype(qstrlen(str: prop.typeName())));
621 }
622 int enumCount = meta->enumeratorCount() - meta->enumeratorOffset();
623 ds << quint32(enumCount);
624 for (int j = meta->enumeratorOffset(); j < meta->enumeratorCount(); j++) {
625 auto const enumMeta = meta->enumerator(index: j);
626 serializeEnum(ds, enumerator: enumMeta);
627 }
628 }
629}
630
631void QDataStreamCodec::serializeDefinition(QDataStream &ds, const QRemoteObjectSourceBase *source)
632{
633 const SourceApiMap *api = source->m_api;
634 const QByteArray desiredClassName(api->typeName().toLatin1());
635 const QByteArray originalClassName = api->className();
636 // The dynamic class will be called typeName on the receiving side of this definition
637 // However, there are types like enums that have the QObject's class name. Replace()
638 // will convert a parameter such as "ParentClassSource::MyEnum" to "ParentClass::MyEnum"
639 // so the type can be properly resolved and registered.
640 auto replace = [&originalClassName, &desiredClassName](QByteArray &name) {
641 name.replace(before: originalClassName, after: desiredClassName);
642 };
643
644 ds << source->m_api->typeName();
645#ifdef QTRO_VERBOSE_PROTOCOL
646 qDebug() << "Serializing definition for" << source->m_api->typeName();
647#endif
648
649 //Now copy the property data
650 const int numEnums = api->enumCount();
651 const auto metaObject = source->m_object->metaObject();
652 ds << quint32(numEnums); //Number of Enums
653#ifdef QTRO_VERBOSE_PROTOCOL
654 qDebug() << " Found" << numEnums << "enumeration types";
655#endif
656 for (int i = 0; i < numEnums; ++i) {
657 auto enumerator = metaObject->enumerator(index: api->sourceEnumIndex(index: i));
658 Q_ASSERT(enumerator.isValid());
659 serializeEnum(ds, enumerator);
660 }
661
662 QSet<const QMetaObject *> gadgets;
663 QSet<QMetaEnum> enums;
664 recurseForGadgets(gadgets, enums, source);
665 serializeGadgets(ds, gadgets, enums, source);
666
667 const int numSignals = api->signalCount();
668 ds << quint32(numSignals); //Number of signals
669 for (int i = 0; i < numSignals; ++i) {
670 const int index = api->sourceSignalIndex(index: i);
671 Q_ASSERT(index >= 0);
672 auto signature = api->signalSignature(index: i);
673 replace(signature);
674 const int count = api->signalParameterCount(index: i);
675 for (int pi = 0; pi < count; ++pi) {
676 const auto metaType = QMetaType(api->signalParameterType(sigIndex: i, paramIndex: pi));
677 if (isSequentialGadgetType(metaType))
678 signature.replace(before: metaType.name(), after: "QtROSequentialContainer");
679 else if (isAssociativeGadgetType(metaType))
680 signature.replace(before: metaType.name(), after: "QtROAssociativeContainer");
681 }
682#ifdef QTRO_VERBOSE_PROTOCOL
683 qDebug() << " Signal" << i << "(signature =" << signature << "parameter names =" << api->signalParameterNames(i) << ")";
684#endif
685 ds << signature;
686 ds << api->signalParameterNames(index: i);
687 }
688
689 const int numMethods = api->methodCount();
690 ds << quint32(numMethods); //Number of methods
691 for (int i = 0; i < numMethods; ++i) {
692 const int index = api->sourceMethodIndex(index: i);
693 Q_ASSERT(index >= 0);
694 auto signature = api->methodSignature(index: i);
695 replace(signature);
696 const int count = api->methodParameterCount(index: i);
697 for (int pi = 0; pi < count; ++pi) {
698 const auto metaType = QMetaType(api->methodParameterType(methodIndex: i, paramIndex: pi));
699 if (isSequentialGadgetType(metaType))
700 signature.replace(before: metaType.name(), after: "QtROSequentialContainer");
701 else if (isAssociativeGadgetType(metaType))
702 signature.replace(before: metaType.name(), after: "QtROAssociativeContainer");
703 }
704 auto typeName = api->typeName(index: i);
705 replace(typeName);
706#ifdef QTRO_VERBOSE_PROTOCOL
707 qDebug() << " Slot" << i << "(signature =" << signature << "parameter names =" << api->methodParameterNames(i) << "return type =" << typeName << ")";
708#endif
709 ds << signature;
710 ds << typeName;
711 ds << api->methodParameterNames(index: i);
712 }
713
714 const int numProperties = api->propertyCount();
715 ds << quint32(numProperties); //Number of properties
716 for (int i = 0; i < numProperties; ++i) {
717 const int index = api->sourcePropertyIndex(index: i);
718 Q_ASSERT(index >= 0);
719
720 const auto target = api->isAdapterProperty(i) ? source->m_adapter : source->m_object;
721 const auto metaProperty = target->metaObject()->property(index);
722 ds << metaProperty.name();
723#ifdef QTRO_VERBOSE_PROTOCOL
724 qDebug() << " Property" << i << "name =" << metaProperty.name();
725#endif
726 if (metaProperty.metaType().flags().testFlag(flag: QMetaType::PointerToQObject)) {
727 auto objectType = getObjectType(typeName: QLatin1String(metaProperty.typeName()));
728 ds << (objectType == ObjectType::CLASS ? "QObject*" : "QAbstractItemModel*");
729#ifdef QTRO_VERBOSE_PROTOCOL
730 qDebug() << " Type:" << (objectType == ObjectType::CLASS ? "QObject*" : "QAbstractItemModel*");
731#endif
732 } else {
733 if (isSequentialGadgetType(metaType: metaProperty.metaType())) {
734 ds << "QtROSequentialContainer";
735#ifdef QTRO_VERBOSE_PROTOCOL
736 qDebug() << " Type:" << "QtROSequentialContainer";
737#endif
738 } else if (isAssociativeGadgetType(metaType: metaProperty.metaType())) {
739 ds << "QtROAssociativeContainer";
740#ifdef QTRO_VERBOSE_PROTOCOL
741 qDebug() << " Type:" << "QtROAssociativeContainer";
742#endif
743 } else {
744 ds << metaProperty.typeName();
745#ifdef QTRO_VERBOSE_PROTOCOL
746 qDebug() << " Type:" << metaProperty.typeName();
747#endif
748 }
749 }
750 if (metaProperty.notifySignalIndex() == -1) {
751 ds << QByteArray();
752#ifdef QTRO_VERBOSE_PROTOCOL
753 qDebug() << " Notification signal: None";
754#endif
755 } else {
756 auto signature = metaProperty.notifySignal().methodSignature();
757 replace(signature);
758 ds << signature;
759#ifdef QTRO_VERBOSE_PROTOCOL
760 qDebug() << " Notification signal:" << signature;
761#endif
762 }
763 }
764}
765
766void QDataStreamCodec::serializeAddObjectPacket(const QString &name, bool isDynamic)
767{
768 m_packet.setId(AddObject);
769 m_packet << name;
770 m_packet << isDynamic;
771 m_packet.finishPacket();
772}
773
774void QDataStreamCodec::deserializeAddObjectPacket(QDataStream &ds, bool &isDynamic)
775{
776 ds >> isDynamic;
777}
778
779void QDataStreamCodec::serializeRemoveObjectPacket(const QString &name)
780{
781 m_packet.setId(RemoveObject);
782 m_packet << name;
783 m_packet.finishPacket();
784}
785//There is no deserializeRemoveObjectPacket - no parameters other than id and name
786
787void QDataStreamCodec::serializeInvokePacket(const QString &name, int call, int index, const QVariantList &args, int serialId, int propertyIndex)
788{
789 m_packet.setId(InvokePacket);
790 m_packet << name;
791 m_packet << call;
792 m_packet << index;
793
794 m_packet << quint32(args.size());
795 for (const auto &arg : args)
796 m_packet << encodeVariant(value: arg);
797
798 m_packet << serialId;
799 m_packet << propertyIndex;
800 m_packet.finishPacket();
801}
802
803void QDataStreamCodec::deserializeInvokePacket(QDataStream& in, int &call, int &index, QVariantList &args, int &serialId, int &propertyIndex)
804{
805 in >> call;
806 in >> index;
807 const bool success = deserializeQVariantList(s&: in, l&: args);
808 Q_ASSERT(success);
809 Q_UNUSED(success)
810 in >> serialId;
811 in >> propertyIndex;
812}
813
814void QDataStreamCodec::serializeInvokeReplyPacket(const QString &name, int ackedSerialId, const QVariant &value)
815{
816 m_packet.setId(InvokeReplyPacket);
817 m_packet << name;
818 m_packet << ackedSerialId;
819 m_packet << value;
820 m_packet.finishPacket();
821}
822
823void QDataStreamCodec::deserializeInvokeReplyPacket(QDataStream& in, int &ackedSerialId, QVariant &value){
824 in >> ackedSerialId;
825 in >> value;
826}
827
828void QDataStreamCodec::serializePropertyChangePacket(QRemoteObjectSourceBase *source, int signalIndex)
829{
830 int internalIndex = source->m_api->propertyRawIndexFromSignal(index: signalIndex);
831 m_packet.setId(PropertyChangePacket);
832 m_packet << source->name();
833 m_packet << internalIndex;
834 serializeProperty(source, internalIndex);
835 m_packet.finishPacket();
836}
837
838void QDataStreamCodec::deserializePropertyChangePacket(QDataStream& in, int &index, QVariant &value)
839{
840 in >> index;
841 in >> value;
842}
843
844void QDataStreamCodec::serializeObjectListPacket(const ObjectInfoList &objects)
845{
846 m_packet.setId(ObjectList);
847 m_packet << objects;
848 m_packet.finishPacket();
849}
850
851void QDataStreamCodec::deserializeObjectListPacket(QDataStream &in, ObjectInfoList &objects)
852{
853 in >> objects;
854}
855
856void QDataStreamCodec::serializePingPacket(const QString &name)
857{
858 m_packet.setId(Ping);
859 m_packet << name;
860 m_packet.finishPacket();
861}
862
863void QDataStreamCodec::serializePongPacket(const QString &name)
864{
865 m_packet.setId(Pong);
866 m_packet << name;
867 m_packet.finishPacket();
868}
869
870QRO_::QRO_(QRemoteObjectSourceBase *source)
871 : name(source->name())
872 , typeName(source->m_api->typeName())
873 , type(source->m_adapter ? ObjectType::MODEL : getObjectType(typeName))
874 , isNull(source->m_object == nullptr)
875 , classDefinition()
876 , parameters()
877{}
878
879QRO_::QRO_(const QVariant &value)
880 : type(ObjectType::GADGET)
881 , isNull(false)
882{
883 const auto metaType = value.metaType();
884 auto meta = metaType.metaObject();
885 QDataStream out(&classDefinition, QIODevice::WriteOnly);
886 const int numProperties = meta->propertyCount();
887 const auto name = metaType.name();
888 const auto typeName = QByteArray::fromRawData(data: name, size: qsizetype(qstrlen(str: name)));
889 out << quint32(0) << quint32(1);
890 out << typeName;
891 out << numProperties;
892#ifdef QTRO_VERBOSE_PROTOCOL
893 qDebug("Serializing POD definition to QRO_ (name = %s)", typeName.constData());
894#endif
895 for (int i = 0; i < numProperties; ++i) {
896 const auto property = meta->property(index: i);
897#ifdef QTRO_VERBOSE_PROTOCOL
898 qDebug(" Data member %d (name = %s, type = %s):", i, property.name(), property.typeName());
899#endif
900 out << QByteArray::fromRawData(data: property.name(), size: qsizetype(qstrlen(str: property.name())));
901 out << QByteArray::fromRawData(data: property.typeName(), size: qsizetype(qstrlen(str: property.typeName())));
902 }
903 int enumCount = meta->enumeratorCount() - meta->enumeratorOffset();
904 out << quint32(enumCount);
905 for (int j = meta->enumeratorOffset(); j < meta->enumeratorCount(); j++) {
906 auto const enumMeta = meta->enumerator(index: j);
907 serializeEnum(ds&: out, enumerator: enumMeta);
908 }
909 QDataStream ds(&parameters, QIODevice::WriteOnly);
910 ds << value;
911#ifdef QTRO_VERBOSE_PROTOCOL
912 qDebug() << " Value:" << value;
913#endif
914}
915
916QDataStream &operator<<(QDataStream &stream, const QRO_ &info)
917{
918 stream << info.name << info.typeName << quint8(info.type) << info.classDefinition << info.isNull;
919 qCDebug(QT_REMOTEOBJECT) << "Serializing " << info;
920 // info.parameters will be filled in by serializeProperty
921 return stream;
922}
923
924QDataStream &operator>>(QDataStream &stream, QRO_ &info)
925{
926 quint8 tmpType;
927 stream >> info.name >> info.typeName >> tmpType >> info.classDefinition >> info.isNull;
928 info.type = static_cast<ObjectType>(tmpType);
929 qCDebug(QT_REMOTEOBJECT) << "Deserializing " << info;
930 if (!info.isNull)
931 stream >> info.parameters;
932 return stream;
933}
934
935QSQ_::QSQ_(const QVariant &variant)
936{
937 QSequentialIterable sequence;
938 QMetaType valueType;
939 if (variant.metaType() == QMetaType::fromType<QtROSequentialContainer>()) {
940 auto container = static_cast<const QtROSequentialContainer *>(variant.constData());
941 typeName = container->m_typeName;
942 valueType = container->m_valueType;
943 valueTypeName = container->m_valueTypeName;
944 sequence = QSequentialIterable(reinterpret_cast<const QVariantList *>(variant.constData()));
945 } else {
946 sequence = variant.value<QSequentialIterable>();
947 typeName = QByteArray(variant.metaType().name());
948 valueType = sequence.metaContainer().valueMetaType();
949 valueTypeName = QByteArray(valueType.name());
950 }
951#ifdef QTRO_VERBOSE_PROTOCOL
952 qDebug("Serializing POD sequence to QSQ_ (type = %s, valueType = %s) with size = %lld",
953 typeName.constData(), valueTypeName.constData(), sequence.size());
954#endif
955 QDataStream ds(&values, QIODevice::WriteOnly);
956 ds << valueTypeName;
957 auto pos = ds.device()->pos();
958 ds << quint32(sequence.size());
959 for (const auto &v : sequence) {
960 if (!valueType.save(stream&: ds, data: v.data())) {
961 ds.device()->seek(pos);
962 ds.resetStatus();
963 ds << quint32(0);
964 values.resize(size: ds.device()->pos());
965 qWarning(msg: "QSQ_: unable to save type '%s', sending empty list.", valueType.name());
966 break;
967 }
968 }
969}
970
971QDataStream &operator<<(QDataStream &stream, const QSQ_ &sequence)
972{
973 stream << sequence.typeName << sequence.valueTypeName << sequence.values;
974 qCDebug(QT_REMOTEOBJECT) << "Serializing " << sequence;
975 return stream;
976}
977
978QDataStream &operator>>(QDataStream &stream, QSQ_ &sequence)
979{
980 stream >> sequence.typeName >> sequence.valueTypeName >> sequence.values;
981 qCDebug(QT_REMOTEOBJECT) << "Deserializing " << sequence;
982 return stream;
983}
984
985QAS_::QAS_(const QVariant &variant)
986{
987 QAssociativeIterable map;
988 QMetaType keyType, transferType, valueType;
989 const QtROAssociativeContainer *container = nullptr;
990 if (variant.metaType() == QMetaType::fromType<QtROAssociativeContainer>()) {
991 container = static_cast<const QtROAssociativeContainer *>(variant.constData());
992 typeName = container->m_typeName;
993 keyType = container->m_keyType;
994 keyTypeName = container->m_keyTypeName;
995 valueType = container->m_valueType;
996 valueTypeName = container->m_valueTypeName;
997 map = QAssociativeIterable(reinterpret_cast<const QVariantMap *>(variant.constData()));
998 } else {
999 map = variant.value<QAssociativeIterable>();
1000 typeName = QByteArray(variant.metaType().name());
1001 keyType = map.metaContainer().keyMetaType();
1002 keyTypeName = QByteArray(keyType.name());
1003 valueType = map.metaContainer().mappedMetaType();
1004 valueTypeName = QByteArray(valueType.name());
1005 }
1006 // Special handling for enums...
1007 transferType = keyType;
1008 if (keyType.flags().testFlag(flag: QMetaType::IsEnumeration)) {
1009 transferType = transferTypeForEnum(enumType: keyType);
1010 auto meta = keyType.metaObject();
1011 // If we are the source, make sure the typeName is converted for any downstream replicas
1012 // the `meta` variable will be non-null when the enum is a class enum
1013 if (meta && !container) {
1014 const int ind = meta->indexOfClassInfo(QCLASSINFO_REMOTEOBJECT_TYPE);
1015 if (ind >= 0) {
1016 bool isFlag = keyTypeName.startsWith(bv: "QFlags<");
1017 if (isFlag || keyTypeName.startsWith(bv: meta->className())) {
1018#ifdef QTRO_VERBOSE_PROTOCOL
1019 QByteArray orig(keyTypeName);
1020#endif
1021 if (isFlag) {
1022 // Q_DECLARE_FLAGS(Flags, Enum) -> `typedef QFlags<Enum> Flags;`
1023 // We know we have an enum for `Flags` because we sent the enums
1024 // from the source, so we just need to look up the alias.
1025 keyTypeName = keyTypeName.mid(index: 7);
1026 keyTypeName.chop(n: 1); // Remove trailing '>'
1027 int index = keyTypeName.lastIndexOf(ch: ':');
1028 for (int i = meta->enumeratorOffset(); i < meta->enumeratorCount(); i++) {
1029 auto en = meta->enumerator(index: i);
1030 auto name = keyTypeName.data() + index + 1;
1031 if (en.isFlag() && qstrcmp(str1: en.enumName(), str2: name) == 0)
1032 keyTypeName.replace(index: index + 1, len: qstrlen(str: en.enumName()), s: en.name());
1033 }
1034 }
1035 keyTypeName.replace(before: meta->className(), after: meta->classInfo(index: ind).value());
1036 QByteArray repName(meta->classInfo(index: ind).value());
1037 repName.append(s: "Replica");
1038 typeName.replace(before: meta->className(), after: repName);
1039#ifdef QTRO_VERBOSE_PROTOCOL
1040 qDebug() << "Converted map key typename from" << orig << "to" << keyTypeName;
1041#endif
1042 }
1043 }
1044 }
1045 }
1046
1047#ifdef QTRO_VERBOSE_PROTOCOL
1048 qDebug("Serializing POD map to QAS_ (type = %s, keyType = %s, valueType = %s), size = %lld",
1049 typeName.constData(), keyTypeName.constData(), valueTypeName.constData(),
1050 map.size());
1051#endif
1052 QDataStream ds(&values, QIODevice::WriteOnly);
1053 ds << keyTypeName;
1054 ds << valueTypeName;
1055 auto pos = ds.device()->pos();
1056 ds << quint32(map.size());
1057 QAssociativeIterable::const_iterator iter = map.begin();
1058 for (int i = 0; i < map.size(); i++) {
1059 QVariant key(container ? container->m_keys.at(i) : iter.key());
1060 if (transferType != keyType)
1061 key.convert(type: transferType);
1062 if (!transferType.save(stream&: ds, data: key.data())) {
1063 ds.device()->seek(pos);
1064 ds.resetStatus();
1065 ds << quint32(0);
1066 values.resize(size: ds.device()->pos());
1067 qWarning(msg: "QAS_: unable to save key '%s', sending empty map.", keyType.name());
1068 break;
1069 }
1070 if (!valueType.save(stream&: ds, data: iter.value().data())) {
1071 ds.device()->seek(pos);
1072 ds.resetStatus();
1073 ds << quint32(0);
1074 values.resize(size: ds.device()->pos());
1075 qWarning(msg: "QAS_: unable to save value '%s', sending empty map.", valueType.name());
1076 break;
1077 }
1078 iter++;
1079 }
1080}
1081
1082QDataStream &operator<<(QDataStream &stream, const QAS_ &map)
1083{
1084 stream << map.typeName << map.keyTypeName << map.valueTypeName << map.values;
1085 qCDebug(QT_REMOTEOBJECT) << "Serializing " << map;
1086 return stream;
1087}
1088
1089QDataStream &operator>>(QDataStream &stream, QAS_ &map)
1090{
1091 stream >> map.typeName >> map.keyTypeName >> map.valueTypeName >> map.values;
1092 qCDebug(QT_REMOTEOBJECT) << "Deserializing " << map;
1093 return stream;
1094}
1095
1096DataStreamPacket::DataStreamPacket(quint16 id)
1097 : QDataStream(&array, QIODevice::WriteOnly), baseAddress(0), size(0)
1098{
1099 this->setVersion(QtRemoteObjects::dataStreamVersion);
1100 this->setByteOrder(QDataStream::LittleEndian);
1101 *this << quint32(0);
1102 *this << id;
1103}
1104
1105void CodecBase::send(const QSet<QtROIoDeviceBase *> &connections)
1106{
1107 const auto bytearray = getPayload();
1108 for (auto conn : connections)
1109 conn->write(data: bytearray);
1110 reset();
1111}
1112
1113void CodecBase::send(const QList<QtROIoDeviceBase *> &connections)
1114{
1115 const auto bytearray = getPayload();
1116 for (auto conn : connections)
1117 conn->write(data: bytearray);
1118 reset();
1119}
1120
1121void CodecBase::send(QtROIoDeviceBase *connection)
1122{
1123 const auto bytearray = getPayload();
1124 connection->write(data: bytearray);
1125 reset();
1126}
1127
1128} // namespace QRemoteObjectPackets
1129
1130QT_IMPL_METATYPE_EXTERN_TAGGED(QRemoteObjectPackets::QRO_, QRemoteObjectPackets__QRO_)
1131
1132QT_END_NAMESPACE
1133

source code of qtremoteobjects/src/remoteobjects/qremoteobjectpacket.cpp