blob: a6cdd11a45c7630963b0246d6bddfe5391cfe8f3 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2017 Ford Motor Company
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtRemoteObjects module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qremoteobjectsourceio_p.h"
#include "qremoteobjectpacket_p.h"
#include "qremoteobjectsource_p.h"
#include "qremoteobjectnode_p.h"
#include "qremoteobjectpendingcall.h"
#include "qtremoteobjectglobal.h"
#include <QtCore/qstringlist.h>
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);
QRemoteObjectPackets::serializeObjectListPacket(m_packet, {QRemoteObjectPackets::ObjectInfo{api->name(), api->typeName(), api->objectSignature()}});
for (auto conn : m_connections)
conn->write(m_packet.array, m_packet.size);
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::registerSource(QRemoteObjectSourceBase *source)
{
Q_ASSERT(source);
const QString &name = source->name();
m_sourceObjects[name] = source;
if (source->isRoot()) {
QRemoteObjectRootSource *root = static_cast<QRemoteObjectRootSource *>(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)
{
IoDeviceBase *connection = qobject_cast<IoDeviceBase*>(conn);
m_connections.remove(connection);
qRODebug(this) << "OnServerDisconnect";
for (QRemoteObjectRootSource *root : qAsConst(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
IoDeviceBase *connection = qobject_cast<IoDeviceBase*>(conn);
QRemoteObjectPacketTypeEnum packetType;
do {
if (!connection->read(packetType, m_rxName))
return;
using namespace QRemoteObjectPackets;
switch (packetType) {
case Ping:
serializePongPacket(m_packet, m_rxName);
connection->write(m_packet.array, m_packet.size);
break;
case AddObject:
{
bool isDynamic;
deserializeAddObjectPacket(connection->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;
deserializeInvokePacket(connection->stream(), call, index, m_rxArgs, serialId, propertyId);
if (m_rxName == QLatin1String("Registry") && !m_registryMapping.contains(connection)) {
const QRemoteObjectSourceLocation loc = m_rxArgs.first().value<QRemoteObjectSourceLocation>();
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_rxName;
//TODO - consider moving this to packet validation?
break;
}
if (source->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++)
decodeVariant(m_rxArgs[i], method.parameterType(i));
}
int typeId = QMetaType::type(source->m_api->typeName(index).constData());
if (!QMetaType(typeId).sizeOf())
typeId = QVariant::Invalid;
QVariant returnValue(typeId, 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>(QRemoteObjectPendingCall());
source->invoke(QMetaObject::InvokeMetaMethod, index, m_rxArgs, &returnValue);
// send reply if wanted
if (serialId >= 0) {
if (returnValue.canConvert<QRemoteObjectPendingCall>()) {
QRemoteObjectPendingCall call = returnValue.value<QRemoteObjectPendingCall>();
// 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) {
serializeInvokeReplyPacket(this->m_packet, this->m_rxName, serialId, encodeVariant(watcher->returnValue()));
connection->write(m_packet.array, m_packet.size);
}
watcher->deleteLater();
});
} else {
serializeInvokeReplyPacket(m_packet, m_rxName, serialId, encodeVariant(returnValue));
connection->write(m_packet.array, m_packet.size);
}
}
} 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_rxName;
//TODO - consider moving this to packet validation?
break;
}
if (source->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;
ServerIoDevice *conn = m_server->nextPendingConnection();
newConnection(conn);
}
void QRemoteObjectSourceIo::newConnection(IoDeviceBase *conn)
{
m_connections.insert(conn);
connect(conn, &IoDeviceBase::readyRead, this, [this, conn]() {
onServerRead(conn);
});
connect(conn, &IoDeviceBase::disconnected, this, [this, conn]() {
onServerDisconnect(conn);
});
serializeHandshakePacket(m_packet);
conn->write(m_packet.array, m_packet.size);
QRemoteObjectPackets::ObjectInfoList infos;
infos.reserve(m_sourceRoots.size());
for (auto remoteObject : qAsConst(m_sourceRoots)) {
infos << QRemoteObjectPackets::ObjectInfo{remoteObject->m_api->name(), remoteObject->m_api->typeName(), remoteObject->m_api->objectSignature()};
}
serializeObjectListPacket(m_packet, infos);
conn->write(m_packet.array, m_packet.size);
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