diff options
26 files changed, 1089 insertions, 65 deletions
diff --git a/src/remoteobjects/doc/src/remoteobjects-external-schemas.qdoc b/src/remoteobjects/doc/src/remoteobjects-external-schemas.qdoc new file mode 100644 index 0000000..fdc8e4f --- /dev/null +++ b/src/remoteobjects/doc/src/remoteobjects-external-schemas.qdoc @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Ford Motor Company +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! +\page qtremoteobjects-external-schemas.html +\title Qt Remote Objects - External QIODevices +\brief Describes how Qt Remote Objects supports custom QIODevice channels. + +\section1 External QIODevices + +Qt Remote Objects supports several communications channels out-of-the-box, such +as the \l QTcpServer and \l QTcpSocket pair. Given the desired \l QUrl for tcp, +or the desired name (for the \l QLocalServer and \l QLocalSocket pair), the +code needed to listen and connect are boilerplate and handled internally by Qt. +Qt Remote Objects supports other types of \l QIODevice as well, and the \l +QRemoteObjectNode classes provide additional methods to support cases where +custom code is needed. + +A contrived example with TCP/IP is shown below. A more realistic example would +use an SSL connection, which would require configuration of certificates, etc. + +\code + // Create the server and listen outside of QtRO + QTcpServer tcpServer; + tcpServer.listen(QHostAddress(127.0.0.1), 65213); + + // Create the host node. We don't need a hostUrl unless we want to take + // advantage of external schemas (see next example). + QRemoteObjectHost srcNode(); + + // Make sure any connections are handed to QtRO + QObject::connect(&tcpServer, &QTcpServer::newConnection, &srcNode, + [&srcNode, &tcpServer]() { + srcNode.addHostSideConnection(tcpServer.nextPendingConnection()); + }); +\endcode + +The Replica side code needs to manually connect to the Host +\code + QRemoteObjectNode repNode(); + QTcpSocket *socket = new QTcpSocket(&repNode); + connect(socket, &QTcpSocket::connected, &repNode, + [socket, &repNode]() { + repNode.addClientSideConnection(socket); + }); + socket->connectToHost(QHostAddress(127.0.0.1), 65213); + }; +\endcode + +\section1 External Schemas + +It is possible to create each side of the QIODevice and call \l +{QRemoteObjectNode::addClientSideConnection(QIODevice *ioDevice)} and \l +{QRemoteObjectHostBase::addHostSideConnection(QIODevice *ioDevice)} as shown +above. This is fully supported, but requires the client know how to establish +the connection or have a way to discover that information. This is exactly the +problem the registry was designed to solve. + +Qt Remote Objects also allows "External Schemas" to be used with the registry, +which helps with connection setup. On the \l QRemoteObjectHostNode side, the +user must set the hostUrl with the desired schema. + +\code + // Use standard tcp url for the registry + const QUrl registryUrl = QUrl(QStringLiteral("tcp://127.0.0.1:65212")); + // Use "exttcp" for the "external" interface + const QUrl extUrl = QUrl(QStringLiteral("exttcp://127.0.0.1:65213")); + + // Create the server and listen outside of QtRO + QTcpServer tcpServer; + tcpServer.listen(QHostAddress(extUrl.host()), extUrl.port()); + + // We need a registry for everyone to connect to + QRemoteObjectRegistryHost registry(registryUrl); + + // Finally, we create our host node and register "exttcp" as our schema. + // We need the AllowExternalRegistration parameter to prevent the node from + // setting a hostUrlInvalid error. + QRemoteObjectHost srcNode(extUrl, registryUrl, QRemoteObjectHost::AllowExternalRegistration); + // From now on, when we call enableRemoting() from this node, the registry + // will be updated to show the Source object at extUrl. +\endcode + +On the \l Replica side, the \l QRemoteObjectNode needs to register a callback +to be used when the external schema is detected. The callback must be a \l +RemoteObjectSchemaHandler. + +\code + // Use standard tcp url for the registry + const QUrl registryUrl = QUrl(QStringLiteral("tcp://127.0.0.1:65212")); + + // This time create the node connected to the registry + QRemoteObjectNode repNode(registryUrl); + + // Create the RemoteObjectSchemaHandler callback + QRemoteObjectNode::RemoteObjectSchemaHandler setupTcp = [&repNode](QUrl url) { + QTcpSocket *socket = new QTcpSocket(&repNode); + connect(socket, &QTcpSocket::connected, + [socket, &repNode]() { + repNode.addClientSideConnection(socket); + }); + connect(socket, QOverload<QAbstractSocket::SocketError>::of(&QSslSocket::error), + [socket](QAbstractSocket::SocketError error) { + delete socket; + }); + socket->connectToHost(url.host(), url.port()); + }; + + // Once we call registerExternalSchema, the above method will be called + // whenever the registry sees an object we are interested in on "exttcp" + repNode.registerExternalSchema(QStringLiteral("exttcp"), setupTcp); +\endcode +*/ diff --git a/src/remoteobjects/doc/src/remoteobjects-index.qdoc b/src/remoteobjects/doc/src/remoteobjects-index.qdoc index d483271..7b008bf 100644 --- a/src/remoteobjects/doc/src/remoteobjects-index.qdoc +++ b/src/remoteobjects/doc/src/remoteobjects-index.qdoc @@ -96,6 +96,7 @@ See \l{Qt Licensing} for further details. \li \l {Source Objects}{Qt Remote Objects Source Objects} \li \l {Replica Objects}{Qt Remote Objects Replica Objects} \li \l {Qt Remote Objects Registry} + \li \l {Qt Remote Objects - External QIODevices} \li \l {Qt Remote Objects Compiler} \li \l {Remote Object Interaction} \li \l {Using Qt Remote Objects} diff --git a/src/remoteobjects/qconnectionfactories.cpp b/src/remoteobjects/qconnectionfactories.cpp index 317d2fa..0ca2fa8 100644 --- a/src/remoteobjects/qconnectionfactories.cpp +++ b/src/remoteobjects/qconnectionfactories.cpp @@ -79,13 +79,14 @@ inline bool fromDataStream(QDataStream &in, QRemoteObjectPacketTypeEnum &type, Q case Ping: type = Ping; break; case Pong: type = Pong; break; default: - qCWarning(QT_REMOTEOBJECT_IO) << "Invalid packet received" << type; + qCWarning(QT_REMOTEOBJECT_IO) << "Invalid packet received" << _type; } if (type == Invalid) return false; if (type == ObjectList) return true; in >> name; + qCDebug(QT_REMOTEOBJECT_IO) << "Packet received of type" << type << "for object" << name; return true; } @@ -225,6 +226,41 @@ ServerIoDevice *QConnectionAbstractServer::nextPendingConnection() return iodevice; } +ExternalIoDevice::ExternalIoDevice(QIODevice *device, QObject *parent) + : IoDeviceBase(parent) + , m_device(device) +{ + initializeDataStream(); + connect(m_device, &QIODevice::aboutToClose, this, [this]() { this->m_isClosing = true; }); + connect(m_device, &QIODevice::readyRead, this, &ExternalIoDevice::readyRead); + auto meta = device->metaObject(); + if (-1 == meta->indexOfSignal(SIGNAL(disconnected()))) + connect(m_device, SIGNAL(disconnected()), this, SIGNAL(disconnected())); +} + +QIODevice *ExternalIoDevice::connection() const +{ + return m_device; +} + +bool ExternalIoDevice::isOpen() const +{ + if (!m_device) + return false; + return m_device->isOpen() && IoDeviceBase::isOpen(); +} + +void ExternalIoDevice::doClose() +{ + if (isOpen()) + m_device->close(); +} + +QString ExternalIoDevice::deviceType() const +{ + return QStringLiteral("ExternalIoDevice"); +} + /*! \class QtROServerFactory \inmodule QtRemoteObjects diff --git a/src/remoteobjects/qconnectionfactories_p.h b/src/remoteobjects/qconnectionfactories_p.h index 56283be..84bc917 100644 --- a/src/remoteobjects/qconnectionfactories_p.h +++ b/src/remoteobjects/qconnectionfactories_p.h @@ -53,6 +53,8 @@ #include <QAbstractSocket> #include <QDataStream> +#include <QIODevice> +#include <QPointer> #include <QtRemoteObjects/qtremoteobjectglobal.h> @@ -91,6 +93,7 @@ public: Q_SIGNALS: void readyRead(); + void disconnected(); protected: virtual QString deviceType() const = 0; @@ -111,9 +114,6 @@ class Q_REMOTEOBJECTS_EXPORT ServerIoDevice : public IoDeviceBase public: explicit ServerIoDevice(QObject *parent = nullptr); -Q_SIGNALS: - void disconnected(); - protected: QString deviceType() const override; }; @@ -156,7 +156,6 @@ public: QUrl url() const; Q_SIGNALS: - void disconnected(); void shouldReconnect(ClientIoDevice*); protected: @@ -169,6 +168,21 @@ private: QUrl m_url; }; +class ExternalIoDevice : public IoDeviceBase +{ + Q_OBJECT + +public: + explicit ExternalIoDevice(QIODevice *device, QObject *parent=nullptr); + QIODevice *connection() const override; + bool isOpen() const override; + +protected: + void doClose() override; + QString deviceType() const override; + QPointer<QIODevice> m_device; +}; + class QtROServerFactory { public: diff --git a/src/remoteobjects/qremoteobjectnode.cpp b/src/remoteobjects/qremoteobjectnode.cpp index 5173c45..2dd7086 100644 --- a/src/remoteobjects/qremoteobjectnode.cpp +++ b/src/remoteobjects/qremoteobjectnode.cpp @@ -163,6 +163,44 @@ void QRemoteObjectNode::setHeartbeatInterval(int interval) } /*! + \since 5.12 + \typedef QRemoteObjectNode::RemoteObjectSchemaHandler + + Typedef for a std::function method that can take a QUrl input and is + responsible for creating the communications channel between this node and + the node hosting the desired \l Source. As some types of QIODevices (e.g., + QSslSocket) require additional steps before the device is ready for use, + the method is responsible for calling \l addClientSideConnection once the + connection is fully established. +*/ + +/*! + \since 5.12 + \brief Provide a custom method to handle externally provided schemas + + This method is tied to the \l Registry and \l {External Schemas}. By + registering a std::function handler for an external schema, the registered + method will be called when the registry is notified of a \l Source you've + acquired being available. Without this registration, QtRO would only be + able to handle the "built-in" schemas. + + The provided method, \a handler, will be called when the registry sees a \l + Source object on a new (not yet connected) Node with a {QUrl::schema()} of + \a schema. The \a handler, of type \l + QRemoteObjectNode::RemoteObjectSchemaHandler will get the \l QUrl of the + Node providing the \l Source as an input parameter, and is responsible for + establishing the communications channel (a \l QIODevice of some sort) and + calling \l addClientSideConnection with it. + + \sa RemoteObjectSchemaHandler +*/ +void QRemoteObjectNode::registerExternalSchema(const QString &schema, QRemoteObjectNode::RemoteObjectSchemaHandler handler) +{ + Q_D(QRemoteObjectNode); + d->schemaHandlers.insert(schema, handler); +} + +/*! \since 5.11 \brief Forward Remote Objects from another network @@ -642,21 +680,26 @@ bool QRemoteObjectNodePrivate::initConnection(const QUrl &address) requestedUrls.insert(address); + if (schemaHandlers.contains(address.scheme())) { + schemaHandlers[address.scheme()](address); + return true; + } + ClientIoDevice *connection = QtROClientFactory::instance()->create(address, q); if (!connection) { qROPrivWarning() << "Could not create ClientIoDevice for client. Invalid url/scheme provided?" << address; return false; } - qROPrivDebug() << "Opening connection to" << address.toString(); qROPrivDebug() << "Replica Connection isValid" << connection->isOpen(); QObject::connect(connection, &ClientIoDevice::shouldReconnect, q, [this, connection]() { onShouldReconnect(connection); }); - QObject::connect(connection, &ClientIoDevice::readyRead, q, [this, connection]() { + QObject::connect(connection, &IoDeviceBase::readyRead, q, [this, connection]() { onClientRead(connection); }); connection->connectToServer(); + return true; } @@ -791,7 +834,7 @@ void QRemoteObjectNodePrivate::handleReplicaConnection(const QString &name) } } -void QRemoteObjectNodePrivate::handleReplicaConnection(const QByteArray &sourceSignature, QConnectedReplicaImplementation *rep, ClientIoDevice *connection) +void QRemoteObjectNodePrivate::handleReplicaConnection(const QByteArray &sourceSignature, QConnectedReplicaImplementation *rep, IoDeviceBase *connection) { if (!checkSignatures(rep->m_objectSignature, sourceSignature)) { qROPrivWarning() << "Signature mismatch for" << rep->m_metaObject->className() << (rep->m_objectName.isEmpty() ? QLatin1String("(unnamed)") : rep->m_objectName); @@ -820,7 +863,7 @@ QReplicaImplementationInterface *QRemoteObjectHostBasePrivate::handleNewAcquire( void QRemoteObjectNodePrivate::onClientRead(QObject *obj) { using namespace QRemoteObjectPackets; - ClientIoDevice *connection = qobject_cast<ClientIoDevice*>(obj); + IoDeviceBase *connection = qobject_cast<IoDeviceBase*>(obj); QRemoteObjectPacketTypeEnum packetType; Q_ASSERT(connection); @@ -913,7 +956,7 @@ void QRemoteObjectNodePrivate::onClientRead(QObject *obj) qROPrivDebug() << "RemoveObject-->" << rxName << this; connectedSources.remove(rxName); connection->removeSource(rxName); - if (replicas.contains(rxName)) { //We have a replica waiting on this remoteObject + if (replicas.contains(rxName)) { //We have a replica using the removed source QSharedPointer<QConnectedReplicaImplementation> rep = qSharedPointerCast<QConnectedReplicaImplementation>(replicas.value(rxName).toStrongRef()); if (rep && !rep->connectionToSource.isNull()) { rep->connectionToSource.clear(); @@ -1105,6 +1148,26 @@ void QRemoteObjectNodePrivate::onClientRead(QObject *obj) */ /*! + \enum QRemoteObjectHostBase::AllowedSchemas + + This enum is used to specify whether a Node will accept a url with an + unrecognized schema for the hostUrl. By default only urls with known + schemas are accepted, but using \c AllowExternalRegistration will enable + the \l Registry to pass your external (to QtRO) url to client Nodes. + + \value BuiltInSchemasOnly Only allow the hostUrl to be set to a QtRO + supported schema. This is the default value, and causes a Node error to be + set if an unrecognized schema is provided. + \value AllowExternalRegistration The provided schema is registered as an + \l {External Schemas} {External Schema} + + \sa QRemoteObjectHost(const QUrl &address, const QUrl ®istryAddress = + QUrl(), AllowedSchemas allowedSchemas=BuiltInSchemasOnly, QObject *parent = + nullptr), setHostUrl(const QUrl &hostAddress, AllowedSchemas + allowedSchemas=BuiltInSchemasOnly) +*/ + +/*! \fn ObjectType *QRemoteObjectNode::acquire(const QString &name) Returns a pointer to a Replica of type ObjectType (which is a template @@ -1217,15 +1280,20 @@ QRemoteObjectHost::QRemoteObjectHost(QObject *parent) Constructs a new QRemoteObjectHost Node (i.e., a Node that supports exposing \l Source objects on the QtRO network) with address \a address. If set, \a registryAddress will be used to connect to the \l - QRemoteObjectRegistry at the provided address. + QRemoteObjectRegistry at the provided address. The \a allowedSchemas + parameter is only needed (and should be set to \l + {QRemoteObjectHostBase::AllowExternalRegistration} + {AllowExternalRegistration}) if the schema of the url should be used as an + \l {External Schemas} {External Schema} by the registry. \sa setHostUrl(), setRegistryUrl() */ -QRemoteObjectHost::QRemoteObjectHost(const QUrl &address, const QUrl ®istryAddress, QObject *parent) +QRemoteObjectHost::QRemoteObjectHost(const QUrl &address, const QUrl ®istryAddress, + AllowedSchemas allowedSchemas, QObject *parent) : QRemoteObjectHostBase(*new QRemoteObjectHostPrivate, parent) { if (!address.isEmpty()) { - if (!setHostUrl(address)) + if (!setHostUrl(address, allowedSchemas)) return; } @@ -1325,7 +1393,7 @@ QUrl QRemoteObjectHostBase::hostUrl() const \internal The HostBase version of this method is protected so the method isn't exposed on RegistryHost nodes. */ -bool QRemoteObjectHostBase::setHostUrl(const QUrl &hostAddress) +bool QRemoteObjectHostBase::setHostUrl(const QUrl &hostAddress, AllowedSchemas allowedSchemas) { Q_D(QRemoteObjectHostBase); if (d->remoteObjectIo) { @@ -1333,13 +1401,11 @@ bool QRemoteObjectHostBase::setHostUrl(const QUrl &hostAddress) return false; } - d->remoteObjectIo = new QRemoteObjectSourceIo(hostAddress, this); - if (d->remoteObjectIo->m_server.isNull()) { //Invalid url/scheme + if (allowedSchemas == AllowedSchemas::BuiltInSchemasOnly && !QtROServerFactory::instance()->isValid(hostAddress)) { d->setLastError(HostUrlInvalid); - delete d->remoteObjectIo; - d->remoteObjectIo = 0; return false; } + d->remoteObjectIo = new QRemoteObjectSourceIo(hostAddress, this); //If we've given a name to the node, set it on the sourceIo as well if (!objectName().isEmpty()) @@ -1368,10 +1434,15 @@ QUrl QRemoteObjectHost::hostUrl() const Sets the \a hostAddress for a host QRemoteObjectNode. Returns \c true if the Host address is set, otherwise \c false. + + The \a allowedSchemas parameter is only needed (and should be set to \l + {QRemoteObjectHostBase::AllowExternalRegistration} + {AllowExternalRegistration}) if the schema of the url should be used as an + \l {External Schemas} {External Schema} by the registry. */ -bool QRemoteObjectHost::setHostUrl(const QUrl &hostAddress) +bool QRemoteObjectHost::setHostUrl(const QUrl &hostAddress, AllowedSchemas allowedSchemas) { - return QRemoteObjectHostBase::setHostUrl(hostAddress); + return QRemoteObjectHostBase::setHostUrl(hostAddress, allowedSchemas); } /*! @@ -1568,6 +1639,30 @@ bool QRemoteObjectNode::connectToNode(const QUrl &address) } /*! + \since 5.12 + + In order to \l QRemoteObjectNode::acquire() \l Replica objects over \l + {External QIODevices}, Qt Remote Objects needs access to the communications + channel (a \l QIODEvice) between the respective nodes. It is the + addClientSideConnection() call that enables this, taking the \a ioDevice as + input. Any acquire() call made without calling addClientSideConnection will + still work, but the Node will not be able to initialize the \l Replica + without being provided the connection to the Host node. + + \sa {QRemoteObjectHostBase::addHostSideConnection} +*/ +void QRemoteObjectNode::addClientSideConnection(QIODevice *ioDevice) +{ + Q_D(QRemoteObjectNode); + ExternalIoDevice *device = new ExternalIoDevice(ioDevice, this); + connect(device, &IoDeviceBase::readyRead, this, [d, device]() { + d->onClientRead(device); + }); + if (device->bytesAvailable()) + d->onClientRead(device); +} + +/*! \fn void QRemoteObjectNode::remoteObjectAdded(const QRemoteObjectSourceLocation &loc) This signal is emitted whenever a new \l {Source} object is added to @@ -1784,6 +1879,29 @@ bool QRemoteObjectHostBase::disableRemoting(QObject *remoteObject) } /*! + \since 5.12 + + In order to \l QRemoteObjectHost::enableRemoting() \l Source objects over + \l {External QIODevices}, Qt Remote Objects needs access to the + communications channel (a \l QIODEvice) between the respective nodes. It is + the addHostSideConnection() call that enables this on the \l Source side, + taking the \a ioDevice as input. Any enableRemoting() call will still work + without calling addHostSideConnection, but the Node will not be able to + share the \l Source objects without being provided the connection to the + Replica node. + + \sa addClientSideConnection +*/ +void QRemoteObjectHostBase::addHostSideConnection(QIODevice *ioDevice) +{ + Q_D(QRemoteObjectHostBase); + if (!d->remoteObjectIo) + d->remoteObjectIo = new QRemoteObjectSourceIo(this); + ExternalIoDevice *device = new ExternalIoDevice(ioDevice, this); + return d->remoteObjectIo->newConnection(device); +} + +/*! Returns a pointer to a Replica which is specifically derived from \l QAbstractItemModel. The \a name provided must match the name used with the matching \l {QRemoteObjectHostBase::enableRemoting} {enableRemoting} that put diff --git a/src/remoteobjects/qremoteobjectnode.h b/src/remoteobjects/qremoteobjectnode.h index a8c4d8f..35d0d9d 100644 --- a/src/remoteobjects/qremoteobjectnode.h +++ b/src/remoteobjects/qremoteobjectnode.h @@ -105,6 +105,7 @@ public: ~QRemoteObjectNode() override; Q_INVOKABLE bool connectToNode(const QUrl &address); + void addClientSideConnection(QIODevice *ioDevice); virtual void setName(const QString &name); template < class ObjectType > ObjectType *acquire(const QString &name = QString()) @@ -140,6 +141,9 @@ public: int heartbeatInterval() const; void setHeartbeatInterval(int interval); + typedef std::function<void (QUrl)> RemoteObjectSchemaHandler; + void registerExternalSchema(const QString &schema, RemoteObjectSchemaHandler handler); + Q_SIGNALS: void remoteObjectAdded(const QRemoteObjectSourceLocation &); void remoteObjectRemoved(const QRemoteObjectSourceLocation &); @@ -165,6 +169,8 @@ class Q_REMOTEOBJECTS_EXPORT QRemoteObjectHostBase : public QRemoteObjectNode { Q_OBJECT public: + enum AllowedSchemas { BuiltInSchemasOnly, AllowExternalRegistration }; + Q_ENUM(AllowedSchemas) ~QRemoteObjectHostBase() override; void setName(const QString &name) override; @@ -177,6 +183,7 @@ public: bool enableRemoting(QObject *object, const QString &name = QString()); bool enableRemoting(QAbstractItemModel *model, const QString &name, const QVector<int> roles, QItemSelectionModel *selectionModel = nullptr); bool disableRemoting(QObject *remoteObject); + void addHostSideConnection(QIODevice *ioDevice); typedef std::function<bool(const QString &, const QString &)> RemoteObjectNameFilter; bool proxy(const QUrl ®istryUrl, const QUrl &hostUrl={}, @@ -185,7 +192,7 @@ public: protected: virtual QUrl hostUrl() const; - virtual bool setHostUrl(const QUrl &hostAddress); + virtual bool setHostUrl(const QUrl &hostAddress, AllowedSchemas allowedSchemas=BuiltInSchemasOnly); QRemoteObjectHostBase(QRemoteObjectHostBasePrivate &, QObject *); private: @@ -198,11 +205,12 @@ class Q_REMOTEOBJECTS_EXPORT QRemoteObjectHost : public QRemoteObjectHostBase Q_OBJECT public: QRemoteObjectHost(QObject *parent = nullptr); - QRemoteObjectHost(const QUrl &address, const QUrl ®istryAddress = QUrl(), QObject *parent = nullptr); + QRemoteObjectHost(const QUrl &address, const QUrl ®istryAddress = QUrl(), + AllowedSchemas allowedSchemas=BuiltInSchemasOnly, QObject *parent = nullptr); QRemoteObjectHost(const QUrl &address, QObject *parent); ~QRemoteObjectHost() override; QUrl hostUrl() const override; - bool setHostUrl(const QUrl &hostAddress) override; + bool setHostUrl(const QUrl &hostAddress, AllowedSchemas allowedSchemas=BuiltInSchemasOnly) override; protected: QRemoteObjectHost(QRemoteObjectHostPrivate &, QObject *); diff --git a/src/remoteobjects/qremoteobjectnode_p.h b/src/remoteobjects/qremoteobjectnode_p.h index efcf936..1a8ebb6 100644 --- a/src/remoteobjects/qremoteobjectnode_p.h +++ b/src/remoteobjects/qremoteobjectnode_p.h @@ -154,7 +154,7 @@ public: virtual QReplicaImplementationInterface *handleNewAcquire(const QMetaObject *meta, QRemoteObjectReplica *instance, const QString &name); void handleReplicaConnection(const QString &name); - void handleReplicaConnection(const QByteArray &sourceSignature, QConnectedReplicaImplementation *rep, ClientIoDevice *connection); + void handleReplicaConnection(const QByteArray &sourceSignature, QConnectedReplicaImplementation *rep, IoDeviceBase *connection); void initialize(); private: bool checkSignatures(const QByteArray &a, const QByteArray &b); @@ -162,7 +162,7 @@ private: public: struct SourceInfo { - ClientIoDevice* device; + IoDeviceBase* device; QString typeName; QByteArray objectSignature; }; @@ -171,6 +171,7 @@ public: QUrl registryAddress; QHash<QString, QWeakPointer<QReplicaImplementationInterface> > replicas; QMap<QString, SourceInfo> connectedSources; + QMap<QString, QRemoteObjectNode::RemoteObjectSchemaHandler> schemaHandlers; QSet<ClientIoDevice*> pendingReconnect; QSet<QUrl> requestedUrls; QRemoteObjectRegistry *registry; diff --git a/src/remoteobjects/qremoteobjectreplica.cpp b/src/remoteobjects/qremoteobjectreplica.cpp index 6cfec3c..dc2f926 100644 --- a/src/remoteobjects/qremoteobjectreplica.cpp +++ b/src/remoteobjects/qremoteobjectreplica.cpp @@ -104,16 +104,23 @@ QConnectedReplicaImplementation::QConnectedReplicaImplementation(const QString & m_heartbeatTimer.start(); }); connect(&m_heartbeatTimer, &QTimer::timeout, this, [this] { + // TODO: Revisit if a baseclass method can be used to avoid specialized cast + // conditional logic. + auto clientIo = qobject_cast<ClientIoDevice *>(connectionToSource); if (m_pendingCalls.contains(0)) { // The source didn't respond in time, disconnect the connection - if (connectionToSource) - connectionToSource->disconnectFromServer(); + if (clientIo) + clientIo->disconnectFromServer(); + else if (connectionToSource) + connectionToSource->close(); } else { serializePingPacket(m_packet, m_objectName); if (sendCommandWithReply(0).d->serialId == -1) { m_heartbeatTimer.stop(); - if (connectionToSource) - connectionToSource->disconnectFromServer(); + if (clientIo) + clientIo->disconnectFromServer(); + else if (connectionToSource) + connectionToSource->close(); } } }); @@ -449,7 +456,7 @@ void QConnectedReplicaImplementation::setProperty(int i, const QVariant &prop) m_propertyStorage[i] = prop; } -void QConnectedReplicaImplementation::setConnection(ClientIoDevice *conn) +void QConnectedReplicaImplementation::setConnection(IoDeviceBase *conn) { if (connectionToSource.isNull()) { connectionToSource = conn; diff --git a/src/remoteobjects/qremoteobjectreplica_p.h b/src/remoteobjects/qremoteobjectreplica_p.h index 6b64b67..471964c 100644 --- a/src/remoteobjects/qremoteobjectreplica_p.h +++ b/src/remoteobjects/qremoteobjectreplica_p.h @@ -67,7 +67,7 @@ QT_BEGIN_NAMESPACE class QRemoteObjectReplica; class QRemoteObjectSource; -class ClientIoDevice; +class IoDeviceBase; class QReplicaImplementationInterface { @@ -165,7 +165,7 @@ public: QRemoteObjectPendingCall sendCommandWithReply(int serialId); bool waitForFinished(const QRemoteObjectPendingCall &call, int timeout) override; void notifyAboutReply(int ackedSerialId, const QVariant &value) override; - void setConnection(ClientIoDevice *conn); + void setConnection(IoDeviceBase *conn); void setDisconnected(); void _q_send(QMetaObject::Call call, int index, const QVariantList &args) override; @@ -176,7 +176,7 @@ public: QVector<QRemoteObjectReplica *> m_parentsNeedingConnect; QVariantList m_propertyStorage; QVector<int> m_childIndices; - QPointer<ClientIoDevice> connectionToSource; + QPointer<IoDeviceBase> connectionToSource; // pending call data int m_curSerialId = 1; // 0 is reserved for heartbeat signals diff --git a/src/remoteobjects/qremoteobjectsource.cpp b/src/remoteobjects/qremoteobjectsource.cpp index 5c9f7f9..36bee20 100644 --- a/src/remoteobjects/qremoteobjectsource.cpp +++ b/src/remoteobjects/qremoteobjectsource.cpp @@ -228,7 +228,7 @@ QRemoteObjectRootSource::~QRemoteObjectRootSource() delete it; } d->m_sourceIo->unregisterSource(this); - Q_FOREACH (ServerIoDevice *io, d->m_listeners) { + Q_FOREACH (IoDeviceBase *io, d->m_listeners) { removeListener(io, true); } delete d; @@ -323,11 +323,11 @@ void QRemoteObjectSourceBase::handleMetaCall(int index, QMetaObject::Call call, serializeInvokePacket(d->m_packet, name(), call, index, *marshalArgs(index, a), -1, propertyIndex); d->m_packet.baseAddress = 0; - Q_FOREACH (ServerIoDevice *io, d->m_listeners) + Q_FOREACH (IoDeviceBase *io, d->m_listeners) io->write(d->m_packet.array, d->m_packet.size); } -void QRemoteObjectRootSource::addListener(ServerIoDevice *io, bool dynamic) +void QRemoteObjectRootSource::addListener(IoDeviceBase *io, bool dynamic) { d->m_listeners.append(io); d->isDynamic = dynamic; @@ -347,7 +347,7 @@ void QRemoteObjectRootSource::addListener(ServerIoDevice *io, bool dynamic) d->isDynamic = false; } -int QRemoteObjectRootSource::removeListener(ServerIoDevice *io, bool shouldSendRemove) +int QRemoteObjectRootSource::removeListener(IoDeviceBase *io, bool shouldSendRemove) { d->m_listeners.removeAll(io); if (shouldSendRemove) diff --git a/src/remoteobjects/qremoteobjectsource_p.h b/src/remoteobjects/qremoteobjectsource_p.h index 86c1c5e..2961e5c 100644 --- a/src/remoteobjects/qremoteobjectsource_p.h +++ b/src/remoteobjects/qremoteobjectsource_p.h @@ -62,7 +62,7 @@ QT_BEGIN_NAMESPACE class QRemoteObjectSourceIo; -class ServerIoDevice; +class IoDeviceBase; class QRemoteObjectSourceBase : public QObject { @@ -87,7 +87,7 @@ public: struct Private { Private(QRemoteObjectSourceIo *io) : m_sourceIo(io), isDynamic(false) {} QRemoteObjectSourceIo *m_sourceIo; - QVector<ServerIoDevice*> m_listeners; + QVector<IoDeviceBase*> m_listeners; QRemoteObjectPackets::DataStreamPacket m_packet; // Types needed during recursively sending a root to a new listener @@ -122,8 +122,8 @@ public: bool isRoot() const override { return true; } QString name() const override { return m_name; } - void addListener(ServerIoDevice *io, bool dynamic = false); - int removeListener(ServerIoDevice *io, bool shouldSendRemove = false); + void addListener(IoDeviceBase *io, bool dynamic = false); + int removeListener(IoDeviceBase *io, bool shouldSendRemove = false); QString m_name; }; diff --git a/src/remoteobjects/qremoteobjectsourceio.cpp b/src/remoteobjects/qremoteobjectsourceio.cpp index 2ba2043..d864ed5 100644 --- a/src/remoteobjects/qremoteobjectsourceio.cpp +++ b/src/remoteobjects/qremoteobjectsourceio.cpp @@ -52,22 +52,31 @@ using namespace QtRemoteObjects; QRemoteObjectSourceIo::QRemoteObjectSourceIo(const QUrl &address, QObject *parent) : QObject(parent) - , m_server(QtROServerFactory::instance()->create(address, this)) + , m_server(QtROServerFactory::instance()->isValid(address) ? + QtROServerFactory::instance()->create(address, this) : nullptr) { if (m_server && m_server->listen(address)) { qRODebug(this) << "QRemoteObjectSourceIo is Listening" << address; } else { - qROWarning(this) << "Listen failed for URL:" << address; - if (m_server) + if (m_server) { + qROWarning(this) << "Listen failed for URL:" << address; qROWarning(this) << m_server->serverError(); - else - qROWarning(this) << "Most likely an unrecognized scheme was used."; + } else { + m_address = address; + qRODebug(this) << "Using" << address << "as external url."; + } return; } connect(m_server.data(), &QConnectionAbstractServer::newConnection, this, &QRemoteObjectSourceIo::handleConnection); } +QRemoteObjectSourceIo::QRemoteObjectSourceIo(QObject *parent) + : QObject(parent) + , m_server(nullptr) +{ +} + QRemoteObjectSourceIo::~QRemoteObjectSourceIo() { qDeleteAll(m_sourceRoots.values()); @@ -93,7 +102,7 @@ bool QRemoteObjectSourceIo::enableRemoting(QObject *object, const SourceApiMap * new QRemoteObjectRootSource(object, api, adapter, this); QRemoteObjectPackets::serializeObjectListPacket(m_packet, {QRemoteObjectPackets::ObjectInfo{api->name(), api->typeName(), api->objectSignature()}}); - foreach (ServerIoDevice *conn, m_connections) + 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"; @@ -120,8 +129,10 @@ void QRemoteObjectSourceIo::registerSource(QRemoteObjectSourceBase *source) qRODebug(this) << "Registering" << name; m_sourceRoots[name] = root; m_objectToSourceMap[source->m_object] = root; - const auto &type = source->m_api->typeName(); - emit remoteObjectAdded(qMakePair(name, QRemoteObjectSourceLocationInfo(type, serverAddress()))); + if (serverAddress().isValid()) { + const auto &type = source->m_api->typeName(); + emit remoteObjectAdded(qMakePair(name, QRemoteObjectSourceLocationInfo(type, serverAddress()))); + } } } @@ -134,13 +145,14 @@ void QRemoteObjectSourceIo::unregisterSource(QRemoteObjectSourceBase *source) const auto type = source->m_api->typeName(); m_objectToSourceMap.remove(source->m_object); m_sourceRoots.remove(name); - emit remoteObjectRemoved(qMakePair(name, QRemoteObjectSourceLocationInfo(type, serverAddress()))); + if (serverAddress().isValid()) + emit remoteObjectRemoved(qMakePair(name, QRemoteObjectSourceLocationInfo(type, serverAddress()))); } } void QRemoteObjectSourceIo::onServerDisconnect(QObject *conn) { - ServerIoDevice *connection = qobject_cast<ServerIoDevice*>(conn); + IoDeviceBase *connection = qobject_cast<IoDeviceBase*>(conn); m_connections.remove(connection); qRODebug(this) << "OnServerDisconnect"; @@ -158,7 +170,7 @@ void QRemoteObjectSourceIo::onServerDisconnect(QObject *conn) void QRemoteObjectSourceIo::onServerRead(QObject *conn) { // Assert the invariant here conn is of type QIODevice - ServerIoDevice *connection = qobject_cast<ServerIoDevice*>(conn); + IoDeviceBase *connection = qobject_cast<IoDeviceBase*>(conn); QRemoteObjectPacketTypeEnum packetType; do { @@ -258,13 +270,18 @@ 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, &ServerIoDevice::disconnected, this, [this, conn]() { - onServerDisconnect(conn); - }); - connect(conn, &ServerIoDevice::readyRead, this, [this, 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); @@ -280,7 +297,9 @@ void QRemoteObjectSourceIo::handleConnection() QUrl QRemoteObjectSourceIo::serverAddress() const { - return m_server->address(); + if (m_server) + return m_server->address(); + return m_address; } QT_END_NAMESPACE diff --git a/src/remoteobjects/qremoteobjectsourceio_p.h b/src/remoteobjects/qremoteobjectsourceio_p.h index c9adde7..4cc045c 100644 --- a/src/remoteobjects/qremoteobjectsourceio_p.h +++ b/src/remoteobjects/qremoteobjectsourceio_p.h @@ -70,12 +70,14 @@ class QRemoteObjectSourceIo : public QObject Q_OBJECT public: explicit QRemoteObjectSourceIo(const QUrl &address, QObject *parent = nullptr); + explicit QRemoteObjectSourceIo(QObject *parent = nullptr); ~QRemoteObjectSourceIo() override; bool enableRemoting(QObject *object, const QMetaObject *meta, const QString &name, const QString &typeName); bool enableRemoting(QObject *object, const SourceApiMap *api, QObject *adapter = nullptr); bool disableRemoting(QObject *object); + void newConnection(IoDeviceBase *conn); QUrl serverAddress() const; @@ -94,15 +96,16 @@ public: void unregisterSource(QRemoteObjectSourceBase *source); QHash<QIODevice*, quint32> m_readSize; - QSet<ServerIoDevice*> m_connections; + QSet<IoDeviceBase*> m_connections; QHash<QObject *, QRemoteObjectRootSource*> m_objectToSourceMap; QMap<QString, QRemoteObjectSourceBase*> m_sourceObjects; QMap<QString, QRemoteObjectRootSource*> m_sourceRoots; - QHash<ServerIoDevice*, QUrl> m_registryMapping; + QHash<IoDeviceBase*, QUrl> m_registryMapping; QScopedPointer<QConnectionAbstractServer> m_server; QRemoteObjectPackets::DataStreamPacket m_packet; QString m_rxName; QVariantList m_rxArgs; + QUrl m_address; }; QT_END_NAMESPACE diff --git a/src/remoteobjects/qtremoteobjectglobal.h b/src/remoteobjects/qtremoteobjectglobal.h index 7e6dfb9..eb23b44 100644 --- a/src/remoteobjects/qtremoteobjectglobal.h +++ b/src/remoteobjects/qtremoteobjectglobal.h @@ -173,6 +173,7 @@ enum QRemoteObjectPacketTypeEnum Ping, Pong }; +Q_ENUM_NS(QRemoteObjectPacketTypeEnum) enum InitialAction { FetchRootSize, diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro index c5f7abc..1627198 100644 --- a/tests/auto/auto.pro +++ b/tests/auto/auto.pro @@ -22,4 +22,4 @@ SUBDIRS += \ sub_localsockettestserver qtHaveModule(qml): SUBDIRS += qml -qtConfig(process): SUBDIRS += integration_multiprocess +qtConfig(process): SUBDIRS += integration_multiprocess integration_external diff --git a/tests/auto/integration/tst_integration.cpp b/tests/auto/integration/tst_integration.cpp index d89e537..1eb7604 100644 --- a/tests/auto/integration/tst_integration.cpp +++ b/tests/auto/integration/tst_integration.cpp @@ -32,11 +32,12 @@ #include <QMetaType> #include <QProcess> #include <QFileInfo> +#include <QTcpServer> +#include <QTcpSocket> #include <QRemoteObjectReplica> #include <QRemoteObjectNode> #include <QRemoteObjectSettingsStore> - #include "engine.h" #include "speedometer.h" #include "rep_engine_replica.h" @@ -136,6 +137,21 @@ class tst_Integration: public QObject { Q_OBJECT + void setupTcp() + { + if (!tcpServer) { + tcpServer = new QTcpServer; + tcpServer->listen(QHostAddress::Any, 65511); + socketClient = new QTcpSocket; + socketClient->connectToHost(QHostAddress::LocalHost, tcpServer->serverPort()); + QVERIFY(socketClient->waitForConnected(5000)); + + QVERIFY(tcpServer->waitForNewConnection(5000)); + QVERIFY(tcpServer->hasPendingConnections()); + socketServer = tcpServer->nextPendingConnection(); + } + } + void setupHost(bool useRegistry=false) { QFETCH_GLOBAL(QUrl, hostUrl); @@ -146,6 +162,9 @@ class tst_Integration: public QObject host->setHostUrl(hostUrl); if (useRegistry) host->setRegistryUrl(registryUrl); + } else { + setupTcp(); + host->addHostSideConnection(socketServer); } } @@ -153,16 +172,19 @@ class tst_Integration: public QObject { QFETCH_GLOBAL(QUrl, hostUrl); QFETCH_GLOBAL(QUrl, registryUrl); + client = new QRemoteObjectNode; + Q_SET_OBJECT_NAME(*client); if (!hostUrl.isEmpty()) { if (useRegistry) - client = new QRemoteObjectNode(registryUrl); + client->setRegistryUrl(registryUrl); else { - client = new QRemoteObjectNode; client->connectToNode(hostUrl); } + } else { + setupTcp(); + client->addClientSideConnection(socketClient); } - Q_SET_OBJECT_NAME(*client); } void setupRegistry() @@ -179,16 +201,20 @@ private: QRemoteObjectHost *host; QRemoteObjectNode *client; QRemoteObjectRegistryHost *registry; + QTcpServer *tcpServer; + QPointer<QTcpSocket> socketClient, socketServer; private slots: void initTestCase_data() { QTest::addColumn<QUrl>("hostUrl"); QTest::addColumn<QUrl>("registryUrl"); + #ifndef SKIP_LOCAL QTest::newRow("local") << QUrl(QLatin1String("local:replica_local_integration")) << QUrl(QLatin1String("local:registry_local_integration")); #endif QTest::newRow("tcp") << QUrl(QLatin1String("tcp://127.0.0.1:65511")) << QUrl(QLatin1String("tcp://127.0.0.1:65512")); + QTest::newRow("external") << QUrl() << QUrl(); #ifdef __QNXNTO__ QTest::newRow("qnx") << QUrl(QLatin1String("qnx:replica")) << QUrl(QLatin1String("qnx:registry")); #endif @@ -208,6 +234,9 @@ private slots: registry = nullptr; host = nullptr; client = nullptr; + tcpServer = nullptr; + socketClient = nullptr; + socketServer = nullptr; } void cleanup() @@ -215,6 +244,13 @@ private slots: delete registry; delete host; delete client; + delete tcpServer; + if (socketClient) { + socketClient->deleteLater(); + } + if (socketServer) { + socketServer->deleteLater(); + } // wait for delivery of RemoveObject events to the source QTest::qWait(200); } @@ -424,6 +460,9 @@ private slots: void registryAddedTest() { + QFETCH_GLOBAL(QUrl, registryUrl); + if (registryUrl.isEmpty()) + QSKIP("Skipping registry tests for external QIODevice types."); setupRegistry(); setupHost(true); @@ -489,6 +528,9 @@ private slots: void registryTest() { + QFETCH_GLOBAL(QUrl, registryUrl); + if (registryUrl.isEmpty()) + QSKIP("Skipping registry tests for external QIODevice types."); setupRegistry(); TcpDataCenterSimpleSource source1; source1.setData1(5); @@ -558,7 +600,9 @@ private slots: void noRegistryTest() { - setupHost(true); + QFETCH_GLOBAL(QUrl, registryUrl); + if (registryUrl.isEmpty()) + QSKIP("Skipping registry tests for external QIODevice types."); setupHost(true); const bool res = host->waitForRegistry(3000); QVERIFY(!res); QCOMPARE(host->registry()->isInitialized(), false); @@ -571,6 +615,8 @@ private slots: { QFETCH_GLOBAL(QUrl, hostUrl); QFETCH_GLOBAL(QUrl, registryUrl); + if (registryUrl.isEmpty()) + QSKIP("Skipping registry tests for external QIODevice types."); setupClient(true); // create a replica before the registry host started @@ -1219,10 +1265,13 @@ private slots: void SchemeTest() { QFETCH_GLOBAL(QUrl, hostUrl); + QFETCH_GLOBAL(QUrl, registryUrl); QRemoteObjectHost valid(hostUrl); QVERIFY(valid.lastError() == QRemoteObjectNode::NoError); QRemoteObjectHost invalid(QUrl(QLatin1String("invalid:invalid"))); QVERIFY(invalid.lastError() == QRemoteObjectNode::HostUrlInvalid); + QRemoteObjectHost validExternal(QUrl(QLatin1String("invalid:invalid")), registryUrl, QRemoteObjectHost::AllowExternalRegistration); + QVERIFY(validExternal.lastError() == QRemoteObjectNode::NoError); QRemoteObjectNode invalidRegistry(QUrl(QLatin1String("invalid:invalid"))); QVERIFY(invalidRegistry.lastError() == QRemoteObjectNode::RegistryNotAcquired); } diff --git a/tests/auto/integration_external/MyInterface.rep b/tests/auto/integration_external/MyInterface.rep new file mode 100644 index 0000000..dfb8752 --- /dev/null +++ b/tests/auto/integration_external/MyInterface.rep @@ -0,0 +1,18 @@ +#include <QtCore> + +class MyInterface +{ + ENUM Enum1 { First, Second, Third } + PROP(Enum1 enum1 = First READWRITE) + + PROP(bool started = false) + + SLOT(bool start()) + SLOT(bool stop()) + SLOT(bool quit()) + SLOT(bool next()) + SLOT(void testEnumParamsInSlots(Enum1 enumSlotParam, bool slotParam2, int)) + + SIGNAL(advance()) + SIGNAL(testEnumParamsInSignals(Enum1 enumSignalParam, bool signalParam2, QString)) +}; diff --git a/tests/auto/integration_external/client/client.pro b/tests/auto/integration_external/client/client.pro new file mode 100644 index 0000000..4c42f5c --- /dev/null +++ b/tests/auto/integration_external/client/client.pro @@ -0,0 +1,16 @@ +TEMPLATE = app +QT += remoteobjects core testlib +QT -= gui + +TARGET = client +DESTDIR = ./ +CONFIG += c++11 +CONFIG -= app_bundle + +REPC_REPLICA = ../MyInterface.rep + +SOURCES += main.cpp \ + +HEADERS += \ + +INCLUDEPATH += $$PWD diff --git a/tests/auto/integration_external/client/main.cpp b/tests/auto/integration_external/client/main.cpp new file mode 100644 index 0000000..05f7f59 --- /dev/null +++ b/tests/auto/integration_external/client/main.cpp @@ -0,0 +1,202 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Ford Motor Company +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtRemoteObjects module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "rep_MyInterface_replica.h" + +#include <QCoreApplication> +#include <QtRemoteObjects/qremoteobjectnode.h> +#include <QtTest/QtTest> + +const QUrl registryUrl = QUrl(QStringLiteral("tcp://127.0.0.1:65212")); + +class tst_Client_Process : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase() + { + m_repNode.setRegistryUrl(registryUrl); + QRemoteObjectNode::RemoteObjectSchemaHandler setupTcp = [this](QUrl url) { + QTcpSocket *socket = new QTcpSocket(&this->m_repNode); + connect(socket, &QTcpSocket::connected, + [socket, this]() { + this->m_repNode.addClientSideConnection(socket); + }); + connect(socket, QOverload<QAbstractSocket::SocketError>::of(&QSslSocket::error), + [socket](QAbstractSocket::SocketError error) { + qDebug() << "SocketError" << error; + delete socket; + }); + socket->connectToHost(url.host(), url.port()); + }; + m_repNode.registerExternalSchema(QStringLiteral("exttcp"), setupTcp); + QVERIFY(m_repNode.waitForRegistry(3000)); + m_rep.reset(m_repNode.acquire<MyInterfaceReplica>()); + } + + void testRun() + { + + QVERIFY(m_rep->waitForSource()); + auto reply = m_rep->start(); + QVERIFY(reply.waitForFinished()); + + // BEGIN: Testing + QSignalSpy advanceSpy(m_rep.data(), SIGNAL(advance())); + + QSignalSpy spy(m_rep.data(), SIGNAL(enum1Changed(MyInterfaceReplica::Enum1))); + QVERIFY(advanceSpy.wait()); + + QCOMPARE(spy.count(), 2); + // END: Testing + + reply = m_rep->stop(); + QVERIFY(reply.waitForFinished()); + } + + void testEnumDetails() + { + QHash<QByteArray, int> kvs = {{"First", 0}, {"Second", 1}, {"Third", 2}}; + QScopedPointer<QRemoteObjectDynamicReplica> rep(m_repNode.acquireDynamic("MyInterface")); + QVERIFY(rep->waitForSource()); + + auto mo = rep->metaObject(); + int enumIdx = mo->indexOfEnumerator("Enum1"); + QVERIFY(enumIdx != -1); + auto enumerator = mo->enumerator(enumIdx); + QCOMPARE(enumerator.name(), "Enum1"); + QCOMPARE(enumerator.keyCount(), 3); + for (int i = 0; i < 3; ++i) { + auto key = enumerator.key(i); + auto val = enumerator.value(i); + auto it = kvs.find(key); + QVERIFY(it != kvs.end()); + QCOMPARE(*it, val); + kvs.erase(it); + } + + int propIdx = mo->indexOfProperty("enum1"); + QVERIFY(propIdx != -1); + auto property = mo->property(propIdx); + property.write(rep.data(), 1); + QTRY_COMPARE(property.read(rep.data()).toInt(), 1); + } + + void testMethodSignalParamDetails() + { + QScopedPointer<QRemoteObjectDynamicReplica> rep(m_repNode.acquireDynamic("MyInterface")); + QVERIFY(rep->waitForSource()); + + auto mo = rep->metaObject(); + int signalIdx = mo->indexOfSignal("testEnumParamsInSignals(MyInterfaceReplica::Enum1,bool,QString)"); + QVERIFY(signalIdx != -1); + auto simm = mo->method(signalIdx); + { + QCOMPARE(simm.parameterCount(), 3); + auto paramNames = simm.parameterNames(); + QCOMPARE(paramNames.size(), 3); + QCOMPARE(paramNames.at(0), QByteArrayLiteral("enumSignalParam")); + QCOMPARE(paramNames.at(1), QByteArrayLiteral("signalParam2")); + QCOMPARE(paramNames.at(2), QByteArrayLiteral("__repc_variable_1")); + QCOMPARE(simm.parameterType(0), QMetaType::type("MyInterfaceReplica::Enum1")); + QCOMPARE(simm.parameterType(1), int(QMetaType::Bool)); + QCOMPARE(simm.parameterType(2), int(QMetaType::QString)); + } + + int slotIdx = mo->indexOfSlot("testEnumParamsInSlots(Enum1,bool,int)"); + QVERIFY(slotIdx != -1); + auto slmm = mo->method(slotIdx); + { + QCOMPARE(slmm .parameterCount(), 3); + auto paramNames = slmm .parameterNames(); + QCOMPARE(paramNames.size(), 3); + QCOMPARE(paramNames.at(0), QByteArrayLiteral("enumSlotParam")); + QCOMPARE(paramNames.at(1), QByteArrayLiteral("slotParam2")); + QCOMPARE(paramNames.at(2), QByteArrayLiteral("__repc_variable_1")); + } + + int enumVal = 0; + mo->invokeMethod(rep.data(), "testEnumParamsInSlots", + QGenericArgument("Enum1", &enumVal), + Q_ARG(bool, true), Q_ARG(int, 1234)); + + int enumIdx = mo->indexOfProperty("enum1"); + QVERIFY(enumIdx != -1); + QTRY_COMPARE(mo->property(enumIdx).read(rep.data()).toInt(), 0); + + int startedIdx = mo->indexOfProperty("started"); + QVERIFY(startedIdx != -1); + QTRY_COMPARE(mo->property(startedIdx).read(rep.data()).toBool(), true); + } + + void testMethodSignal() + { + QScopedPointer<MyInterfaceReplica> rep(new MyInterfaceReplica()); + rep->setNode(&m_repNode); + QVERIFY(rep->waitForSource()); + + rep->testEnumParamsInSlots(MyInterfaceReplica::Second, false, 74); + + connect(rep.data(), &MyInterfaceReplica::testEnumParamsInSignals, + [](MyInterfaceReplica::Enum1 enumSignalParam) { QCOMPARE(enumSignalParam, MyInterfaceReplica::Second); }); + + QTRY_COMPARE(rep->enum1(), MyInterfaceReplica::Second); + QTRY_COMPARE(rep->started(), false); + } + + void testDisconnect() + { + auto reply = m_rep->next(); + QSignalSpy stateSpy(m_rep.data(), &MyInterfaceReplica::stateChanged); + QVERIFY(reply.waitForFinished()); + + QVERIFY(stateSpy.wait()); + QVERIFY(m_rep->state() == QRemoteObjectReplica::Suspect); + + stateSpy.clear(); + QVERIFY(stateSpy.wait()); + QVERIFY(m_rep->state() == QRemoteObjectReplica::Valid); + // Make sure we updated to the correct enum1 value + QVERIFY(m_rep->enum1() == MyInterfaceReplica::First); + } + + void cleanupTestCase() + { + auto reply = m_rep->quit(); + QVERIFY(reply.waitForFinished()); + } + +private: + QRemoteObjectNode m_repNode; + QScopedPointer<MyInterfaceReplica> m_rep; +}; + +QTEST_MAIN(tst_Client_Process) + +#include "main.moc" diff --git a/tests/auto/integration_external/external/external.pro b/tests/auto/integration_external/external/external.pro new file mode 100644 index 0000000..5fcf5be --- /dev/null +++ b/tests/auto/integration_external/external/external.pro @@ -0,0 +1,9 @@ +CONFIG += testcase c++11 +CONFIG -= app_bundle +TARGET = tst_integration_external +DESTDIR = ./ +QT += testlib remoteobjects +QT -= gui + +SOURCES += \ + tst_integration_external.cpp diff --git a/tests/auto/integration_external/external/tst_integration_external.cpp b/tests/auto/integration_external/external/tst_integration_external.cpp new file mode 100644 index 0000000..8994db8 --- /dev/null +++ b/tests/auto/integration_external/external/tst_integration_external.cpp @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Ford Motor Company +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtRemoteObjects module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QtTest> +#include <QMetaType> +#include <QProcess> +#include <QStandardPaths> + +namespace { + +QString findExecutable(const QString &executableName, const QStringList &paths) +{ + const auto path = QStandardPaths::findExecutable(executableName, paths); + if (!path.isEmpty()) { + return path; + } + + qWarning() << "Could not find executable:" << executableName << "in any of" << paths; + return QString(); +} + +} + +class tst_Integration_External: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase() + { + QLoggingCategory::setFilterRules("qt.remoteobjects.warning=false"); + } + + void cleanup() + { + // wait for delivery of RemoveObject events to the source + QTest::qWait(200); + } + + void testRun_data() + { + QTest::addColumn<bool>("templated"); + QTest::newRow("non-templated enableRemoting") << false; + QTest::newRow("templated enableRemoting") << true; + } + + void testRun() + { + QFETCH(bool, templated); + + qDebug() << "Starting server process"; + QProcess serverProc; + serverProc.setProcessChannelMode(QProcess::ForwardedChannels); + if (templated) { + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + env.insert("TEMPLATED_REMOTING", "true"); + serverProc.setProcessEnvironment(env); + } + serverProc.start(findExecutable("server", { + QCoreApplication::applicationDirPath() + "/../server/" + })); + QVERIFY(serverProc.waitForStarted()); + + // wait for server start + QTest::qWait(200); + + qDebug() << "Starting client process"; + QProcess clientProc; + clientProc.setProcessChannelMode(QProcess::ForwardedChannels); + clientProc.start(findExecutable("client", { + QCoreApplication::applicationDirPath() + "/../client/" + })); + QVERIFY(clientProc.waitForStarted()); + + QVERIFY(clientProc.waitForFinished()); + QVERIFY(serverProc.waitForFinished()); + + QCOMPARE(serverProc.exitCode(), 0); + QCOMPARE(clientProc.exitCode(), 0); + } +}; + +QTEST_MAIN(tst_Integration_External) + +#include "tst_integration_external.moc" diff --git a/tests/auto/integration_external/integration_external.pro b/tests/auto/integration_external/integration_external.pro new file mode 100644 index 0000000..6dead73 --- /dev/null +++ b/tests/auto/integration_external/integration_external.pro @@ -0,0 +1,2 @@ +TEMPLATE = subdirs +SUBDIRS = client server external diff --git a/tests/auto/integration_external/server/main.cpp b/tests/auto/integration_external/server/main.cpp new file mode 100644 index 0000000..e97408b --- /dev/null +++ b/tests/auto/integration_external/server/main.cpp @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Ford Motor Company +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtRemoteObjects module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "mytestserver.h" + +#include <QCoreApplication> +#include <QTcpServer> +#include <QtTest/QtTest> + +const QUrl registryUrl = QUrl(QStringLiteral("tcp://127.0.0.1:65212")); +const QUrl extUrl = QUrl(QStringLiteral("exttcp://127.0.0.1:65213")); +const QUrl extUrl2 = QUrl(QStringLiteral("exttcp://127.0.0.1:65214")); + +class tst_Server_Process : public QObject +{ + Q_OBJECT + + struct Device + { + Device(QUrl url) : srcNode(url, registryUrl, QRemoteObjectHost::AllowExternalRegistration) + { + tcpServer.listen(QHostAddress(url.host()), url.port()); + QVERIFY(srcNode.waitForRegistry(3000)); + QObject::connect(&tcpServer, &QTcpServer::newConnection, [this]() { + auto conn = this->tcpServer.nextPendingConnection(); + this->srcNode.addHostSideConnection(conn); + }); + } + QTcpServer tcpServer; + QRemoteObjectHost srcNode; + }; + +private Q_SLOTS: + void testRun() + { + QRemoteObjectRegistryHost registry(registryUrl); + + Device dev1(extUrl); + MyTestServer myTestServer; + bool templated = qEnvironmentVariableIsSet("TEMPLATED_REMOTING"); + if (templated) + QVERIFY(dev1.srcNode.enableRemoting<MyInterfaceSourceAPI>(&myTestServer)); + else + QVERIFY(dev1.srcNode.enableRemoting(&myTestServer)); + + qDebug() << "Waiting for incoming connections"; + + QSignalSpy waitForStartedSpy(&myTestServer, SIGNAL(startedChanged(bool))); + QVERIFY(waitForStartedSpy.isValid()); + QVERIFY(waitForStartedSpy.wait()); + QCOMPARE(waitForStartedSpy.value(0).value(0).toBool(), true); + + // wait for delivery of events + QTest::qWait(200); + + qDebug() << "Client connected"; + + // BEGIN: Testing + + // make sure continuous changes to enums don't mess up the protocol + myTestServer.setEnum1(MyTestServer::Second); + myTestServer.setEnum1(MyTestServer::Third); + + emit myTestServer.advance(); + + waitForStartedSpy.clear(); + QVERIFY(waitForStartedSpy.wait()); + QCOMPARE(waitForStartedSpy.value(0).value(0).toBool(), false); + + bool next = false; + connect(&myTestServer, &MyTestServer::nextStep, [&next]{ next = true; }); + QTRY_VERIFY_WITH_TIMEOUT(next, 5000); + dev1.srcNode.disableRemoting(&myTestServer); + + // Change a value while replica is suspect + myTestServer.setEnum1(MyTestServer::First); + + // Share the object on a different "device", make sure registry updates and connects + Device dev2(extUrl2); + dev2.srcNode.enableRemoting(&myTestServer); + + // wait for quit + bool quit = false; + connect(&myTestServer, &MyTestServer::quitApp, [&quit]{quit = true;}); + QTRY_VERIFY_WITH_TIMEOUT(quit, 5000); + + // wait for delivery of events + QTest::qWait(200); + + qDebug() << "Done. Shutting down."; + } +}; + +QTEST_MAIN(tst_Server_Process) + +#include "main.moc" diff --git a/tests/auto/integration_external/server/mytestserver.cpp b/tests/auto/integration_external/server/mytestserver.cpp new file mode 100644 index 0000000..44421f4 --- /dev/null +++ b/tests/auto/integration_external/server/mytestserver.cpp @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Ford Motor Company +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtRemoteObjects module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qdebug.h> + +#include "mytestserver.h" +#include "rep_MyInterface_source.h" + +MyTestServer::MyTestServer(QObject *parent) + : MyInterfaceSimpleSource(parent) +{ + qDebug() << "Server started"; +} + +MyTestServer::~MyTestServer() +{ + qDebug() << "Server stopped"; +} + +bool MyTestServer::start() +{ + setStarted(true); + return true; +} + +bool MyTestServer::stop() +{ + setStarted(false); + return true; +} + +bool MyTestServer::quit() +{ + emit quitApp(); + return true; +} + +bool MyTestServer::next() +{ + emit nextStep(); + return true; +} + +void MyTestServer::testEnumParamsInSlots(Enum1 enumSlotParam, bool slotParam2, int number) +{ + setEnum1(enumSlotParam); + setStarted(slotParam2); + emit testEnumParamsInSignals(enum1(), started(), QString::number(number)); +} diff --git a/tests/auto/integration_external/server/mytestserver.h b/tests/auto/integration_external/server/mytestserver.h new file mode 100644 index 0000000..c0edda0 --- /dev/null +++ b/tests/auto/integration_external/server/mytestserver.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Ford Motor Company +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtRemoteObjects module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef MYTESTSERVER_H +#define MYTESTSERVER_H + +#include <QTimer> + +#include <QtRemoteObjects/qremoteobjectnode.h> +#include <QtRemoteObjects/qremoteobjectsource.h> + +#include "rep_MyInterface_source.h" + +class MyTestServer : public MyInterfaceSimpleSource +{ + Q_OBJECT + +public: + MyTestServer(QObject *parent = nullptr); + ~MyTestServer() override; + +public Q_SLOTS: + bool start() override; + bool stop() override; + bool quit() override; + bool next() override; + void testEnumParamsInSlots(Enum1 enumSlotParam, bool slotParam2, int __repc_variable_1) override; + +Q_SIGNALS: + void quitApp(); + void nextStep(); +}; + +#endif // MYTESTSERVER_H diff --git a/tests/auto/integration_external/server/server.pro b/tests/auto/integration_external/server/server.pro new file mode 100644 index 0000000..fea0c03 --- /dev/null +++ b/tests/auto/integration_external/server/server.pro @@ -0,0 +1,19 @@ +TEMPLATE = app +QT += remoteobjects core testlib +QT -= gui + +TARGET = server +DESTDIR = ./ +CONFIG += c++11 +CONFIG -= app_bundle + +REPC_SOURCE = $$PWD/../MyInterface.rep + +SOURCES += main.cpp \ + mytestserver.cpp + +HEADERS += \ + mytestserver.h + $$OUT_PWD/rep_MyInterface_source.h + +INCLUDEPATH += $$PWD |