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:network-protocol
4
5#include "qremoteobjectreplica.h"
6#include "qremoteobjectreplica_p.h"
7
8#include "qremoteobjectnode.h"
9#include "qremoteobjectnode_p.h"
10#include "qremoteobjectdynamicreplica.h"
11#include "qremoteobjectpacket_p.h"
12#include "qremoteobjectpendingcall_p.h"
13#include "qconnectionfactories_p.h"
14#include "qremoteobjectsource_p.h"
15
16#include <QtCore/qcoreapplication.h>
17#include <QtCore/qdatastream.h>
18#include <QtCore/qelapsedtimer.h>
19#include <QtCore/qvariant.h>
20#include <QtCore/qthread.h>
21
22#include <limits>
23
24QT_BEGIN_NAMESPACE
25
26using namespace QRemoteObjectPackets;
27
28QT_WARNING_PUSH
29QT_WARNING_DISABLE_GCC("-Wtautological-compare")
30
31#if !defined(Q_OS_WIN) && !defined(Q_OS_INTEGRITY)
32Q_STATIC_ASSERT_X(&QRemoteObjectReplica::staticMetaObject == &QRemoteObjectDynamicReplica::staticMetaObject,
33 "m_signalOffset initializer expects QRemoteObjectDynamicReplica to not have a unique staticMetaObject");
34#endif
35
36QT_WARNING_POP
37
38// If QRemoteObjectDynamicReplica ever gets its own staticMetaObject, some commented out code will need to be
39// used. It was changed to avoid a Coverity complaint. We use the above static assert to detect if this changes
40// in the future. See FIX #1, #2, #3 in this file.
41
42QRemoteObjectReplicaImplementation::QRemoteObjectReplicaImplementation(const QString &name, const QMetaObject *meta, QRemoteObjectNode *_node)
43 : QObject(nullptr), m_objectName(name), m_metaObject(meta), m_numSignals(0), m_methodOffset(0)
44 // Uncomment the following two lines if QRemoteObjectDynamicReplica gets a unique staticMetaObject (FIX #1, #2)
45 //, m_signalOffset(meta ? QRemoteObjectReplica::staticMetaObject.methodCount() : QRemoteObjectDynamicReplica::staticMetaObject.methodCount())
46 //, m_propertyOffset(meta ? QRemoteObjectReplica::staticMetaObject.propertyCount() : QRemoteObjectDynamicReplica::staticMetaObject.propertyCount())
47 , m_signalOffset(QRemoteObjectReplica::staticMetaObject.methodCount())
48 , m_propertyOffset(QRemoteObjectReplica::staticMetaObject.propertyCount())
49 , m_node(_node)
50 , m_objectSignature(QtPrivate::qtro_classinfo_signature(metaObject: m_metaObject))
51 , m_state(meta ? QRemoteObjectReplica::Default : QRemoteObjectReplica::Uninitialized)
52{
53}
54
55QRemoteObjectReplicaImplementation::~QRemoteObjectReplicaImplementation()
56{
57}
58
59QConnectedReplicaImplementation::QConnectedReplicaImplementation(const QString &name, const QMetaObject *meta, QRemoteObjectNode *node)
60 : QRemoteObjectReplicaImplementation(name, meta, node), connectionToSource(nullptr)
61{
62 m_heartbeatTimer.setTimerType(Qt::CoarseTimer);
63 m_heartbeatTimer.setSingleShot(true);
64 m_heartbeatTimer.setInterval(node->heartbeatInterval());
65
66 connect(sender: node, signal: &QRemoteObjectNode::heartbeatIntervalChanged, context: this, slot: [this](int interval) {
67 m_heartbeatTimer.stop();
68 m_heartbeatTimer.setInterval(interval);
69 if (interval)
70 m_heartbeatTimer.start();
71 });
72 connect(sender: &m_heartbeatTimer, signal: &QTimer::timeout, context: this, slot: [this] {
73 // TODO: Revisit if a baseclass method can be used to avoid specialized cast
74 // conditional logic.
75
76 if (m_pendingCalls.contains(key: 0)) {
77 m_pendingCalls.take(key: 0);
78 // The source didn't respond in time, disconnect the connection
79 if (connectionToSource) {
80 auto clientIo = qobject_cast<QtROClientIoDevice *>(object: connectionToSource);
81 if (clientIo)
82 clientIo->disconnectFromServer();
83 else
84 connectionToSource->close();
85 }
86 } else {
87 if (connectionToSource.isNull()) {
88 qCDebug(QT_REMOTEOBJECT) << "Ignoring heartbeat as there is no source connected.";
89 return;
90 }
91 connectionToSource->d_func()->m_codec->serializePingPacket(name: m_objectName);
92 if (sendCommandWithReply(serialId: 0).d->serialId == -1) {
93 m_heartbeatTimer.stop();
94 auto clientIo = qobject_cast<QtROClientIoDevice *>(object: connectionToSource);
95 if (clientIo)
96 clientIo->disconnectFromServer();
97 else
98 connectionToSource->close();
99 }
100 }
101 });
102
103 if (!meta)
104 return;
105
106 auto offsetMeta = m_metaObject;
107 QtRemoteObjects::getTypeNameAndMetaobjectFromClassInfo(meta&: offsetMeta);
108 for (int index = offsetMeta->propertyOffset(); index < offsetMeta->propertyCount(); ++index) {
109 const QMetaProperty property = offsetMeta->property(index);
110 if (property.metaType().flags().testFlag(flag: QMetaType::PointerToQObject))
111 m_childIndices << index - offsetMeta->propertyOffset();
112 }
113}
114
115QConnectedReplicaImplementation::~QConnectedReplicaImplementation()
116{
117 if (!connectionToSource.isNull()) {
118 qCDebug(QT_REMOTEOBJECT) << "Replica deleted: sending RemoveObject to RemoteObjectSource" << m_objectName;
119 connectionToSource->d_func()->m_codec->serializeRemoveObjectPacket(name: m_objectName);
120 sendCommand();
121 }
122 for (auto prop : m_propertyStorage) {
123 if (prop.canConvert<QObject*>()) {
124 if (auto o = prop.value<QObject*>())
125 o->deleteLater();
126 }
127 }
128}
129
130bool QRemoteObjectReplicaImplementation::needsDynamicInitialization() const
131{
132 return m_metaObject == nullptr;
133}
134
135void QRemoteObjectReplicaImplementation::setState(QRemoteObjectReplica::State state)
136{
137 if (m_state.loadAcquire() != QRemoteObjectReplica::Suspect && m_state.loadAcquire() >= state)
138 return;
139
140 int oldState = m_state.loadAcquire();
141 m_state.storeRelease(newValue: state);
142
143 // We should emit initialized before emitting any changed signals in case connections are made in a
144 // Slot responding to initialized/validChanged.
145 if (m_state.loadAcquire() == QRemoteObjectReplica::Valid) {
146 // we're initialized now, emit signal
147 emitInitialized();
148 }
149
150 const static int stateChangedIndex = QRemoteObjectReplica::staticMetaObject.indexOfMethod(method: "stateChanged(State,State)");
151 Q_ASSERT(stateChangedIndex != -1);
152 void *args[] = {nullptr, &state, &oldState};
153 QMetaObject::activate(sender: this, metaObject(), local_signal_index: stateChangedIndex, argv: args);
154}
155
156void QRemoteObjectReplicaImplementation::emitNotified()
157{
158 const static int notifiedIndex = QRemoteObjectReplica::staticMetaObject.indexOfMethod(method: "notified()");
159 Q_ASSERT(notifiedIndex != -1);
160 void *args[] = {nullptr};
161 QMetaObject::activate(sender: this, metaObject(), local_signal_index: notifiedIndex, argv: args);
162}
163
164bool QConnectedReplicaImplementation::sendCommand()
165{
166 Q_ASSERT(connectionToSource);
167 if (!connectionToSource->isOpen())
168 return false;
169
170 connectionToSource->d_func()->m_codec->send(connection: connectionToSource);
171 if (m_heartbeatTimer.interval())
172 m_heartbeatTimer.start();
173 return true;
174}
175
176QList<int> QConnectedReplicaImplementation::childIndices() const
177{
178 return m_childIndices;
179}
180
181void QConnectedReplicaImplementation::initialize(QVariantList &&values)
182{
183 qCDebug(QT_REMOTEOBJECT) << "initialize()" << m_propertyStorage.size();
184 const int nParam = int(values.size());
185 QVarLengthArray<int> changedProperties(nParam);
186 const int offset = m_propertyOffset;
187 for (int i = 0; i < nParam; ++i) {
188 qCDebug(QT_REMOTEOBJECT) << " in loop" << i << m_propertyStorage.size();
189 changedProperties[i] = -1;
190 if (m_propertyStorage[i] != values.at(i)) {
191 const QMetaProperty property = m_metaObject->property(index: i+offset);
192 m_propertyStorage[i] = QRemoteObjectPackets::decodeVariant(value: std::move(values[i]), metaType: property.metaType());
193 changedProperties[i] = i;
194 }
195 qCDebug(QT_REMOTEOBJECT) << "SETPROPERTY" << i << m_metaObject->property(index: i+offset).name()
196 << m_propertyStorage[i].typeName()
197 << m_propertyStorage[i].toString();
198 }
199
200 Q_ASSERT(m_state.loadAcquire() < QRemoteObjectReplica::Valid || m_state.loadAcquire() == QRemoteObjectReplica::Suspect);
201 setState(QRemoteObjectReplica::Valid);
202
203 void *args[] = {nullptr, nullptr};
204 for (int i = 0; i < nParam; ++i) {
205 if (changedProperties[i] < 0)
206 continue;
207 const int notifyIndex = m_metaObject->property(index: changedProperties[i]+offset).notifySignalIndex();
208 if (notifyIndex < 0)
209 continue;
210 qCDebug(QT_REMOTEOBJECT) << " Before activate" << notifyIndex << m_metaObject->property(index: notifyIndex).name();
211 args[1] = m_propertyStorage[i].data();
212 QMetaObject::activate(sender: this, metaObject(), local_signal_index: notifyIndex, argv: args);
213 }
214 emitNotified();
215
216 qCDebug(QT_REMOTEOBJECT) << "isSet = true for" << m_objectName;
217 if (node()->heartbeatInterval())
218 m_heartbeatTimer.start();
219}
220
221void QRemoteObjectReplicaImplementation::emitInitialized()
222{
223 const static int initializedIndex = QRemoteObjectReplica::staticMetaObject.indexOfMethod(method: "initialized()");
224 Q_ASSERT(initializedIndex != -1);
225 void *noArgs[] = {nullptr};
226 QMetaObject::activate(sender: this, metaObject(), local_signal_index: initializedIndex, argv: noArgs);
227}
228
229/*!
230 \internal
231*/
232void QRemoteObjectReplica::persistProperties(const QString &repName, const QByteArray &repSig, const QVariantList &props) const
233{
234 if (!node()) {
235 qWarning(msg: "Tried calling persistProperties on a replica (%s) that hasn't been initialized with a node", qPrintable(repName));
236 return;
237 }
238 node()->persistProperties(repName, repSig, props);
239}
240
241/*!
242 \internal
243*/
244QVariantList QRemoteObjectReplica::retrieveProperties(const QString &repName, const QByteArray &repSig) const
245{
246 if (!node()) {
247 qWarning(msg: "Tried calling retrieveProperties on a replica (%s) that hasn't been initialized with a node", qPrintable(repName));
248 return QVariantList();
249 }
250 return node()->retrieveProperties(repName, repSig);
251}
252
253void QRemoteObjectReplicaImplementation::setDynamicMetaObject(const QMetaObject *meta)
254{
255 Q_ASSERT(!m_metaObject);
256
257 m_metaObject = meta;
258}
259
260void QConnectedReplicaImplementation::setDynamicMetaObject(const QMetaObject *meta)
261{
262 QRemoteObjectReplicaImplementation::setDynamicMetaObject(meta);
263
264 for (int index = m_metaObject->propertyOffset(); index < m_metaObject->propertyCount(); ++index) {
265 const QMetaProperty property = m_metaObject->property(index);
266 if (property.metaType().flags().testFlag(flag: QMetaType::PointerToQObject))
267 m_childIndices << index - m_metaObject->propertyOffset();
268 }
269}
270
271void QRemoteObjectReplicaImplementation::setDynamicProperties(QVariantList &&values)
272{
273 const int offset = m_propertyOffset;
274 int propertyIndex = -1;
275 for (auto &prop : values) {
276 propertyIndex++;
277 const QMetaProperty property = m_metaObject->property(index: propertyIndex+offset);
278 prop = QRemoteObjectPackets::decodeVariant(value: std::move(prop), metaType: property.metaType());
279 }
280 //rely on order of properties;
281 setProperties(std::move(values));
282}
283
284void QConnectedReplicaImplementation::setDynamicProperties(QVariantList &&values)
285{
286 QRemoteObjectReplicaImplementation::setDynamicProperties(std::move(values));
287 for (QRemoteObjectReplica *obj : std::exchange(obj&: m_parentsNeedingConnect, new_val: {}))
288 configurePrivate(obj);
289
290 Q_ASSERT(m_state.loadAcquire() < QRemoteObjectReplica::Valid);
291 setState(QRemoteObjectReplica::Valid);
292
293 void *args[] = {nullptr, nullptr};
294 for (int index = m_metaObject->propertyOffset(); index < m_metaObject->propertyCount(); ++index) {
295 const QMetaProperty mp = m_metaObject->property(index);
296 if (mp.hasNotifySignal()) {
297 qCDebug(QT_REMOTEOBJECT) << " Before activate" << index << m_metaObject->property(index).name();
298 args[1] = this->m_propertyStorage[index-m_propertyOffset].data();
299 QMetaObject::activate(sender: this, metaObject(), local_signal_index: mp.notifySignalIndex(), argv: args);
300 }
301 }
302 emitNotified();
303
304 qCDebug(QT_REMOTEOBJECT) << "isSet = true for" << m_objectName;
305}
306
307bool QConnectedReplicaImplementation::isInitialized() const
308{
309 return m_state.loadAcquire() > QRemoteObjectReplica::Default && m_state.loadAcquire() != QRemoteObjectReplica::SignatureMismatch;
310}
311
312bool QConnectedReplicaImplementation::waitForSource(int timeout)
313{
314 switch (state()) {
315 case QRemoteObjectReplica::State::Valid:
316 return true;
317 case QRemoteObjectReplica::State::SignatureMismatch:
318 return false;
319 default:
320 break;
321 }
322
323 const static int stateChangedIndex = QRemoteObjectReplica::staticMetaObject.indexOfMethod(method: "stateChanged(State,State)");
324 Q_ASSERT(stateChangedIndex != -1);
325
326 QEventLoop loop;
327 QMetaObject::connect(sender: this, signal_index: stateChangedIndex,
328 receiver: &loop, method_index: QEventLoop::staticMetaObject.indexOfMethod(method: "quit()"),
329 type: Qt::DirectConnection, types: nullptr);
330
331 QTimer t; // NB: Related to QTBUG-94570 - don't use QTimer::singleShot here.
332 if (timeout >= 0) {
333 t.setSingleShot(true);
334 connect(sender: &t, signal: &QTimer::timeout, context: &loop, slot: &QEventLoop::quit);
335 t.start(msec: timeout);
336 }
337
338 // enter the event loop and wait for a reply
339 loop.exec(flags: QEventLoop::ExcludeUserInputEvents | QEventLoop::WaitForMoreEvents);
340
341 return state() == QRemoteObjectReplica::State::Valid;
342}
343
344void QConnectedReplicaImplementation::_q_send(QMetaObject::Call call, int index, const QVariantList &args)
345{
346 static const bool debugArgs = qEnvironmentVariableIsSet(varName: "QT_REMOTEOBJECT_DEBUG_ARGUMENTS");
347
348 Q_ASSERT(call == QMetaObject::InvokeMetaMethod || call == QMetaObject::WriteProperty);
349 if (connectionToSource.isNull()) {
350 qCWarning(QT_REMOTEOBJECT) << "connectionToSource is null";
351 return;
352 }
353
354 if (call == QMetaObject::InvokeMetaMethod) {
355 if (debugArgs) {
356 qCDebug(QT_REMOTEOBJECT) << "Send" << call << this->m_metaObject->method(index).name() << index << args << connectionToSource;
357 } else {
358 qCDebug(QT_REMOTEOBJECT) << "Send" << call << this->m_metaObject->method(index).name() << index << connectionToSource;
359 }
360 if (index < m_methodOffset) //index - m_methodOffset < 0 is invalid, and can't be resolved on the Source side
361 qCWarning(QT_REMOTEOBJECT) << "Skipping invalid method invocation. Index not found:" << index << "( offset =" << m_methodOffset << ") object:" << m_objectName << this->m_metaObject->method(index).name();
362 else {
363 connectionToSource->d_func()->m_codec->serializeInvokePacket(name: m_objectName, call, index: index - m_methodOffset, args);
364 sendCommand();
365 }
366 } else {
367 qCDebug(QT_REMOTEOBJECT) << "Send" << call << this->m_metaObject->property(index).name() << index << args << connectionToSource;
368 if (index < m_propertyOffset) //index - m_propertyOffset < 0 is invalid, and can't be resolved on the Source side
369 qCWarning(QT_REMOTEOBJECT) << "Skipping invalid property invocation. Index not found:" << index << "( offset =" << m_propertyOffset << ") object:" << m_objectName << this->m_metaObject->property(index).name();
370 else {
371 connectionToSource->d_func()->m_codec->serializeInvokePacket(name: m_objectName, call, index: index - m_propertyOffset, args);
372 sendCommand();
373 }
374 }
375}
376
377QRemoteObjectPendingCall QConnectedReplicaImplementation::_q_sendWithReply(QMetaObject::Call call, int index, const QVariantList &args)
378{
379 Q_ASSERT(call == QMetaObject::InvokeMetaMethod);
380 if (connectionToSource.isNull()) {
381 qCWarning(QT_REMOTEOBJECT) << "connectionToSource is null";
382 return QRemoteObjectPendingCall();
383 }
384
385 qCDebug(QT_REMOTEOBJECT) << "Send" << call << this->m_metaObject->method(index).name() << index << args << connectionToSource;
386 int serialId = (m_curSerialId == std::numeric_limits<int>::max() ? 1 : m_curSerialId++);
387 connectionToSource->d_func()->m_codec->serializeInvokePacket(name: m_objectName, call, index: index - m_methodOffset, args, serialId);
388 return sendCommandWithReply(serialId);
389}
390
391QRemoteObjectPendingCall QConnectedReplicaImplementation::sendCommandWithReply(int serialId)
392{
393 bool success = sendCommand();
394 if (!success) {
395 return QRemoteObjectPendingCall(); // invalid
396 }
397
398 qCDebug(QT_REMOTEOBJECT) << "Sent InvokePacket with serial id:" << serialId;
399 QRemoteObjectPendingCall pendingCall(new QRemoteObjectPendingCallData(serialId, this));
400 Q_ASSERT(!m_pendingCalls.contains(serialId));
401 m_pendingCalls[serialId] = pendingCall;
402 return pendingCall;
403}
404
405void QConnectedReplicaImplementation::notifyAboutReply(int ackedSerialId, const QVariant &value)
406{
407 QRemoteObjectPendingCall call = m_pendingCalls.take(key: ackedSerialId);
408 if (ackedSerialId == 0) {
409 m_heartbeatTimer.stop();
410 if (m_heartbeatTimer.interval())
411 m_heartbeatTimer.start();
412 return;
413 }
414
415 QMutexLocker mutex(&call.d->mutex);
416
417 // clear error flag
418 call.d->error = QRemoteObjectPendingCall::NoError;
419 call.d->returnValue = value;
420
421 // notify watchers if needed
422 if (call.d->watcherHelper)
423 call.d->watcherHelper->emitSignals();
424}
425
426bool QConnectedReplicaImplementation::waitForFinished(const QRemoteObjectPendingCall& call, int timeout)
427{
428 if (!call.d->watcherHelper)
429 call.d->watcherHelper.reset(other: new QRemoteObjectPendingCallWatcherHelper);
430
431 call.d->mutex.unlock();
432
433 QEventLoop loop;
434 loop.connect(sender: call.d->watcherHelper.data(), signal: &QRemoteObjectPendingCallWatcherHelper::finished,
435 context: &loop, slot: &QEventLoop::quit);
436
437 QTimer t; // NB: Related to QTBUG-94570 - don't use QTimer::singleShot here.
438 if (timeout >= 0) {
439 t.setSingleShot(true);
440 connect(sender: &t, signal: &QTimer::timeout, context: &loop, slot: &QEventLoop::quit);
441 t.start(msec: timeout);
442 }
443
444 // enter the event loop and wait for a reply
445 loop.exec(flags: QEventLoop::ExcludeUserInputEvents | QEventLoop::WaitForMoreEvents);
446
447 call.d->mutex.lock();
448
449 return call.d->error != QRemoteObjectPendingCall::InvalidMessage;
450}
451
452const QVariant QConnectedReplicaImplementation::getProperty(int i) const
453{
454 Q_ASSERT_X(i >= 0 && i < m_propertyStorage.size(), __FUNCTION__, qPrintable(QString(QLatin1String("0 <= %1 < %2")).arg(i).arg(m_propertyStorage.size())));
455 return m_propertyStorage[i];
456}
457
458void QConnectedReplicaImplementation::setProperties(QVariantList &&properties)
459{
460 Q_ASSERT(m_propertyStorage.isEmpty());
461 m_propertyStorage.reserve(asize: properties.size());
462 m_propertyStorage = std::move(properties);
463}
464
465void QConnectedReplicaImplementation::setProperty(int i, const QVariant &prop)
466{
467 m_propertyStorage[i] = prop;
468}
469
470void QConnectedReplicaImplementation::setConnection(QtROIoDeviceBase *conn)
471{
472 if (connectionToSource.isNull()) {
473 connectionToSource = conn;
474 qCDebug(QT_REMOTEOBJECT) << "setConnection started" << conn << m_objectName;
475 }
476 requestRemoteObjectSource();
477}
478
479void QConnectedReplicaImplementation::setDisconnected()
480{
481 Q_ASSERT(connectionToSource);
482 connectionToSource.clear();
483 setState(QRemoteObjectReplica::State::Suspect);
484 for (const int index : childIndices()) {
485 auto pointerToQObject = qvariant_cast<QObject *>(v: getProperty(i: index));
486 auto child = qobject_cast<QRemoteObjectReplica *>(object: pointerToQObject);
487 if (child) {
488 QConnectedReplicaImplementation *childReplicaImplData
489 = static_cast<QConnectedReplicaImplementation *>(child->d_impl.data());
490 if (childReplicaImplData && !childReplicaImplData->connectionToSource.isNull())
491 childReplicaImplData->setDisconnected();
492 }
493 }
494}
495
496void QConnectedReplicaImplementation::requestRemoteObjectSource()
497{
498 Q_ASSERT(connectionToSource);
499 connectionToSource->d_func()->m_codec->serializeAddObjectPacket(name: m_objectName, isDynamic: needsDynamicInitialization());
500 sendCommand();
501}
502
503void QRemoteObjectReplicaImplementation::configurePrivate(QRemoteObjectReplica *rep)
504{
505 qCDebug(QT_REMOTEOBJECT) << "configurePrivate starting for" << this->m_objectName;
506 //We need to connect the Replicant only signals too
507 // Uncomment the following two lines if QRemoteObjectDynamicReplica gets a unique staticMetaObject (FIX #3)
508 //const QMetaObject *m = rep->inherits("QRemoteObjectDynamicReplica") ?
509 // &QRemoteObjectDynamicReplica::staticMetaObject : &QRemoteObjectReplica::staticMetaObject;
510 const QMetaObject *m = &QRemoteObjectReplica::staticMetaObject;
511 for (int i = m->methodOffset(); i < m->methodCount(); ++i)
512 {
513 const QMetaMethod mm = m->method(index: i);
514 if (mm.methodType() == QMetaMethod::Signal) {
515 const bool res = QMetaObject::connect(sender: this, signal_index: i, receiver: rep, method_index: i, type: Qt::DirectConnection, types: nullptr);
516 qCDebug(QT_REMOTEOBJECT) << " Rep connect"<<i<<res<<mm.name();
517 Q_UNUSED(res)
518 }
519 }
520 if (m_methodOffset == 0) //We haven't initialized the offsets yet
521 {
522 const int index = m_metaObject->indexOfClassInfo(QCLASSINFO_REMOTEOBJECT_TYPE);
523 const QMetaObject *metaObject = m_metaObject;
524 if (index != -1) { //We have an object created from repc or at least with QCLASSINFO defined
525 while (true) {
526 Q_ASSERT(metaObject->superClass()); //This recurses to QObject, which doesn't have QCLASSINFO_REMOTEOBJECT_TYPE
527 if (index != metaObject->superClass()->indexOfClassInfo(QCLASSINFO_REMOTEOBJECT_TYPE)) //At the point we don't find the same QCLASSINFO_REMOTEOBJECT_TYPE,
528 //we have the metaobject we should work from
529 break;
530 metaObject = metaObject->superClass();
531 }
532 }
533
534 for (int i = m_signalOffset; i < metaObject->methodCount(); ++i) {
535 const QMetaMethod mm = metaObject->method(index: i);
536 if (mm.methodType() == QMetaMethod::Signal) {
537 ++m_numSignals;
538 const bool res = QMetaObject::connect(sender: this, signal_index: i, receiver: rep, method_index: i, type: Qt::DirectConnection, types: nullptr);
539 qCDebug(QT_REMOTEOBJECT) << " Connect"<<i<<res<<mm.name();
540 Q_UNUSED(res)
541 }
542 }
543 m_methodOffset = m_signalOffset + m_numSignals;
544 qCDebug(QT_REMOTEOBJECT) << QStringLiteral("configurePrivate finished, signalOffset = %1, methodOffset = %2, #Signals = %3").arg(a: m_signalOffset).arg(a: m_methodOffset).arg(a: m_numSignals);
545 } else { //We have initialized offsets, this is an additional Replica attaching
546 for (int i = m_signalOffset; i < m_methodOffset; ++i) {
547 const bool res = QMetaObject::connect(sender: this, signal_index: i, receiver: rep, method_index: i, type: Qt::DirectConnection, types: nullptr);
548 qCDebug(QT_REMOTEOBJECT) << " Connect"<<i<<res<<m_metaObject->method(index: i).name();
549 Q_UNUSED(res)
550 }
551 if (isInitialized()) {
552 qCDebug(QT_REMOTEOBJECT) << QStringLiteral("ReplicaImplementation initialized, emitting signal on replica");
553 emit rep->initialized(); //Emit from new replica only
554 }
555 if (state() != QRemoteObjectReplica::Valid) {
556 qCDebug(QT_REMOTEOBJECT) << QStringLiteral("ReplicaImplementation not currently valid, emitting signal on replica");
557 emit rep->stateChanged(state: state(), oldState: m_metaObject ? QRemoteObjectReplica::Default : QRemoteObjectReplica::Uninitialized);
558 }
559
560 qCDebug(QT_REMOTEOBJECT) << QStringLiteral("configurePrivate finished, added replica to existing ReplicaImplementation");
561 }
562}
563
564void QConnectedReplicaImplementation::configurePrivate(QRemoteObjectReplica *rep)
565{
566 if (m_metaObject) {
567 // see QRemoteObjectReplicaImplementation::configurePrivate
568 const bool firstReplicaInstance = (m_methodOffset == 0);
569
570 QRemoteObjectReplicaImplementation::configurePrivate(rep);
571
572 // ensure that notify signals are emitted for the new replica, when
573 // we are initializing an nth replica of the same type
574 if (!firstReplicaInstance) {
575 const int offset = m_propertyOffset;
576 const int nParam = int(m_propertyStorage.size());
577 void *args[] = {nullptr, nullptr};
578 for (int i = 0; i < nParam; ++i) {
579 const int notifyIndex = m_metaObject->property(index: i+offset).notifySignalIndex();
580 if (notifyIndex < 0)
581 continue;
582 qCDebug(QT_REMOTEOBJECT) << " Before activate" << notifyIndex << m_metaObject->property(index: i+offset).name();
583 args[1] = m_propertyStorage[i].data();
584 // NOTE: this over-emits (assumes all values have changed)
585 QMetaObject::activate(sender: rep, rep->metaObject(), local_signal_index: notifyIndex - m_signalOffset, argv: args);
586 }
587 }
588 } else
589 m_parentsNeedingConnect.append(t: rep);
590}
591
592/*!
593 \class QRemoteObjectReplica
594 \inmodule QtRemoteObjects
595 \brief A class interacting with (but not implementing) a Qt API on the Remote Object network.
596
597 A Remote Object Replica is a QObject proxy for another QObject (called the
598 \l {Source} object). Once initialized, a replica can be considered a
599 "latent copy" of the \l {Source} object. That is, every change to a
600 Q_PROPERTY on the \l {Source}, or signal emitted by the \l {Source} will be
601 updated/emitted by all \l {Replica} objects. Latency
602 is introduced by process scheduling by any OSes involved and network
603 communication latency. As long as the replica has been initialized and the
604 communication is not disrupted, receipt and order of changes is guaranteed.
605
606 The \l {isInitialized} and \l {state} properties (and corresponding
607 \l {initialized()}/\l {stateChanged()} signals) allow the state of a
608 \l {Replica} to be determined.
609
610 While Qt Remote Objects (QtRO) handles the initialization and
611 synchronization of \l {Replica} objects, there are numerous steps happening
612 behind the scenes which can fail and that aren't encountered in single
613 process Qt applications. See \l {Troubleshooting} for advice on how to
614 handle such issues when using a remote objects network.
615*/
616
617/*!
618 \enum QRemoteObjectReplica::State
619
620 This enum type specifies the various state codes associated with QRemoteObjectReplica states:
621
622 \value Uninitialized Initial value of DynamicReplica, where nothing is
623 known about the replica before connection to source.
624
625 \value Default Initial value of static replica, where any defaults set in
626 the .rep file are available so it can be used if necessary.
627
628 \value Valid Indicates the replica is connected, has good property values
629 and can be interacted with.
630
631 \value Suspect Error state that occurs if the connection to the source is
632 lost after it is initialized.
633
634 \value SignatureMismatch Error state that occurs if a connection to the
635 source is made, but the source and replica are not derived from the same
636 .rep (only possible for static Replicas).
637*/
638
639/*!
640 \fn void QRemoteObjectReplica::stateChanged(State state, State oldState)
641
642 This signal is emitted whenever a replica's state toggles between
643 \l QRemoteObjectReplica::State.
644
645 The change in state is represented with \a state and \a oldState.
646
647 \sa state(), initialized()
648*/
649
650/*!
651 \fn void QRemoteObjectReplica::initialized()
652
653 This signal is emitted once the replica is initialized. An intialized replica
654 has all property values set, but has not yet emitted any property change
655 notifications.
656
657 \sa isInitialized(), stateChanged()
658*/
659
660/*!
661 \fn void QRemoteObjectReplica::notified()
662
663 This signal is emitted once the replica is initialized and all property change
664 notifications have been emitted.
665
666 It is sometimes useful to respond to property changes as events.
667 For example, you might want to display a user notification when a certain
668 property change occurs. However, this user notification would then also be
669 triggered when a replica first became \c QRemoteObjectReplica::Valid, as
670 all property change signals are emitted at that time. This isn't always desirable,
671 and \c notified allows the developer to distinguish between these two cases.
672*/
673
674/*!
675 \internal
676 \enum QRemoteObjectReplica::ConstructorType
677*/
678
679/*!
680 \property QRemoteObjectReplica::state
681 \brief Returns the replica state.
682
683 This property holds the replica \l QRemoteObjectReplica::State.
684*/
685
686/*!
687 \property QRemoteObjectReplica::node
688 \brief A pointer to the node this object was acquired from.
689*/
690
691/*!
692 \internal This (protected) constructor for QRemoteObjectReplica can be used to create
693 replica objects from QML.
694*/
695QRemoteObjectReplica::QRemoteObjectReplica(ConstructorType t)
696 : QObject(nullptr)
697 , d_impl(t == DefaultConstructor ? new QStubReplicaImplementation : nullptr)
698{
699 qRegisterMetaType<State>(typeName: "State");
700}
701
702QRemoteObjectReplica::QRemoteObjectReplica(QObjectPrivate &dptr, QObject *parent)
703 : QObject(dptr, parent)
704 , d_impl(new QStubReplicaImplementation)
705{
706}
707
708/*!
709 \internal
710*/
711QRemoteObjectReplica::~QRemoteObjectReplica()
712{
713}
714
715/*!
716 \internal
717*/
718void QRemoteObjectReplica::send(QMetaObject::Call call, int index, const QVariantList &args)
719{
720 Q_ASSERT(index != -1);
721
722 d_impl->_q_send(call, index, args);
723}
724
725/*!
726 \internal
727*/
728QRemoteObjectPendingCall QRemoteObjectReplica::sendWithReply(QMetaObject::Call call, int index, const QVariantList &args)
729{
730 return d_impl->_q_sendWithReply(call, index, args);
731}
732
733/*!
734 \internal
735*/
736const QVariant QRemoteObjectReplica::propAsVariant(int i) const
737{
738 return d_impl->getProperty(i);
739}
740
741/*!
742 \internal
743*/
744void QRemoteObjectReplica::initializeNode(QRemoteObjectNode *node, const QString &name)
745{
746 node->initializeReplica(instance: this, name);
747}
748
749/*!
750 \internal
751*/
752void QRemoteObjectReplica::setProperties(QVariantList &&properties)
753{
754 d_impl->setProperties(std::move(properties));
755}
756
757/*!
758 \internal
759*/
760void QRemoteObjectReplica::setChild(int i, const QVariant &value)
761{
762 d_impl->setProperty(i, value);
763}
764
765/*!
766 Returns \c true if this replica has been initialized with data from the
767 \l {Source} object. Returns \c false otherwise.
768
769 \sa state()
770*/
771bool QRemoteObjectReplica::isInitialized() const
772{
773 return d_impl->isInitialized();
774}
775
776/*!
777 Returns the current \l {QRemoteObjectReplica::State}{state} of the replica.
778
779 \sa isInitialized
780*/
781QRemoteObjectReplica::State QRemoteObjectReplica::state() const
782{
783 return d_impl->state();
784}
785
786QRemoteObjectNode *QRemoteObjectReplica::node() const
787{
788 return d_impl->node();
789}
790
791void QRemoteObjectReplica::setNode(QRemoteObjectNode *_node)
792{
793 const QRemoteObjectNode *curNode = node();
794 if (curNode) {
795 qCWarning(QT_REMOTEOBJECT) << "Ignoring call to setNode as the node has already been set";
796 return;
797 }
798 d_impl.clear();
799 _node->initializeReplica(instance: this);
800}
801
802/*!
803 \internal
804*/
805void QRemoteObjectReplica::initialize()
806{
807}
808
809/*!
810 Returns \c true if this replica has been initialized and has a valid
811 connection with the \l {QRemoteObjectNode} {node} hosting the \l {Source}.
812 Returns \c false otherwise.
813
814 \sa isInitialized()
815*/
816bool QRemoteObjectReplica::isReplicaValid() const
817{
818 return state() == Valid;
819}
820
821/*!
822 Blocking call that waits for the replica to become initialized or until the
823 \a timeout (in ms) expires. Returns \c true if the replica is initialized
824 when the call completes, \c false otherwise.
825
826 If \a timeout is -1, this function will not time out.
827
828 \sa isInitialized(), initialized()
829*/
830bool QRemoteObjectReplica::waitForSource(int timeout)
831{
832 return d_impl->waitForSource(timeout);
833}
834
835QInProcessReplicaImplementation::QInProcessReplicaImplementation(const QString &name, const QMetaObject *meta, QRemoteObjectNode * node)
836 : QRemoteObjectReplicaImplementation(name, meta, node)
837{
838}
839
840QInProcessReplicaImplementation::~QInProcessReplicaImplementation()
841{
842}
843
844const QVariant QInProcessReplicaImplementation::getProperty(int i) const
845{
846 Q_ASSERT(connectionToSource);
847 Q_ASSERT(connectionToSource->m_object);
848 const int index = i + QRemoteObjectSource::qobjectPropertyOffset;
849 Q_ASSERT(index >= 0 && index < connectionToSource->m_object->metaObject()->propertyCount());
850 return connectionToSource->m_object->metaObject()->property(index).read(obj: connectionToSource->m_object);
851}
852
853void QInProcessReplicaImplementation::setProperties(QVariantList &&)
854{
855 //TODO some verification here maybe?
856}
857
858void QInProcessReplicaImplementation::setProperty(int i, const QVariant &property)
859{
860 Q_ASSERT(connectionToSource);
861 Q_ASSERT(connectionToSource->m_object);
862 const int index = i + QRemoteObjectSource::qobjectPropertyOffset;
863 Q_ASSERT(index >= 0 && index < connectionToSource->m_object->metaObject()->propertyCount());
864 connectionToSource->m_object->metaObject()->property(index).write(obj: connectionToSource->m_object, value: property);
865}
866
867void QInProcessReplicaImplementation::_q_send(QMetaObject::Call call, int index, const QVariantList &args)
868{
869 Q_ASSERT(call == QMetaObject::InvokeMetaMethod || call == QMetaObject::WriteProperty);
870
871 const SourceApiMap *api = connectionToSource->m_api;
872 if (call == QMetaObject::InvokeMetaMethod) {
873 const int resolvedIndex = api->sourceMethodIndex(index: index - m_methodOffset);
874 if (resolvedIndex < 0)
875 qCWarning(QT_REMOTEOBJECT) << "Skipping invalid invocation. Index not found:" << index - m_methodOffset;
876 else
877 connectionToSource->invoke(c: call, index: index - m_methodOffset, args);
878 } else {
879 const int resolvedIndex = connectionToSource->m_api->sourcePropertyIndex(index: index - m_propertyOffset);
880 if (resolvedIndex < 0)
881 qCWarning(QT_REMOTEOBJECT) << "Skipping invalid property setter. Index not found:" << index - m_propertyOffset;
882 else
883 connectionToSource->invoke(c: call, index: index - m_propertyOffset, args);
884 }
885}
886
887QRemoteObjectPendingCall QInProcessReplicaImplementation::_q_sendWithReply(QMetaObject::Call call, int index, const QVariantList &args)
888{
889 Q_ASSERT(call == QMetaObject::InvokeMetaMethod);
890
891 const int ReplicaIndex = index - m_methodOffset;
892 auto metaType = QMetaType::fromName(name: connectionToSource->m_api->typeName(index: ReplicaIndex).constData());
893 if (!metaType.sizeOf())
894 metaType = QMetaType(QMetaType::UnknownType);
895 QVariant returnValue(metaType, nullptr);
896
897 const int resolvedIndex = connectionToSource->m_api->sourceMethodIndex(index: ReplicaIndex);
898 if (resolvedIndex < 0) {
899 qCWarning(QT_REMOTEOBJECT) << "Skipping invalid invocation. Index not found:" << ReplicaIndex;
900 return QRemoteObjectPendingCall();
901 }
902
903 connectionToSource->invoke(c: call, index: ReplicaIndex, args, returnValue: &returnValue);
904 return QRemoteObjectPendingCall::fromCompletedCall(returnValue);
905}
906
907QStubReplicaImplementation::QStubReplicaImplementation() {}
908
909QStubReplicaImplementation::~QStubReplicaImplementation() {}
910
911const QVariant QStubReplicaImplementation::getProperty(int i) const
912{
913 Q_ASSERT_X(i >= 0 && i < m_propertyStorage.size(), __FUNCTION__, qPrintable(QString(QLatin1String("0 <= %1 < %2")).arg(i).arg(m_propertyStorage.size())));
914 return m_propertyStorage[i];
915}
916
917void QStubReplicaImplementation::setProperties(QVariantList &&properties)
918{
919 Q_ASSERT(m_propertyStorage.isEmpty());
920 m_propertyStorage.reserve(asize: properties.size());
921 m_propertyStorage = std::move(properties);
922}
923
924void QStubReplicaImplementation::setProperty(int i, const QVariant &prop)
925{
926 m_propertyStorage[i] = prop;
927}
928
929void QStubReplicaImplementation::_q_send(QMetaObject::Call call, int index, const QVariantList &args)
930{
931 Q_UNUSED(call)
932 Q_UNUSED(index)
933 Q_UNUSED(args)
934 qWarning(msg: "Tried calling a slot or setting a property on a replica that hasn't been initialized with a node");
935}
936
937QRemoteObjectPendingCall QStubReplicaImplementation::_q_sendWithReply(QMetaObject::Call call, int index, const QVariantList &args)
938{
939 Q_UNUSED(call)
940 Q_UNUSED(index)
941 Q_UNUSED(args)
942 qWarning(msg: "Tried calling a slot or setting a property on a replica that hasn't been initialized with a node");
943 return QRemoteObjectPendingCall(); //Invalid
944}
945
946QT_END_NAMESPACE
947

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