// Copyright (C) 2017 Ford Motor Company // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qremoteobjectsourceio_p.h" #include "qremoteobjectpacket_p.h" #include "qremoteobjectsource_p.h" #include "qremoteobjectnode_p.h" #include "qremoteobjectpendingcall.h" #include "qtremoteobjectglobal.h" #include "qconnection_local_backend_p.h" #include QT_BEGIN_NAMESPACE using namespace QtRemoteObjects; QRemoteObjectSourceIo::QRemoteObjectSourceIo(const QUrl &address, QObject *parent) : QObject(parent) , m_server(QtROServerFactory::instance()->isValid(address) ? QtROServerFactory::instance()->create(address, this) : nullptr) , m_address(address) { if (m_server == nullptr) qRODebug(this) << "Using" << m_address << "as external url."; } QRemoteObjectSourceIo::QRemoteObjectSourceIo(QObject *parent) : QObject(parent) , m_server(nullptr) { } QRemoteObjectSourceIo::~QRemoteObjectSourceIo() { qDeleteAll(m_sourceRoots.values()); } bool QRemoteObjectSourceIo::startListening() { if (!m_server->listen(m_address)) { qROCritical(this) << "Listen failed for URL:" << m_address; qROCritical(this) << m_server->serverError(); return false; } qRODebug(this) << "QRemoteObjectSourceIo is Listening" << m_address; connect(m_server.data(), &QConnectionAbstractServer::newConnection, this, &QRemoteObjectSourceIo::handleConnection); return true; } bool QRemoteObjectSourceIo::enableRemoting(QObject *object, const QMetaObject *meta, const QString &name, const QString &typeName) { if (m_sourceRoots.contains(name)) { qROWarning(this) << "Tried to register QRemoteObjectRootSource twice" << name; return false; } return enableRemoting(object, new DynamicApiMap(object, meta, name, typeName)); } bool QRemoteObjectSourceIo::enableRemoting(QObject *object, const SourceApiMap *api, QObject *adapter) { const QString name = api->name(); if (!api->isDynamic() && m_sourceRoots.contains(name)) { qROWarning(this) << "Tried to register QRemoteObjectRootSource twice" << name; return false; } new QRemoteObjectRootSource(object, api, adapter, this); m_codec->serializeObjectListPacket({QRemoteObjectPackets::ObjectInfo{api->name(), api->typeName(), api->objectSignature()}}); m_codec->send(m_connections); if (const int count = m_connections.size()) qRODebug(this) << "Wrote new QObjectListPacket for" << api->name() << "to" << count << "connections"; return true; } bool QRemoteObjectSourceIo::disableRemoting(QObject *object) { QRemoteObjectRootSource *source = m_objectToSourceMap.take(object); if (!source) return false; delete source; return true; } void QRemoteObjectSourceIo::setSocketOptions(QLocalServer::SocketOptions options) { if (!m_server.isNull()) { LocalServerImpl *server = qobject_cast(m_server.get()); if (server != nullptr) { server->setSocketOptions(options); } else { qROWarning(this) << "Attempting to set socket options to a backend that is non-localserver"; } } } void QRemoteObjectSourceIo::registerSource(QRemoteObjectSourceBase *source) { Q_ASSERT(source); const QString &name = source->name(); m_sourceObjects[name] = source; if (source->isRoot()) { QRemoteObjectRootSource *root = static_cast(source); qRODebug(this) << "Registering" << name; m_sourceRoots[name] = root; m_objectToSourceMap[source->m_object] = root; if (serverAddress().isValid()) { const auto &type = source->m_api->typeName(); emit remoteObjectAdded(qMakePair(name, QRemoteObjectSourceLocationInfo(type, serverAddress()))); } } } void QRemoteObjectSourceIo::unregisterSource(QRemoteObjectSourceBase *source) { Q_ASSERT(source); const QString &name = source->name(); m_sourceObjects.remove(name); if (source->isRoot()) { const auto type = source->m_api->typeName(); m_objectToSourceMap.remove(source->m_object); m_sourceRoots.remove(name); if (serverAddress().isValid()) emit remoteObjectRemoved(qMakePair(name, QRemoteObjectSourceLocationInfo(type, serverAddress()))); } } void QRemoteObjectSourceIo::onServerDisconnect(QObject *conn) { QtROIoDeviceBase *connection = qobject_cast(conn); m_connections.remove(connection); qRODebug(this) << "OnServerDisconnect"; for (QRemoteObjectRootSource *root : std::as_const(m_sourceRoots)) root->removeListener(connection); const QUrl location = m_registryMapping.value(connection); emit serverRemoved(location); m_registryMapping.remove(connection); connection->close(); connection->deleteLater(); } void QRemoteObjectSourceIo::onServerRead(QObject *conn) { // Assert the invariant here conn is of type QIODevice QtROIoDeviceBase *connection = qobject_cast(conn); QRemoteObjectPacketTypeEnum packetType; do { if (!connection->read(packetType, m_rxName)) return; using namespace QRemoteObjectPackets; switch (packetType) { case Ping: m_codec->serializePongPacket(m_rxName); m_codec->send(connection); break; case AddObject: { bool isDynamic; m_codec->deserializeAddObjectPacket(connection->d_func()->stream(), isDynamic); qRODebug(this) << "AddObject" << m_rxName << isDynamic; if (m_sourceRoots.contains(m_rxName)) { QRemoteObjectRootSource *root = m_sourceRoots[m_rxName]; root->addListener(connection, isDynamic); } else { qROWarning(this) << "Request to attach to non-existent RemoteObjectSource:" << m_rxName; } break; } case RemoveObject: { qRODebug(this) << "RemoveObject" << m_rxName; if (m_sourceRoots.contains(m_rxName)) { QRemoteObjectRootSource *root = m_sourceRoots[m_rxName]; const int count = root->removeListener(connection); Q_UNUSED(count); //TODO - possible to have a timer that closes connections if not reopened within a timeout? } else { qROWarning(this) << "Request to detach from non-existent RemoteObjectSource:" << m_rxName; } qRODebug(this) << "RemoveObject finished" << m_rxName; break; } case InvokePacket: { int call, index, serialId, propertyId; m_codec->deserializeInvokePacket(connection->d_func()->stream(), call, index, m_rxArgs, serialId, propertyId); if (m_rxName == QLatin1String("Registry") && !m_registryMapping.contains(connection)) { const QRemoteObjectSourceLocation loc = m_rxArgs.first().value(); m_registryMapping[connection] = loc.second.hostUrl; } if (m_sourceObjects.contains(m_rxName)) { QRemoteObjectSourceBase *source = m_sourceObjects[m_rxName]; if (call == QMetaObject::InvokeMetaMethod) { const int resolvedIndex = source->m_api->sourceMethodIndex(index); if (resolvedIndex < 0) { //Invalid index qROWarning(this) << "Invalid method invoke packet received. Index =" << index <<"which is out of bounds for type"<m_api->isAdapterMethod(index)) qRODebug(this) << "Adapter (method) Invoke-->" << m_rxName << source->m_adapter->metaObject()->method(resolvedIndex).name(); else { qRODebug(this) << "Source (method) Invoke-->" << m_rxName << source->m_object->metaObject()->method(resolvedIndex).methodSignature(); auto method = source->m_object->metaObject()->method(resolvedIndex); const int parameterCount = method.parameterCount(); for (int i = 0; i < parameterCount; i++) m_rxArgs[i] = decodeVariant(std::move(m_rxArgs[i]), method.parameterMetaType(i)); } auto metaType = QMetaType::fromName(source->m_api->typeName(index).constData()); if (!metaType.sizeOf()) metaType = QMetaType(QMetaType::UnknownType); QVariant returnValue(metaType, nullptr); // If a Replica is used as a Source (which node->proxy() does) we can have a PendingCall return value. // In this case, we need to wait for the pending call and send that. if (source->m_api->typeName(index) == QByteArrayLiteral("QRemoteObjectPendingCall")) returnValue = QVariant::fromValue(QRemoteObjectPendingCall()); source->invoke(QMetaObject::InvokeMetaMethod, index, m_rxArgs, &returnValue); // send reply if wanted if (serialId >= 0) { if (returnValue.canConvert()) { QRemoteObjectPendingCall call = returnValue.value(); // Watcher will be destroyed when connection is, or when the finished lambda is called QRemoteObjectPendingCallWatcher *watcher = new QRemoteObjectPendingCallWatcher(call, connection); QObject::connect(watcher, &QRemoteObjectPendingCallWatcher::finished, connection, [this, serialId, connection, watcher]() { if (watcher->error() == QRemoteObjectPendingCall::NoError) { m_codec->serializeInvokeReplyPacket(this->m_rxName, serialId, encodeVariant(watcher->returnValue())); m_codec->send(connection); } watcher->deleteLater(); }); } else { m_codec->serializeInvokeReplyPacket(m_rxName, serialId, encodeVariant(returnValue)); m_codec->send(connection); } } } else { const int resolvedIndex = source->m_api->sourcePropertyIndex(index); if (resolvedIndex < 0) { qROWarning(this) << "Invalid property invoke packet received. Index =" << index <<"which is out of bounds for type"<m_api->isAdapterProperty(index)) qRODebug(this) << "Adapter (write property) Invoke-->" << m_rxName << source->m_adapter->metaObject()->property(resolvedIndex).name(); else qRODebug(this) << "Source (write property) Invoke-->" << m_rxName << source->m_object->metaObject()->property(resolvedIndex).name(); source->invoke(QMetaObject::WriteProperty, index, m_rxArgs); } } break; } default: qRODebug(this) << "OnReadReady invalid type" << packetType; } } while (connection->bytesAvailable()); // have bytes left over, so do another iteration } void QRemoteObjectSourceIo::handleConnection() { qRODebug(this) << "handleConnection" << m_connections; QtROServerIoDevice *conn = m_server->nextPendingConnection(); newConnection(conn); } void QRemoteObjectSourceIo::newConnection(QtROIoDeviceBase *conn) { m_connections.insert(conn); connect(conn, &QtROIoDeviceBase::readyRead, this, [this, conn]() { onServerRead(conn); }); connect(conn, &QtROIoDeviceBase::disconnected, this, [this, conn]() { onServerDisconnect(conn); }); m_codec->serializeHandshakePacket(); m_codec->send(conn); QRemoteObjectPackets::ObjectInfoList infos; infos.reserve(m_sourceRoots.size()); for (auto remoteObject : std::as_const(m_sourceRoots)) { infos << QRemoteObjectPackets::ObjectInfo{remoteObject->m_api->name(), remoteObject->m_api->typeName(), remoteObject->m_api->objectSignature()}; } m_codec->serializeObjectListPacket(infos); m_codec->send(conn); qRODebug(this) << "Wrote ObjectList packet from Server" << QStringList(m_sourceRoots.keys()); } QUrl QRemoteObjectSourceIo::serverAddress() const { if (m_server) return m_server->address(); return m_address; } QT_END_NAMESPACE