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

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