1/****************************************************************************
2**
3** Copyright (C) 2017 Ford Motor Company
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtRemoteObjects module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qremoteobjectsourceio_p.h"
41
42#include "qremoteobjectpacket_p.h"
43#include "qremoteobjectsource_p.h"
44#include "qremoteobjectnode_p.h"
45#include "qremoteobjectpendingcall.h"
46#include "qtremoteobjectglobal.h"
47
48#include <QtCore/qstringlist.h>
49
50QT_BEGIN_NAMESPACE
51
52using namespace QtRemoteObjects;
53
54QRemoteObjectSourceIo::QRemoteObjectSourceIo(const QUrl &address, QObject *parent)
55 : QObject(parent)
56 , m_server(QtROServerFactory::instance()->isValid(url: address) ?
57 QtROServerFactory::instance()->create(url: address, parent: this) : nullptr)
58 , m_address(address)
59{
60 if (m_server == nullptr)
61 qRODebug(this) << "Using" << m_address << "as external url.";
62}
63
64QRemoteObjectSourceIo::QRemoteObjectSourceIo(QObject *parent)
65 : QObject(parent)
66 , m_server(nullptr)
67{
68}
69
70QRemoteObjectSourceIo::~QRemoteObjectSourceIo()
71{
72 qDeleteAll(c: m_sourceRoots.values());
73}
74
75bool QRemoteObjectSourceIo::startListening()
76{
77 if (!m_server->listen(address: m_address)) {
78 qROCritical(this) << "Listen failed for URL:" << m_address;
79 qROCritical(this) << m_server->serverError();
80 return false;
81 }
82
83 qRODebug(this) << "QRemoteObjectSourceIo is Listening" << m_address;
84 connect(sender: m_server.data(), signal: &QConnectionAbstractServer::newConnection, receiver: this,
85 slot: &QRemoteObjectSourceIo::handleConnection);
86 return true;
87}
88
89bool QRemoteObjectSourceIo::enableRemoting(QObject *object, const QMetaObject *meta, const QString &name, const QString &typeName)
90{
91 if (m_sourceRoots.contains(akey: name)) {
92 qROWarning(this) << "Tried to register QRemoteObjectRootSource twice" << name;
93 return false;
94 }
95
96 return enableRemoting(object, api: new DynamicApiMap(object, meta, name, typeName));
97}
98
99bool QRemoteObjectSourceIo::enableRemoting(QObject *object, const SourceApiMap *api, QObject *adapter)
100{
101 const QString name = api->name();
102 if (!api->isDynamic() && m_sourceRoots.contains(akey: name)) {
103 qROWarning(this) << "Tried to register QRemoteObjectRootSource twice" << name;
104 return false;
105 }
106
107 new QRemoteObjectRootSource(object, api, adapter, this);
108 QRemoteObjectPackets::serializeObjectListPacket(m_packet, {QRemoteObjectPackets::ObjectInfo{.name: api->name(), .typeName: api->typeName(), .signature: api->objectSignature()}});
109 for (auto conn : m_connections)
110 conn->write(data: m_packet.array, m_packet.size);
111 if (const int count = m_connections.size())
112 qRODebug(this) << "Wrote new QObjectListPacket for" << api->name() << "to" << count << "connections";
113 return true;
114}
115
116bool QRemoteObjectSourceIo::disableRemoting(QObject *object)
117{
118 QRemoteObjectRootSource *source = m_objectToSourceMap.take(akey: object);
119 if (!source)
120 return false;
121
122 delete source;
123 return true;
124}
125
126void QRemoteObjectSourceIo::registerSource(QRemoteObjectSourceBase *source)
127{
128 Q_ASSERT(source);
129 const QString &name = source->name();
130 m_sourceObjects[name] = source;
131 if (source->isRoot()) {
132 QRemoteObjectRootSource *root = static_cast<QRemoteObjectRootSource *>(source);
133 qRODebug(this) << "Registering" << name;
134 m_sourceRoots[name] = root;
135 m_objectToSourceMap[source->m_object] = root;
136 if (serverAddress().isValid()) {
137 const auto &type = source->m_api->typeName();
138 emit remoteObjectAdded(qMakePair(x: name, y: QRemoteObjectSourceLocationInfo(type, serverAddress())));
139 }
140 }
141}
142
143void QRemoteObjectSourceIo::unregisterSource(QRemoteObjectSourceBase *source)
144{
145 Q_ASSERT(source);
146 const QString &name = source->name();
147 m_sourceObjects.remove(akey: name);
148 if (source->isRoot()) {
149 const auto type = source->m_api->typeName();
150 m_objectToSourceMap.remove(akey: source->m_object);
151 m_sourceRoots.remove(akey: name);
152 if (serverAddress().isValid())
153 emit remoteObjectRemoved(qMakePair(x: name, y: QRemoteObjectSourceLocationInfo(type, serverAddress())));
154 }
155}
156
157void QRemoteObjectSourceIo::onServerDisconnect(QObject *conn)
158{
159 IoDeviceBase *connection = qobject_cast<IoDeviceBase*>(object: conn);
160 m_connections.remove(value: connection);
161
162 qRODebug(this) << "OnServerDisconnect";
163
164 for (QRemoteObjectRootSource *root : qAsConst(t&: m_sourceRoots))
165 root->removeListener(io: connection);
166
167 const QUrl location = m_registryMapping.value(akey: connection);
168 emit serverRemoved(url: location);
169 m_registryMapping.remove(akey: connection);
170 connection->close();
171 connection->deleteLater();
172}
173
174void QRemoteObjectSourceIo::onServerRead(QObject *conn)
175{
176 // Assert the invariant here conn is of type QIODevice
177 IoDeviceBase *connection = qobject_cast<IoDeviceBase*>(object: conn);
178 QRemoteObjectPacketTypeEnum packetType;
179
180 do {
181
182 if (!connection->read(packetType, m_rxName))
183 return;
184
185 using namespace QRemoteObjectPackets;
186
187 switch (packetType) {
188 case Ping:
189 serializePongPacket(ds&: m_packet, name: m_rxName);
190 connection->write(data: m_packet.array, m_packet.size);
191 break;
192 case AddObject:
193 {
194 bool isDynamic;
195 deserializeAddObjectPacket(connection->stream(), isDynamic);
196 qRODebug(this) << "AddObject" << m_rxName << isDynamic;
197 if (m_sourceRoots.contains(akey: m_rxName)) {
198 QRemoteObjectRootSource *root = m_sourceRoots[m_rxName];
199 root->addListener(io: connection, dynamic: isDynamic);
200 } else {
201 qROWarning(this) << "Request to attach to non-existent RemoteObjectSource:" << m_rxName;
202 }
203 break;
204 }
205 case RemoveObject:
206 {
207 qRODebug(this) << "RemoveObject" << m_rxName;
208 if (m_sourceRoots.contains(akey: m_rxName)) {
209 QRemoteObjectRootSource *root = m_sourceRoots[m_rxName];
210 const int count = root->removeListener(io: connection);
211 Q_UNUSED(count);
212 //TODO - possible to have a timer that closes connections if not reopened within a timeout?
213 } else {
214 qROWarning(this) << "Request to detach from non-existent RemoteObjectSource:" << m_rxName;
215 }
216 qRODebug(this) << "RemoveObject finished" << m_rxName;
217 break;
218 }
219 case InvokePacket:
220 {
221 int call, index, serialId, propertyId;
222 deserializeInvokePacket(in&: connection->stream(), call, index, args&: m_rxArgs, serialId, propertyIndex&: propertyId);
223 if (m_rxName == QLatin1String("Registry") && !m_registryMapping.contains(akey: connection)) {
224 const QRemoteObjectSourceLocation loc = m_rxArgs.first().value<QRemoteObjectSourceLocation>();
225 m_registryMapping[connection] = loc.second.hostUrl;
226 }
227 if (m_sourceObjects.contains(akey: m_rxName)) {
228 QRemoteObjectSourceBase *source = m_sourceObjects[m_rxName];
229 if (call == QMetaObject::InvokeMetaMethod) {
230 const int resolvedIndex = source->m_api->sourceMethodIndex(index);
231 if (resolvedIndex < 0) { //Invalid index
232 qROWarning(this) << "Invalid method invoke packet received. Index =" << index <<"which is out of bounds for type"<<m_rxName;
233 //TODO - consider moving this to packet validation?
234 break;
235 }
236 if (source->m_api->isAdapterMethod(index))
237 qRODebug(this) << "Adapter (method) Invoke-->" << m_rxName << source->m_adapter->metaObject()->method(index: resolvedIndex).name();
238 else {
239 qRODebug(this) << "Source (method) Invoke-->" << m_rxName << source->m_object->metaObject()->method(index: resolvedIndex).methodSignature();
240 auto method = source->m_object->metaObject()->method(index: resolvedIndex);
241 const int parameterCount = method.parameterCount();
242 for (int i = 0; i < parameterCount; i++)
243 decodeVariant(value&: m_rxArgs[i], type: method.parameterType(index: i));
244 }
245 int typeId = QMetaType::type(typeName: source->m_api->typeName(index).constData());
246 if (!QMetaType(typeId).sizeOf())
247 typeId = QVariant::Invalid;
248 QVariant returnValue(typeId, nullptr);
249 // If a Replica is used as a Source (which node->proxy() does) we can have a PendingCall return value.
250 // In this case, we need to wait for the pending call and send that.
251 if (source->m_api->typeName(index) == QByteArrayLiteral("QRemoteObjectPendingCall"))
252 returnValue = QVariant::fromValue<QRemoteObjectPendingCall>(value: QRemoteObjectPendingCall());
253 source->invoke(c: QMetaObject::InvokeMetaMethod, index, args: m_rxArgs, returnValue: &returnValue);
254 // send reply if wanted
255 if (serialId >= 0) {
256 if (returnValue.canConvert<QRemoteObjectPendingCall>()) {
257 QRemoteObjectPendingCall call = returnValue.value<QRemoteObjectPendingCall>();
258 // Watcher will be destroyed when connection is, or when the finished lambda is called
259 QRemoteObjectPendingCallWatcher *watcher = new QRemoteObjectPendingCallWatcher(call, connection);
260 QObject::connect(sender: watcher, signal: &QRemoteObjectPendingCallWatcher::finished, context: connection, slot: [this, serialId, connection, watcher]() {
261 if (watcher->error() == QRemoteObjectPendingCall::NoError) {
262 serializeInvokeReplyPacket(this->m_packet, name: this->m_rxName, ackedSerialId: serialId, value: encodeVariant(value: watcher->returnValue()));
263 connection->write(data: m_packet.array, m_packet.size);
264 }
265 watcher->deleteLater();
266 });
267 } else {
268 serializeInvokeReplyPacket(m_packet, name: m_rxName, ackedSerialId: serialId, value: encodeVariant(value: returnValue));
269 connection->write(data: m_packet.array, m_packet.size);
270 }
271 }
272 } else {
273 const int resolvedIndex = source->m_api->sourcePropertyIndex(index);
274 if (resolvedIndex < 0) {
275 qROWarning(this) << "Invalid property invoke packet received. Index =" << index <<"which is out of bounds for type"<<m_rxName;
276 //TODO - consider moving this to packet validation?
277 break;
278 }
279 if (source->m_api->isAdapterProperty(index))
280 qRODebug(this) << "Adapter (write property) Invoke-->" << m_rxName << source->m_adapter->metaObject()->property(index: resolvedIndex).name();
281 else
282 qRODebug(this) << "Source (write property) Invoke-->" << m_rxName << source->m_object->metaObject()->property(index: resolvedIndex).name();
283 source->invoke(c: QMetaObject::WriteProperty, index, args: m_rxArgs);
284 }
285 }
286 break;
287 }
288 default:
289 qRODebug(this) << "OnReadReady invalid type" << packetType;
290 }
291 } while (connection->bytesAvailable()); // have bytes left over, so do another iteration
292}
293
294void QRemoteObjectSourceIo::handleConnection()
295{
296 qRODebug(this) << "handleConnection" << m_connections;
297
298 ServerIoDevice *conn = m_server->nextPendingConnection();
299 newConnection(conn);
300}
301
302void QRemoteObjectSourceIo::newConnection(IoDeviceBase *conn)
303{
304 m_connections.insert(value: conn);
305 connect(sender: conn, signal: &IoDeviceBase::readyRead, context: this, slot: [this, conn]() {
306 onServerRead(conn);
307 });
308 connect(sender: conn, signal: &IoDeviceBase::disconnected, context: this, slot: [this, conn]() {
309 onServerDisconnect(conn);
310 });
311
312 serializeHandshakePacket(m_packet);
313 conn->write(data: m_packet.array, m_packet.size);
314
315 QRemoteObjectPackets::ObjectInfoList infos;
316 infos.reserve(asize: m_sourceRoots.size());
317 for (auto remoteObject : qAsConst(t&: m_sourceRoots)) {
318 infos << QRemoteObjectPackets::ObjectInfo{.name: remoteObject->m_api->name(), .typeName: remoteObject->m_api->typeName(), .signature: remoteObject->m_api->objectSignature()};
319 }
320 serializeObjectListPacket(m_packet, infos);
321 conn->write(data: m_packet.array, m_packet.size);
322 qRODebug(this) << "Wrote ObjectList packet from Server" << QStringList(m_sourceRoots.keys());
323}
324
325QUrl QRemoteObjectSourceIo::serverAddress() const
326{
327 if (m_server)
328 return m_server->address();
329 return m_address;
330}
331
332QT_END_NAMESPACE
333

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