Revision control
Copy as Markdown
// 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
// Qt-Security score:critical reason:network-protocol
#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 <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);
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<LocalServerImpl *>(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<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)
{
QtROIoDeviceBase *connection = qobject_cast<QtROIoDeviceBase*>(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<QtROIoDeviceBase*>(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)) {
bool expectedFormat = false;
if (m_rxArgs.size() > 0) {
const QVariant arg = m_rxArgs.first();
if (arg.canConvert<QRemoteObjectSourceLocation>()) {
const QRemoteObjectSourceLocation loc = arg.value<QRemoteObjectSourceLocation>();
m_registryMapping[connection] = loc.second.hostUrl;
expectedFormat = true;
}
}
if (!expectedFormat) {
qROWarning(this) << "Unexpected packet format for the Registry object. "
"Not enough arguments or the first argument is not "
"QRemoteObjectSourceLocation.";
break;
}
}
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;
}
QMetaMethod method;
if (source->m_api->isAdapterMethod(index)) {
method = source->m_adapter->metaObject()->method(resolvedIndex);
qRODebug(this) << "Adapter (method) Invoke-->" << m_rxName << method.name();
} else {
method = source->m_object->metaObject()->method(resolvedIndex);
qRODebug(this) << "Source (method) Invoke-->" << m_rxName << method.methodSignature();
}
{
const int parameterCount = method.parameterCount();
if (m_rxArgs.size() != parameterCount) {
qROWarning(this) << "Argument count mismatch for" << m_rxName
<< method.methodSignature() << "- expected"
<< parameterCount << "got" << m_rxArgs.size()
<< ". Ignoring the call.";
break;
}
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>(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) {
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_rxName;
//TODO - consider moving this to packet validation?
break;
}
QMetaProperty property;
if (source->m_api->isAdapterProperty(index)) {
property = source->m_adapter->metaObject()->property(resolvedIndex);
qRODebug(this) << "Adapter (write property) Invoke-->" << m_rxName << property.name();
} else {
property = source->m_object->metaObject()->property(resolvedIndex);
qRODebug(this) << "Source (write property) Invoke-->" << m_rxName << property.name();
}
if (m_rxArgs.size() != 1) {
qROWarning(this) << "Invalid argument count for property invoke packet. Got"
<< m_rxArgs.size() << "arguments for" << m_rxName
<< property.name();
break;
}
m_rxArgs[0] = decodeVariant(std::move(m_rxArgs[0]), property.metaType());
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