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
4#include "qremoteobjectsourceio_p.h"
5
6#include "qremoteobjectpacket_p.h"
7#include "qremoteobjectsource_p.h"
8#include "qremoteobjectnode_p.h"
9#include "qremoteobjectpendingcall.h"
10#include "qtremoteobjectglobal.h"
11
12#include <QtCore/qstringlist.h>
13
14QT_BEGIN_NAMESPACE
15
16using namespace QtRemoteObjects;
17
18QRemoteObjectSourceIo::QRemoteObjectSourceIo(const QUrl &address, QObject *parent)
19 : QObject(parent)
20 , m_server(QtROServerFactory::instance()->isValid(url: address) ?
21 QtROServerFactory::instance()->create(url: address, parent: this) : nullptr)
22 , m_address(address)
23{
24 if (m_server == nullptr)
25 qRODebug(this) << "Using" << m_address << "as external url.";
26}
27
28QRemoteObjectSourceIo::QRemoteObjectSourceIo(QObject *parent)
29 : QObject(parent)
30 , m_server(nullptr)
31{
32}
33
34QRemoteObjectSourceIo::~QRemoteObjectSourceIo()
35{
36 qDeleteAll(c: m_sourceRoots.values());
37}
38
39bool QRemoteObjectSourceIo::startListening()
40{
41 if (!m_server->listen(address: m_address)) {
42 qROCritical(this) << "Listen failed for URL:" << m_address;
43 qROCritical(this) << m_server->serverError();
44 return false;
45 }
46
47 qRODebug(this) << "QRemoteObjectSourceIo is Listening" << m_address;
48 connect(sender: m_server.data(), signal: &QConnectionAbstractServer::newConnection, context: this,
49 slot: &QRemoteObjectSourceIo::handleConnection);
50 return true;
51}
52
53bool QRemoteObjectSourceIo::enableRemoting(QObject *object, const QMetaObject *meta, const QString &name, const QString &typeName)
54{
55 if (m_sourceRoots.contains(key: name)) {
56 qROWarning(this) << "Tried to register QRemoteObjectRootSource twice" << name;
57 return false;
58 }
59
60 return enableRemoting(object, api: new DynamicApiMap(object, meta, name, typeName));
61}
62
63bool QRemoteObjectSourceIo::enableRemoting(QObject *object, const SourceApiMap *api, QObject *adapter)
64{
65 const QString name = api->name();
66 if (!api->isDynamic() && m_sourceRoots.contains(key: name)) {
67 qROWarning(this) << "Tried to register QRemoteObjectRootSource twice" << name;
68 return false;
69 }
70
71 new QRemoteObjectRootSource(object, api, adapter, this);
72 m_codec->serializeObjectListPacket({QRemoteObjectPackets::ObjectInfo{.name: api->name(), .typeName: api->typeName(), .signature: api->objectSignature()}});
73 m_codec->send(connections: m_connections);
74 if (const int count = m_connections.size())
75 qRODebug(this) << "Wrote new QObjectListPacket for" << api->name() << "to" << count << "connections";
76 return true;
77}
78
79bool QRemoteObjectSourceIo::disableRemoting(QObject *object)
80{
81 QRemoteObjectRootSource *source = m_objectToSourceMap.take(key: object);
82 if (!source)
83 return false;
84
85 delete source;
86 return true;
87}
88
89void QRemoteObjectSourceIo::registerSource(QRemoteObjectSourceBase *source)
90{
91 Q_ASSERT(source);
92 const QString &name = source->name();
93 m_sourceObjects[name] = source;
94 if (source->isRoot()) {
95 QRemoteObjectRootSource *root = static_cast<QRemoteObjectRootSource *>(source);
96 qRODebug(this) << "Registering" << name;
97 m_sourceRoots[name] = root;
98 m_objectToSourceMap[source->m_object] = root;
99 if (serverAddress().isValid()) {
100 const auto &type = source->m_api->typeName();
101 emit remoteObjectAdded(qMakePair(value1: name, value2: QRemoteObjectSourceLocationInfo(type, serverAddress())));
102 }
103 }
104}
105
106void QRemoteObjectSourceIo::unregisterSource(QRemoteObjectSourceBase *source)
107{
108 Q_ASSERT(source);
109 const QString &name = source->name();
110 m_sourceObjects.remove(key: name);
111 if (source->isRoot()) {
112 const auto type = source->m_api->typeName();
113 m_objectToSourceMap.remove(key: source->m_object);
114 m_sourceRoots.remove(key: name);
115 if (serverAddress().isValid())
116 emit remoteObjectRemoved(qMakePair(value1: name, value2: QRemoteObjectSourceLocationInfo(type, serverAddress())));
117 }
118}
119
120void QRemoteObjectSourceIo::onServerDisconnect(QObject *conn)
121{
122 QtROIoDeviceBase *connection = qobject_cast<QtROIoDeviceBase*>(object: conn);
123 m_connections.remove(value: connection);
124
125 qRODebug(this) << "OnServerDisconnect";
126
127 for (QRemoteObjectRootSource *root : std::as_const(t&: m_sourceRoots))
128 root->removeListener(io: connection);
129
130 const QUrl location = m_registryMapping.value(key: connection);
131 emit serverRemoved(url: location);
132 m_registryMapping.remove(key: connection);
133 connection->close();
134 connection->deleteLater();
135}
136
137void QRemoteObjectSourceIo::onServerRead(QObject *conn)
138{
139 // Assert the invariant here conn is of type QIODevice
140 QtROIoDeviceBase *connection = qobject_cast<QtROIoDeviceBase*>(object: conn);
141 QRemoteObjectPacketTypeEnum packetType;
142
143 do {
144
145 if (!connection->read(packetType, m_rxName))
146 return;
147
148 using namespace QRemoteObjectPackets;
149
150 switch (packetType) {
151 case Ping:
152 m_codec->serializePongPacket(name: m_rxName);
153 m_codec->send(connection);
154 break;
155 case AddObject:
156 {
157 bool isDynamic;
158 m_codec->deserializeAddObjectPacket(connection->d_func()->stream(), isDynamic);
159 qRODebug(this) << "AddObject" << m_rxName << isDynamic;
160 if (m_sourceRoots.contains(key: m_rxName)) {
161 QRemoteObjectRootSource *root = m_sourceRoots[m_rxName];
162 root->addListener(io: connection, dynamic: isDynamic);
163 } else {
164 qROWarning(this) << "Request to attach to non-existent RemoteObjectSource:" << m_rxName;
165 }
166 break;
167 }
168 case RemoveObject:
169 {
170 qRODebug(this) << "RemoveObject" << m_rxName;
171 if (m_sourceRoots.contains(key: m_rxName)) {
172 QRemoteObjectRootSource *root = m_sourceRoots[m_rxName];
173 const int count = root->removeListener(io: connection);
174 Q_UNUSED(count);
175 //TODO - possible to have a timer that closes connections if not reopened within a timeout?
176 } else {
177 qROWarning(this) << "Request to detach from non-existent RemoteObjectSource:" << m_rxName;
178 }
179 qRODebug(this) << "RemoveObject finished" << m_rxName;
180 break;
181 }
182 case InvokePacket:
183 {
184 int call, index, serialId, propertyId;
185 m_codec->deserializeInvokePacket(in&: connection->d_func()->stream(), call, index, args&: m_rxArgs, serialId, propertyIndex&: propertyId);
186 if (m_rxName == QLatin1String("Registry") && !m_registryMapping.contains(key: connection)) {
187 const QRemoteObjectSourceLocation loc = m_rxArgs.first().value<QRemoteObjectSourceLocation>();
188 m_registryMapping[connection] = loc.second.hostUrl;
189 }
190 if (m_sourceObjects.contains(key: m_rxName)) {
191 QRemoteObjectSourceBase *source = m_sourceObjects[m_rxName];
192 if (call == QMetaObject::InvokeMetaMethod) {
193 const int resolvedIndex = source->m_api->sourceMethodIndex(index);
194 if (resolvedIndex < 0) { //Invalid index
195 qROWarning(this) << "Invalid method invoke packet received. Index =" << index <<"which is out of bounds for type"<<m_rxName;
196 //TODO - consider moving this to packet validation?
197 break;
198 }
199 if (source->m_api->isAdapterMethod(index))
200 qRODebug(this) << "Adapter (method) Invoke-->" << m_rxName << source->m_adapter->metaObject()->method(index: resolvedIndex).name();
201 else {
202 qRODebug(this) << "Source (method) Invoke-->" << m_rxName << source->m_object->metaObject()->method(index: resolvedIndex).methodSignature();
203 auto method = source->m_object->metaObject()->method(index: resolvedIndex);
204 const int parameterCount = method.parameterCount();
205 for (int i = 0; i < parameterCount; i++)
206 m_rxArgs[i] = decodeVariant(value: std::move(m_rxArgs[i]), metaType: method.parameterMetaType(index: i));
207 }
208 auto metaType = QMetaType::fromName(name: source->m_api->typeName(index).constData());
209 if (!metaType.sizeOf())
210 metaType = QMetaType(QMetaType::UnknownType);
211 QVariant returnValue(metaType, nullptr);
212 // If a Replica is used as a Source (which node->proxy() does) we can have a PendingCall return value.
213 // In this case, we need to wait for the pending call and send that.
214 if (source->m_api->typeName(index) == QByteArrayLiteral("QRemoteObjectPendingCall"))
215 returnValue = QVariant::fromValue<QRemoteObjectPendingCall>(value: QRemoteObjectPendingCall());
216 source->invoke(c: QMetaObject::InvokeMetaMethod, index, args: m_rxArgs, returnValue: &returnValue);
217 // send reply if wanted
218 if (serialId >= 0) {
219 if (returnValue.canConvert<QRemoteObjectPendingCall>()) {
220 QRemoteObjectPendingCall call = returnValue.value<QRemoteObjectPendingCall>();
221 // Watcher will be destroyed when connection is, or when the finished lambda is called
222 QRemoteObjectPendingCallWatcher *watcher = new QRemoteObjectPendingCallWatcher(call, connection);
223 QObject::connect(sender: watcher, signal: &QRemoteObjectPendingCallWatcher::finished, context: connection, slot: [this, serialId, connection, watcher]() {
224 if (watcher->error() == QRemoteObjectPendingCall::NoError) {
225 m_codec->serializeInvokeReplyPacket(name: this->m_rxName, ackedSerialId: serialId, value: encodeVariant(value: watcher->returnValue()));
226 m_codec->send(connection);
227 }
228 watcher->deleteLater();
229 });
230 } else {
231 m_codec->serializeInvokeReplyPacket(name: m_rxName, ackedSerialId: serialId, value: encodeVariant(value: returnValue));
232 m_codec->send(connection);
233 }
234 }
235 } else {
236 const int resolvedIndex = source->m_api->sourcePropertyIndex(index);
237 if (resolvedIndex < 0) {
238 qROWarning(this) << "Invalid property invoke packet received. Index =" << index <<"which is out of bounds for type"<<m_rxName;
239 //TODO - consider moving this to packet validation?
240 break;
241 }
242 if (source->m_api->isAdapterProperty(index))
243 qRODebug(this) << "Adapter (write property) Invoke-->" << m_rxName << source->m_adapter->metaObject()->property(index: resolvedIndex).name();
244 else
245 qRODebug(this) << "Source (write property) Invoke-->" << m_rxName << source->m_object->metaObject()->property(index: resolvedIndex).name();
246 source->invoke(c: QMetaObject::WriteProperty, index, args: m_rxArgs);
247 }
248 }
249 break;
250 }
251 default:
252 qRODebug(this) << "OnReadReady invalid type" << packetType;
253 }
254 } while (connection->bytesAvailable()); // have bytes left over, so do another iteration
255}
256
257void QRemoteObjectSourceIo::handleConnection()
258{
259 qRODebug(this) << "handleConnection" << m_connections;
260
261 QtROServerIoDevice *conn = m_server->nextPendingConnection();
262 newConnection(conn);
263}
264
265void QRemoteObjectSourceIo::newConnection(QtROIoDeviceBase *conn)
266{
267 m_connections.insert(value: conn);
268 connect(sender: conn, signal: &QtROIoDeviceBase::readyRead, context: this, slot: [this, conn]() {
269 onServerRead(conn);
270 });
271 connect(sender: conn, signal: &QtROIoDeviceBase::disconnected, context: this, slot: [this, conn]() {
272 onServerDisconnect(conn);
273 });
274
275 m_codec->serializeHandshakePacket();
276 m_codec->send(connection: conn);
277
278 QRemoteObjectPackets::ObjectInfoList infos;
279 infos.reserve(asize: m_sourceRoots.size());
280 for (auto remoteObject : std::as_const(t&: m_sourceRoots)) {
281 infos << QRemoteObjectPackets::ObjectInfo{.name: remoteObject->m_api->name(), .typeName: remoteObject->m_api->typeName(), .signature: remoteObject->m_api->objectSignature()};
282 }
283 m_codec->serializeObjectListPacket(infos);
284 m_codec->send(connection: conn);
285 qRODebug(this) << "Wrote ObjectList packet from Server" << QStringList(m_sourceRoots.keys());
286}
287
288QUrl QRemoteObjectSourceIo::serverAddress() const
289{
290 if (m_server)
291 return m_server->address();
292 return m_address;
293}
294
295QT_END_NAMESPACE
296

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