/**************************************************************************** ** ** Copyright (C) 2018 The Qt Company Ltd and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtSystems 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 "objectendpoint_dbus_p.h" #include "instancemanager_p.h" #include "proxyobject_p.h" #include "qsignalintercepter_p.h" #include #include #include #include QT_BEGIN_NAMESPACE class Response { public: Response() : isFinished(false), result(0) { } bool isFinished; void* result; }; typedef QHash Replies; Q_GLOBAL_STATIC(Replies, openRequests); class ServiceSignalIntercepter : public QSignalIntercepter { //Do not put Q_OBJECT here public: ServiceSignalIntercepter(QObject* sender, const QByteArray& signal, ObjectEndPoint* parent) : QSignalIntercepter(sender, signal, parent), endPoint(parent) { } void setMetaIndex(int index) { metaIndex = index; } protected: void activated( const QList& args ) { endPoint->invokeRemote(metaIndex, args, QMetaType::Void); } private: ObjectEndPoint* endPoint; int metaIndex; }; struct ClientInstance { QString clientId; QRemoteServiceRegister::Entry entry; QUuid instanceId; int ref; }; class ObjectEndPointPrivate { public: ObjectEndPointPrivate() { } ~ObjectEndPointPrivate() { } // Used on client and service side ObjectEndPoint::Type endPointType; ObjectEndPoint* parent; // Used to calculate the registered paths on DBus QRemoteServiceRegister::Entry entry; QUuid serviceInstanceId; // Service side local client ownership list QList clientList; }; /*! Client to service communication only used for establishing an object request since the returned proxy is an interface to that object registered on QtDBus. Client communicates directly to QtDBus for method and property access. Signals are automatically relayed from QtDBus to the proxy object. */ ObjectEndPoint::ObjectEndPoint(Type type, QServiceIpcEndPoint* comm, QObject* parent) : QObject(parent), dispatch(comm), service(0), iface(0) { Q_ASSERT(dispatch); d = new ObjectEndPointPrivate; d->parent = this; d->endPointType = type; dispatch->setParent(this); connect(dispatch, SIGNAL(readyRead()), this, SLOT(newPackageReady())); if (type == Client) { // client waiting for construct proxy and registers DBus custom type qDBusRegisterMetaType(); qRegisterMetaType(); return; } else { connect(InstanceManager::instance(), SIGNAL(instanceClosed(QRemoteServiceRegister::Entry,QUuid)), this, SLOT(unregisterObjectDBus(QRemoteServiceRegister::Entry,QUuid))); if (dispatch->packageAvailable()) QTimer::singleShot(0, this, SLOT(newPackageReady())); } } ObjectEndPoint::~ObjectEndPoint() { if (iface) delete iface; delete d; } /*! Removes all instances of the client from the instance manager */ void ObjectEndPoint::disconnected(const QString& clientId, const QString& instanceId) { // Service Side Q_ASSERT(d->endPointType != ObjectEndPoint::Client); for (int i=d->clientList.size()-1; i>=0; i--) { // Find right client process if (d->clientList[i].clientId == clientId) { if (d->clientList[i].ref-- == 1) { QRemoteServiceRegister::Entry entry = d->clientList[i].entry; QUuid instance = d->clientList[i].instanceId; if (instance.toString() == instanceId) { // Remove an instance from the InstanceManager and local list InstanceManager::instance()->removeObjectInstance(entry, instance); d->clientList.removeAt(i); } } } } } /*! Unregisters the DBus object */ void ObjectEndPoint::unregisterObjectDBus(const QRemoteServiceRegister::Entry& entry, const QUuid& id) { uint hash = qHash(id.toString()); QString objPath = QLatin1Char('/') + entry.interfaceName() + QLatin1Char('/') + entry.version() + QLatin1Char('/') + QString::number(hash); objPath.replace(QLatin1Char('.'), QLatin1Char('/')); QDBusConnection::sessionBus().unregisterObject(objPath, QDBusConnection::UnregisterTree); } /*! Client requests proxy object. The proxy is owned by calling code and this object must clean itself up upon destruction of proxy. */ QObject* ObjectEndPoint::constructProxy(const QRemoteServiceRegister::Entry& entry) { // Client side Q_ASSERT(d->endPointType == ObjectEndPoint::Client); // Request a serialized meta object QServicePackage p; p.d = new QServicePackagePrivate(); p.d->messageId = QUuid::createUuid(); p.d->entry = entry; Response* response = new Response(); openRequests()->insert(p.d->messageId, response); dispatch->writePackage(p); waitForResponse(p.d->messageId); // Get the proxy based on the meta object if (response->isFinished) { if (response->result == 0) qWarning() << "Request for remote service failed"; else service = reinterpret_cast(response->result); } else { qDebug() << "response passed but not finished"; } openRequests()->take(p.d->messageId); delete response; if (!service) return 0; // Connect all DBus interface signals to the proxy slots const QMetaObject *mo = service->metaObject(); while (mo && strcmp(mo->className(), "QObject")) { for (int i = mo->methodOffset(); i < mo->methodCount(); i++) { const QMetaMethod mm = mo->method(i); if (mm.methodType() == QMetaMethod::Signal) { QByteArray sig(mm.methodSignature()); bool customType = false; QList params = mm.parameterTypes(); for (int arg = 0; arg < params.size(); arg++) { const QByteArray& type = params[arg]; int variantType = QMetaType::type(type); if (variantType >= QMetaType::User || variantType == QMetaType::QVariant) { sig.replace(QByteArray(type), QByteArray("QDBusVariant")); customType = true; } } int serviceIndex = iface->metaObject()->indexOfSignal(sig); QByteArray signal = QByteArray("2").append(sig); if (serviceIndex > 0) { if (customType) { QObject::connect(iface, signal.constData(), signalsObject, signal.constData()); ServiceSignalIntercepter *intercept = new ServiceSignalIntercepter((QObject*)signalsObject, signal, this); intercept->setMetaIndex(localToRemote[i]); } else { QObject::connect(iface, signal.constData(), service, signal.constData()); } } } } mo = mo->superClass(); } return service; } /*! Received a new package from the DBus client-server controller. Once an object request is handled there is only direct communication to the DBus object so no other package types should be received on this layer. */ void ObjectEndPoint::newPackageReady() { // Client and service side while (dispatch->packageAvailable()) { // must call get getSecurityCredentials everytime you call nextPackage QServiceClientCredentials creds; dispatch->getSecurityCredentials(creds); QServicePackage p = dispatch->nextPackage(); if (!p.isValid()) continue; if (p.d->packageType == QServicePackage::ObjectCreation) { objectRequest(p, creds); } else { qWarning() << "Unknown package type received."; } } } void ObjectEndPoint::setLookupTable(int *local, int *remote) { localToRemote = local; remoteToLocal = remote; } /*! Service finds existing objects or spawns new object instances and registers them on DBus using a hash of the unique instance ID. This registered object has a special metaobject representation of the service that is compatible with the QDBus type system. Client receives a package containing the information to connect an interface to the registered DBus object. */ void ObjectEndPoint::objectRequest(const QServicePackage& p, QServiceClientCredentials& creds) { if (p.d->responseType != QServicePackage::NotAResponse ) { // Client side Q_ASSERT(d->endPointType == ObjectEndPoint::Client); d->serviceInstanceId = p.d->instanceId; d->entry = p.d->entry; Response* response = openRequests()->value(p.d->messageId); if (p.d->responseType == QServicePackage::Failed) { response->result = 0; response->isFinished = true; QTimer::singleShot(0, this, SIGNAL(pendingRequestFinished())); qWarning() << "Service instantiation failed"; return; } // Deserialize meta object and create proxy object QServiceProxy* proxy = new QServiceProxy(p.d->payload.toByteArray(), this); response->result = reinterpret_cast(proxy); response->isFinished = true; // Create DBUS interface by using a hash of the service instance ID QString serviceName = QStringLiteral("com.nokia.qtmobility.sfw.") + p.d->entry.serviceName(); uint hash = qHash(d->serviceInstanceId.toString()); QString objPath = QLatin1Char('/') + p.d->entry.interfaceName() + QLatin1Char('/') + p.d->entry.version() + QLatin1Char('/') + QString::number(hash); objPath.replace(QLatin1Char('.'), QLatin1Char('/')); #ifdef DEBUG qDebug() << "Client Interface ObjectPath:" << objPath; #endif // Instantiate our DBus interface and its corresponding signals object if (!iface) iface = new QDBusInterface(serviceName, objPath, QString(), QDBusConnection::sessionBus(), this); signalsObject = new QServiceMetaObjectDBus(iface, true); // Wake up waiting proxy construction code QTimer::singleShot(0, this, SIGNAL(pendingRequestFinished())); } else { // Service side Q_ASSERT(d->endPointType == ObjectEndPoint::Service); QServicePackage response = p.createResponse(); InstanceManager* iManager = InstanceManager::instance(); if (!creds.isValid()) { qWarning() << "SFW Unable to get socket credentials client asking for" << p.d->entry.interfaceName() << p.d->entry.serviceName() << "this may fail in the future"; } // Instantiate service object from type register service = iManager->createObjectInstance(p.d->entry, d->serviceInstanceId, creds); if (!service) { qWarning() << "Cannot instantiate service object"; dispatch->writePackage(response); return; } if (!creds.isClientAccepted()) { qWarning() << "SFW Security failure by" << creds.getProcessIdentifier() << creds.getUserIdentifier() << creds.getGroupIdentifier() << "requesting" << p.d->entry.interfaceName() << p.d->entry.serviceName(); iManager->removeObjectInstance(p.d->entry, d->serviceInstanceId); dispatch->writePackage(response); return; } // Start DBus connection and register proxy service if (!QDBusConnection::sessionBus().isConnected()) { qWarning() << "Cannot connect to DBus"; } // DBus registration path uses a hash of the service instance ID QString serviceName = QStringLiteral("com.nokia.qtmobility.sfw.") + p.d->entry.serviceName(); uint hash = qHash(d->serviceInstanceId.toString()); QString objPath = QLatin1Char('/') + p.d->entry.interfaceName() + QLatin1Char('/') + p.d->entry.version() + QLatin1Char('/') + QString::number(hash); objPath.replace(QLatin1Char('.'), QLatin1Char('/')); QServiceMetaObjectDBus *serviceDBus = new QServiceMetaObjectDBus(service); QDBusConnection::sessionBus().registerObject(objPath, serviceDBus, QDBusConnection::ExportAllContents); QString clientId = p.d->payload.toString(); int exists = 0; for (int i=d->clientList.size()-1; i>=0; i--) { // Find right client process if (d->clientList[i].clientId == clientId) { d->clientList[i].ref++; exists = 1; break; } } if (!exists) { // Add new instance to client ownership list ClientInstance c; c.clientId = clientId; c.entry = p.d->entry; c.instanceId = d->serviceInstanceId; c.ref = 1; d->clientList << c; } #ifdef DEBUG qDebug() << "Service Interface ObjectPath:" << objPath; const QMetaObject *s_meta = service->metaObject(); qDebug() << "+++++++++++++++++++++SERVICE+++++++++++++++++++++++"; qDebug() << s_meta->className(); qDebug() << "METHOD COUNT: " << s_meta->methodCount(); for (int i=0; imethodCount(); i++) { QMetaMethod mm = s_meta->method(i); QString type; switch (mm.methodType()) { case QMetaMethod::Method: type = "Q_INVOKABLE"; break; case QMetaMethod::Signal: type = "SIGNAL"; break; case QMetaMethod::Slot: type = "SLOT"; break; default: break; } QString returnType = mm.typeName(); if (returnType == "") returnType = "void"; qDebug() << "METHOD" << type << ":" << returnType << mm.methodSignature(); } qDebug() << "++++++++++++++++++++++++++++++++++++++++++++++++++++"; if (!iface) iface = new QDBusInterface(serviceName, objPath, "", QDBusConnection::sessionBus(), this); const QMetaObject *i_meta = iface->metaObject(); qDebug() << "++++++++++++++++++++DBUS SERVICE++++++++++++++++++++"; qDebug() << i_meta->className(); qDebug() << "METHOD COUNT: " << i_meta->methodCount(); for (int i=0; imethodCount(); i++) { QMetaMethod mm = i_meta->method(i); QString type; switch (mm.methodType()) { case QMetaMethod::Method: type = "Q_INVOKABLE"; break; case QMetaMethod::Signal: type = "SIGNAL"; break; case QMetaMethod::Slot: type = "SLOT"; break; default: break; } QString returnType = mm.typeName(); if (returnType == "") returnType = "void"; qDebug() << "METHOD" << type << ":" << returnType << mm.methodSignature(); } qDebug() << "+++++++++++++++++++++++++++++++++++++++++++++++++++"; #endif // Get meta object from type register const QMetaObject* meta = iManager->metaObject(p.d->entry); if (!meta) { qDebug() << "Unknown type" << p.d->entry; dispatch->writePackage(response); return; } // Serialize meta object QByteArray data; QDataStream stream( &data, QIODevice::WriteOnly | QIODevice::Append ); QMetaObjectBuilder builder(meta); builder.serialize(stream); // Send meta object and instance ID to the client for processing d->entry = p.d->entry; response.d->instanceId = d->serviceInstanceId; response.d->entry = p.d->entry; response.d->responseType = QServicePackage::Success; response.d->payload = QVariant(data); dispatch->writePackage(response); } } /*! Returns the created service instance Id */ QString ObjectEndPoint::getInstanceId() const { Q_ASSERT(d->endPointType == ObjectEndPoint::Client); return d->serviceInstanceId.toString(); } /*! Client side property call that directly accesses properties through the DBus interface. Read and reset have special hardcoded DBus methods due to the nature of QtDBus properties without an adaptor class incorrectly forwarding the metacall type */ QVariant ObjectEndPoint::invokeRemoteProperty(int metaIndex, const QVariant& arg, int /*returnType*/, QMetaObject::Call c ) { Q_ASSERT(d->endPointType == ObjectEndPoint::Client); const QMetaObject *imeta = service->metaObject(); QMetaProperty property = imeta->property(metaIndex); if (c == QMetaObject::WriteProperty) { // Writing property, direct property DBus call if (!iface->setProperty(property.name(), arg)) { qWarning() << "Service property write call failed"; } } else if (c == QMetaObject::ResetProperty) { // Resetting property, direct special method DBus call QVariantList args; args << QVariant(QLatin1String(property.name())); QDBusMessage msg = iface->callWithArgumentList(QDBus::Block, QLatin1String("propertyReset"), args); if (msg.type() == QDBusMessage::InvalidMessage) { qWarning() << "Service property reset call failed"; } } else if (c == QMetaObject::ReadProperty) { // Reading property, direct special method DBus call QVariantList args; args << QVariant(QLatin1String(property.name())); QDBusMessage msg = iface->callWithArgumentList(QDBus::Block, QLatin1String("propertyRead"), args); if (msg.type() == QDBusMessage::ReplyMessage) { QVariantList retList = msg.arguments(); return retList[0]; } else { qWarning() << "Service property read call failed" << msg.errorMessage(); } } else { qWarning() << "Invalid property call"; } return QVariant(); } /*! Client side method call that converts an argument of type to its corresponding value as a valid type supported by the QtDBus type system. Supports conversion from a QVariant, QList, QMap, QHash, and custom user-defined types. */ QVariant ObjectEndPoint::toDBusVariant(const QByteArray& typeName, const QVariant& arg) { QVariant dbusVariant = arg; int type = QMetaType::type(typeName); if (type == QMetaType::QVariant) { // Wrap QVariants in a QDBusVariant QDBusVariant replacement(arg); dbusVariant = QVariant::fromValue(replacement); } else if (type >= QMetaType::User) { // Wrap custom types in a QDBusVariant of the type name and // a buffer of its variant-wrapped data QByteArray buffer; QDataStream stream(&buffer, QIODevice::ReadWrite | QIODevice::Append); stream << arg; QServiceUserTypeDBus customType; customType.typeName = typeName; customType.variantBuffer = buffer; QDBusVariant replacement(QVariant::fromValue(customType)); dbusVariant = QVariant::fromValue(replacement); } return dbusVariant; } /*! Client side method call that directly accesses the object through the DBus interface. All arguments and return types are processed and converted accordingly so that all functions satisfy the QtDBus type system. */ QVariant ObjectEndPoint::invokeRemote(int metaIndex, const QVariantList& args, int returnType) { QMetaMethod method = service->metaObject()->method(remoteToLocal[metaIndex]); Q_ASSERT(d->endPointType == ObjectEndPoint::Client); // Check is this is a signal relay if (method.methodType() == QMetaMethod::Signal) { // Convert custom arguments QVariantList convertedList; QList params = method.parameterTypes(); for (int i = 0; i < params.size(); i++) { const QByteArray& type = params[i]; int variantType = QMetaType::type(type); if (variantType >= QMetaType::User || variantType == QMetaType::QVariant) { QDBusVariant dbusVariant = qvariant_cast(args[i]); QVariant variant = dbusVariant.variant(); if (variantType == QMetaType::QVariant) { convertedList << variant; } else { QByteArray buffer = variant.toByteArray(); QDataStream stream(&buffer, QIODevice::ReadWrite); QVariant *customType = new QVariant(variantType, (const void*)0); QMetaType::load(stream, QMetaType::QVariant, customType); convertedList << *customType; } } else { convertedList << args[i]; } } // Signal relay const int numArgs = convertedList.size(); QVarLengthArray a( numArgs+1 ); a[0] = 0; const QList pTypes = method.parameterTypes(); for ( int arg = 0; arg < numArgs; ++arg ) { if (pTypes.at(arg) == "QVariant") { a[arg+1] = (void *)&( convertedList[arg] ); } else { a[arg+1] = (void *)( convertedList[arg].data() ); } } // Activate the service proxy signal call QMetaObject::activate(service, remoteToLocal[metaIndex], a.data()); return QVariant(); } // Method call so process arguments and convert if not a supported DBus type QVariantList convertedList; QList params = method.parameterTypes(); for (int i = 0; i < params.size(); i++) { QVariant converted = toDBusVariant(params[i], args[i]); convertedList << converted; } bool validDBus = false; QDBusMessage msg; // Find the method name and try a direct DBus call QString methodName(QLatin1String(method.methodSignature().constData())); methodName.truncate(methodName.indexOf(QLatin1String("("))); if (method.methodType() == QMetaMethod::Slot || method.methodType() == QMetaMethod::Method) { // Slot or Invokable method msg = iface->callWithArgumentList(QDBus::Block, methodName, convertedList); if (msg.type() == QDBusMessage::ReplyMessage) { validDBus = true; } } // DBus call should only fail for methods with invalid type definitions if (validDBus) { if (returnType == QMetaType::Void) { // Void method call return QVariant(); } else { // Use DBus message return value QVariantList retList = msg.arguments(); // Process return const QByteArray& retType = QByteArray(method.typeName()); int variantType = QMetaType::type(retType); if (variantType == QMetaType::QVariant) { // QVariant return from QDBusVariant wrapper QDBusVariant dbusVariant = qvariant_cast(retList[0]); return dbusVariant.variant(); } else if (variantType >= QMetaType::User) { // Custom return type QDBusVariant dbusVariant = qvariant_cast(retList[0]); QVariant convert = dbusVariant.variant(); QServiceUserTypeDBus customType = qdbus_cast(convert); QByteArray buffer = customType.variantBuffer; QDataStream stream(&buffer, QIODevice::ReadWrite); // Load our buffered variant-wrapped custom return QVariant *customReturn = new QVariant(variantType, (const void*)0); QMetaType::load(stream, QMetaType::QVariant, customReturn); return QVariant(variantType, customReturn->data()); } else { // Standard return type return retList[0]; } } } else { qWarning( "%s::%s cannot be called.", service->metaObject()->className(), method.methodSignature().constData()); } return QVariant(); } /*! Client side waits for service side requested */ void ObjectEndPoint::waitForResponse(const QUuid& requestId) { Q_ASSERT(d->endPointType == ObjectEndPoint::Client); if (openRequests()->contains(requestId) ) { Response* response = openRequests()->value(requestId); QEventLoop* loop = new QEventLoop( this ); connect(this, SIGNAL(pendingRequestFinished()), loop, SLOT(quit())); while (!response->isFinished) { loop->exec(); } delete loop; } } #include "moc_objectendpoint_dbus_p.cpp" QT_END_NAMESPACE