From de6c757a26f6e04b7340c39ea5020d1e2a5ea2f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 9 Aug 2018 14:19:40 +0200 Subject: [PATCH] Implement basic functionality of JSON RPC server --- README.md | 3 +- .../authentication/authenticationreply.cpp | 34 ++- .../authentication/authenticationreply.h | 18 +- .../authentication/authenticator.cpp | 20 ++ .../authentication/authenticator.h | 16 +- .../authentication/awsauthenticator.cpp | 16 +- .../authentication/awsauthenticator.h | 8 +- libnymea-remoteproxy/engine.cpp | 18 +- libnymea-remoteproxy/engine.h | 4 + .../jsonrpc/authenticationhandler.cpp | 47 ++- .../jsonrpc/authenticationhandler.h | 6 +- libnymea-remoteproxy/jsonrpc/jsonhandler.cpp | 11 + libnymea-remoteproxy/jsonrpc/jsonhandler.h | 10 + libnymea-remoteproxy/jsonrpc/jsonreply.cpp | 28 +- libnymea-remoteproxy/jsonrpc/jsonreply.h | 28 +- libnymea-remoteproxy/jsonrpc/jsontypes.cpp | 7 + libnymea-remoteproxy/jsonrpc/jsontypes.h | 4 + libnymea-remoteproxy/jsonrpcserver.cpp | 187 +++++++++--- libnymea-remoteproxy/jsonrpcserver.h | 46 +-- libnymea-remoteproxy/proxyclient.cpp | 75 ++++- libnymea-remoteproxy/proxyclient.h | 35 ++- libnymea-remoteproxy/proxyconfiguration.cpp | 4 + libnymea-remoteproxy/proxyconfiguration.h | 4 + libnymea-remoteproxy/proxyserver.cpp | 52 +++- libnymea-remoteproxy/proxyserver.h | 14 +- libnymea-remoteproxy/transportinterface.cpp | 4 + libnymea-remoteproxy/transportinterface.h | 6 + libnymea-remoteproxy/websocketserver.cpp | 18 +- libnymea-remoteproxy/websocketserver.h | 6 + libnymea-remoteproxyclient/jsonrpcclient.cpp | 6 + libnymea-remoteproxyclient/jsonrpcclient.h | 17 ++ .../libnymea-remoteproxyclient.pro | 8 +- .../remoteproxyconnector.cpp | 6 + .../socketconnector.cpp | 11 + libnymea-remoteproxyclient/socketconnector.h | 29 ++ .../websocketconnector.cpp | 8 +- .../websocketconnector.h | 8 +- server/main.cpp | 1 + tests/mockauthenticator.cpp | 54 +++- tests/mockauthenticator.h | 30 +- tests/nymea-remoteproxy-tests.cpp | 272 +++++++++++++++--- tests/nymea-remoteproxy-tests.h | 52 +++- 42 files changed, 1044 insertions(+), 187 deletions(-) create mode 100644 libnymea-remoteproxyclient/jsonrpcclient.cpp create mode 100644 libnymea-remoteproxyclient/jsonrpcclient.h create mode 100644 libnymea-remoteproxyclient/socketconnector.cpp create mode 100644 libnymea-remoteproxyclient/socketconnector.h diff --git a/README.md b/README.md index 861bd4d..831853c 100644 --- a/README.md +++ b/README.md @@ -46,12 +46,11 @@ In order to run the test, you can call `make check` in the build directory or ru If you want to create a line coverage report from the tests simply run following command in the source directory: - $ apt install lcov + $ apt install lcov gcovr $ ./create-coverage-html.sh The resulting coverage report will be place in the `coverage-html` directory. - # Usage In order to get information about the server you can start the command with the `--help` parameter. diff --git a/libnymea-remoteproxy/authentication/authenticationreply.cpp b/libnymea-remoteproxy/authentication/authenticationreply.cpp index 78949e9..4ee757e 100644 --- a/libnymea-remoteproxy/authentication/authenticationreply.cpp +++ b/libnymea-remoteproxy/authentication/authenticationreply.cpp @@ -1,22 +1,18 @@ #include "authenticationreply.h" #include "authentication/authenticator.h" -AuthenticationReply::AuthenticationReply(const QUuid clientId, const QString &token, QObject *parent) : +namespace remoteproxy { + +AuthenticationReply::AuthenticationReply(ProxyClient *proxyClient, QObject *parent) : QObject(parent), - m_clientId(clientId), - m_token(token) + m_proxyClient(proxyClient) { } -QUuid AuthenticationReply::clientId() const +ProxyClient *AuthenticationReply::proxyClient() const { - return m_clientId; -} - -QString AuthenticationReply::token() const -{ - return m_token; + return m_proxyClient; } bool AuthenticationReply::isTimedOut() const @@ -29,18 +25,32 @@ bool AuthenticationReply::isFinished() const return m_finished; } +Authenticator::AuthenticationError AuthenticationReply::error() const +{ + return m_error; +} + void AuthenticationReply::setError(Authenticator::AuthenticationError error) { m_error = error; } +void AuthenticationReply::setFinished() +{ + emit finished(); +} + void AuthenticationReply::onTimeout() { - - + m_timedOut = true; + m_error = Authenticator::AuthenticationErrorTimeout; + emit finished(); } void AuthenticationReply::abort() { + m_error = Authenticator::AuthenticationErrorAborted; + emit finished(); +} } diff --git a/libnymea-remoteproxy/authentication/authenticationreply.h b/libnymea-remoteproxy/authentication/authenticationreply.h index 369ed8f..4aa05a0 100644 --- a/libnymea-remoteproxy/authentication/authenticationreply.h +++ b/libnymea-remoteproxy/authentication/authenticationreply.h @@ -8,16 +8,15 @@ #include "authenticator.h" +namespace remoteproxy { + class AuthenticationReply : public QObject { Q_OBJECT public: friend class Authenticator; - explicit AuthenticationReply(const QUuid clientId, const QString &token, QObject *parent = nullptr); - - QUuid clientId() const; - QString token() const; + ProxyClient *proxyClient() const; bool isTimedOut() const; bool isFinished() const; @@ -25,15 +24,18 @@ public: Authenticator::AuthenticationError error() const; private: - QUuid m_clientId; - QString m_token; + explicit AuthenticationReply(ProxyClient *proxyClient, QObject *parent = nullptr); + + ProxyClient *m_proxyClient = nullptr; QTimer m_timer; bool m_timedOut = false; bool m_finished = false; - Authenticator::AuthenticationError m_error; + + Authenticator::AuthenticationError m_error = Authenticator::AuthenticationErrorUnknown; void setError(Authenticator::AuthenticationError error); + void setFinished(); signals: void finished(); @@ -46,4 +48,6 @@ public slots: }; +} + #endif // AUTHENTICATIONREPLY_H diff --git a/libnymea-remoteproxy/authentication/authenticator.cpp b/libnymea-remoteproxy/authentication/authenticator.cpp index 42e4da5..2cee567 100644 --- a/libnymea-remoteproxy/authentication/authenticator.cpp +++ b/libnymea-remoteproxy/authentication/authenticator.cpp @@ -1,13 +1,33 @@ +#include "proxyclient.h" #include "authenticator.h" #include "authenticationreply.h" +namespace remoteproxy { + Authenticator::Authenticator(QObject *parent) : QObject(parent) { } +void Authenticator::setReplyError(AuthenticationReply *reply, Authenticator::AuthenticationError error) +{ + reply->setError(error); +} + +void Authenticator::setReplyFinished(AuthenticationReply *reply) +{ + reply->setFinished(); +} + +AuthenticationReply *Authenticator::createAuthenticationReply(ProxyClient *proxyClient, QObject *parent) +{ + return new AuthenticationReply(proxyClient, parent); +} + Authenticator::~Authenticator() { } + +} diff --git a/libnymea-remoteproxy/authentication/authenticator.h b/libnymea-remoteproxy/authentication/authenticator.h index ae29626..cda3e84 100644 --- a/libnymea-remoteproxy/authentication/authenticator.h +++ b/libnymea-remoteproxy/authentication/authenticator.h @@ -4,6 +4,9 @@ #include #include +namespace remoteproxy { + +class ProxyClient; class AuthenticationReply; class Authenticator : public QObject @@ -12,6 +15,7 @@ class Authenticator : public QObject public: enum AuthenticationError { AuthenticationErrorNoError, + AuthenticationErrorUnknown, AuthenticationErrorTimeout, AuthenticationErrorAborted, AuthenticationErrorAuthenticationFailed, @@ -22,8 +26,18 @@ public: explicit Authenticator(QObject *parent = nullptr); virtual ~Authenticator() = 0; + virtual QString name() const = 0; + public slots: - virtual AuthenticationReply *authenticate(const QUuid &clientId, const QString &token) = 0; + virtual AuthenticationReply *authenticate(ProxyClient *proxyClient) = 0; + +protected: + void setReplyError(AuthenticationReply *reply, AuthenticationError error); + void setReplyFinished(AuthenticationReply *reply); + + AuthenticationReply *createAuthenticationReply(ProxyClient *proxyClient, QObject *parent = nullptr); }; +} + #endif // AUTHENTICATOR_H diff --git a/libnymea-remoteproxy/authentication/awsauthenticator.cpp b/libnymea-remoteproxy/authentication/awsauthenticator.cpp index c485a47..bbd8860 100644 --- a/libnymea-remoteproxy/authentication/awsauthenticator.cpp +++ b/libnymea-remoteproxy/authentication/awsauthenticator.cpp @@ -1,17 +1,25 @@ +#include "proxyclient.h" #include "awsauthenticator.h" #include "loggingcategories.h" +namespace remoteproxy { + AwsAuthenticator::AwsAuthenticator(QObject *parent) : Authenticator(parent) { } -AuthenticationReply *AwsAuthenticator::authenticate(const QUuid &clientId, const QString &token) +QString AwsAuthenticator::name() const { - qCDebug(dcAuthenticator()) << "Start authenticating" << clientId << "using token" << token; - - AuthenticationReply *reply = new AuthenticationReply(clientId, token, this); + return "AWS authenticator"; +} +AuthenticationReply *AwsAuthenticator::authenticate(ProxyClient *proxyClient) +{ + qCDebug(dcAuthenticator()) << name() << "Start authenticating" << proxyClient << "using token" << proxyClient->token(); + AuthenticationReply *reply = createAuthenticationReply(proxyClient, this); return reply; } + +} diff --git a/libnymea-remoteproxy/authentication/awsauthenticator.h b/libnymea-remoteproxy/authentication/awsauthenticator.h index 766014f..06da9ee 100644 --- a/libnymea-remoteproxy/authentication/awsauthenticator.h +++ b/libnymea-remoteproxy/authentication/awsauthenticator.h @@ -6,6 +6,8 @@ #include "authenticator.h" #include "authenticationreply.h" +namespace remoteproxy { + class AwsAuthenticator : public Authenticator { Q_OBJECT @@ -13,9 +15,13 @@ public: explicit AwsAuthenticator(QObject *parent = nullptr); ~AwsAuthenticator() override = default; + QString name() const override; + public slots: - AuthenticationReply *authenticate(const QUuid &clientId, const QString &token) override; + AuthenticationReply *authenticate(ProxyClient *proxyClient) override; }; +} + #endif // AWSAUTHENTICATOR_H diff --git a/libnymea-remoteproxy/engine.cpp b/libnymea-remoteproxy/engine.cpp index 0bf796a..40e5959 100644 --- a/libnymea-remoteproxy/engine.cpp +++ b/libnymea-remoteproxy/engine.cpp @@ -1,6 +1,8 @@ #include "engine.h" #include "loggingcategories.h" +namespace remoteproxy { + Engine *Engine::s_instance = nullptr; Engine *Engine::instance() @@ -73,12 +75,12 @@ void Engine::stop() if (m_proxyServer) { m_proxyServer->stopServer(); - m_proxyServer->deleteLater(); + delete m_proxyServer; m_proxyServer = nullptr; } if (m_webSocketServer) { - m_webSocketServer->deleteLater(); + delete m_webSocketServer; m_webSocketServer = nullptr; } @@ -114,7 +116,7 @@ void Engine::setWebSocketServerPort(const quint16 &port) void Engine::setSslConfiguration(const QSslConfiguration &configuration) { - qCDebug(dcEngine()) << "SSL Configuration:"; + qCDebug(dcEngine()) << "SSL certificate information:"; qCDebug(dcEngine()) << " Common name:" << configuration.localCertificate().issuerInfo(QSslCertificate::CommonName); qCDebug(dcEngine()) << " Organisation:" << configuration.localCertificate().issuerInfo(QSslCertificate::Organization); qCDebug(dcEngine()) << " Organisation unit name:" << configuration.localCertificate().issuerInfo(QSslCertificate::OrganizationalUnitName); @@ -134,18 +136,18 @@ void Engine::setAuthenticationServerUrl(const QUrl &url) void Engine::setAuthenticator(Authenticator *authenticator) { + if (m_authenticator == authenticator) + return; + if (m_authenticator) { qCDebug(dcEngine()) << "There is already an authenticator registered. Unregister default authenticator"; - m_authenticator->deleteLater(); m_authenticator = nullptr; - // FIXME: do unconnect } m_authenticator = authenticator; - - + // FIXME: connect } Authenticator *Engine::authenticator() const @@ -183,3 +185,5 @@ void Engine::setRunning(bool running) m_running = running; emit runningChanged(m_running); } + +} diff --git a/libnymea-remoteproxy/engine.h b/libnymea-remoteproxy/engine.h index 355edab..0d0bea3 100644 --- a/libnymea-remoteproxy/engine.h +++ b/libnymea-remoteproxy/engine.h @@ -10,6 +10,8 @@ #include "websocketserver.h" #include "authentication/authenticator.h" +namespace remoteproxy { + class Engine : public QObject { Q_OBJECT @@ -62,4 +64,6 @@ signals: }; +} + #endif // ENGINE_H diff --git a/libnymea-remoteproxy/jsonrpc/authenticationhandler.cpp b/libnymea-remoteproxy/jsonrpc/authenticationhandler.cpp index 4f6da88..193dedc 100644 --- a/libnymea-remoteproxy/jsonrpc/authenticationhandler.cpp +++ b/libnymea-remoteproxy/jsonrpc/authenticationhandler.cpp @@ -4,6 +4,8 @@ #include "engine.h" +namespace remoteproxy { + AuthenticationHandler::AuthenticationHandler(QObject *parent) : JsonHandler(parent) { @@ -11,31 +13,34 @@ AuthenticationHandler::AuthenticationHandler(QObject *parent) : QVariantMap params; QVariantMap returns; setDescription("Authenticate", "Authenticate this connection. This should always be the first request to the server. The given id is the unique server/client uuid (i.e. the uuid of server/client)."); - params.insert("id", JsonTypes::basicTypeToString(JsonTypes::Uuid)); + params.insert("uuid", JsonTypes::basicTypeToString(JsonTypes::String)); params.insert("name", JsonTypes::basicTypeToString(JsonTypes::String)); params.insert("token", JsonTypes::basicTypeToString(JsonTypes::String)); setParams("Authenticate", params); returns.insert("authenticationError", JsonTypes::authenticationErrorRef()); setReturns("Authenticate", returns); - } +} QString AuthenticationHandler::name() const { return "Authentication"; } -JsonReply *AuthenticationHandler::Authenticate(const QVariantMap ¶ms, const QUuid &clientId) +JsonReply *AuthenticationHandler::Authenticate(const QVariantMap ¶ms, ProxyClient *proxyClient) { - qCDebug(dcJsonRpc()) << "Authenticate:" << clientId.toString(); - - QString clientName = params.value("name").toString(); - QString clientToken = params.value("token").toString(); - QUuid clientDeviceId = QUuid(params.value("id").toString()); - - Q_UNUSED(clientDeviceId); + QString uuid = params.value("uuid").toString(); + QString name = params.value("name").toString(); + QString token = params.value("token").toString(); + qCDebug(dcJsonRpc()) << "Authenticate:" << name << uuid << token; JsonReply *jsonReply = createAsyncReply("Authenticate"); - AuthenticationReply *authReply = Engine::instance()->authenticator()->authenticate(clientId, clientToken); + + // Set the token for this proxy client + proxyClient->setUuid(uuid); + proxyClient->setName(name); + proxyClient->setToken(token); + + AuthenticationReply *authReply = Engine::instance()->authenticator()->authenticate(proxyClient); connect(authReply, &AuthenticationReply::finished, this, &AuthenticationHandler::onAuthenticationFinished); m_runningAuthentications.insert(authReply, jsonReply); @@ -45,8 +50,22 @@ JsonReply *AuthenticationHandler::Authenticate(const QVariantMap ¶ms, const void AuthenticationHandler::onAuthenticationFinished() { - //AuthenticationReply *authReply = static_cast(sender()); - //JsonReply *jsonReply = m_runningAuthentications.take(authReply); + AuthenticationReply *authenticationReply = static_cast(sender()); + JsonReply *jsonReply = m_runningAuthentications.take(authenticationReply); + + authenticationReply->deleteLater(); + + qCDebug(dcJsonRpc()) << "Authentication respons ready for" << authenticationReply->proxyClient() << authenticationReply->error(); + + if (authenticationReply->error() != Authenticator::AuthenticationErrorNoError) { + qCWarning(dcJsonRpc()) << "Authentication error occured" << authenticationReply->error(); + jsonReply->setSuccess(true); + } else { + jsonReply->setSuccess(false); + } - //emit asyncReply() + jsonReply->setData(errorToReply(authenticationReply->error())); + jsonReply->finished(); +} + } diff --git a/libnymea-remoteproxy/jsonrpc/authenticationhandler.h b/libnymea-remoteproxy/jsonrpc/authenticationhandler.h index d829c12..0056159 100644 --- a/libnymea-remoteproxy/jsonrpc/authenticationhandler.h +++ b/libnymea-remoteproxy/jsonrpc/authenticationhandler.h @@ -6,6 +6,8 @@ #include "jsonhandler.h" #include "authentication/authenticationreply.h" +namespace remoteproxy { + class AuthenticationHandler : public JsonHandler { Q_OBJECT @@ -15,7 +17,7 @@ public: QString name() const override; - Q_INVOKABLE JsonReply *Authenticate(const QVariantMap ¶ms, const QUuid &clientId); + Q_INVOKABLE JsonReply *Authenticate(const QVariantMap ¶ms, ProxyClient *proxyClient); private: QHash m_runningAuthentications; @@ -26,4 +28,6 @@ private slots: }; +} + #endif // AUTHENTICATIONHANDLER_H diff --git a/libnymea-remoteproxy/jsonrpc/jsonhandler.cpp b/libnymea-remoteproxy/jsonrpc/jsonhandler.cpp index 091036e..17257a4 100644 --- a/libnymea-remoteproxy/jsonrpc/jsonhandler.cpp +++ b/libnymea-remoteproxy/jsonrpc/jsonhandler.cpp @@ -7,6 +7,8 @@ #include "jsontypes.h" #include "loggingcategories.h" +namespace remoteproxy { + JsonHandler::JsonHandler(QObject *parent): QObject(parent) { @@ -106,6 +108,13 @@ void JsonHandler::setReturns(const QString &methodName, const QVariantMap &retur qCWarning(dcJsonRpc()) << "Cannot set returns. No such method:" << methodName; } +QVariantMap JsonHandler::errorToReply(Authenticator::AuthenticationError error) const +{ + QVariantMap returns; + returns.insert("authenticationError", JsonTypes::authenticationErrorToString(error)); + return returns; +} + JsonReply *JsonHandler::createReply(const QVariantMap &data) const { return JsonReply::createReply(const_cast(this), data); @@ -115,3 +124,5 @@ JsonReply *JsonHandler::createAsyncReply(const QString &method) const { return JsonReply::createAsyncReply(const_cast(this), method); } + +} diff --git a/libnymea-remoteproxy/jsonrpc/jsonhandler.h b/libnymea-remoteproxy/jsonrpc/jsonhandler.h index 90432e5..086c614 100644 --- a/libnymea-remoteproxy/jsonrpc/jsonhandler.h +++ b/libnymea-remoteproxy/jsonrpc/jsonhandler.h @@ -7,6 +7,10 @@ #include #include +#include "authentication/authenticator.h" + +namespace remoteproxy { + class JsonReply; class JsonHandler : public QObject @@ -36,8 +40,14 @@ protected: void setParams(const QString &methodName, const QVariantMap ¶ms); void setReturns(const QString &methodName, const QVariantMap &returns); + QVariantMap errorToReply(Authenticator::AuthenticationError error) const; + + JsonReply *createReply(const QVariantMap &data) const; JsonReply *createAsyncReply(const QString &method) const; }; + +} + #endif // JSONHANDLER_H diff --git a/libnymea-remoteproxy/jsonrpc/jsonreply.cpp b/libnymea-remoteproxy/jsonrpc/jsonreply.cpp index 0acefce..496a50c 100644 --- a/libnymea-remoteproxy/jsonrpc/jsonreply.cpp +++ b/libnymea-remoteproxy/jsonrpc/jsonreply.cpp @@ -1,5 +1,7 @@ #include "jsonreply.h" +namespace remoteproxy { + JsonReply *JsonReply::createReply(JsonHandler *handler, const QVariantMap &data) { return new JsonReply(TypeSync, handler, QString(), data); @@ -35,14 +37,14 @@ QString JsonReply::method() const return m_method; } -QUuid JsonReply::connectionId() const +QUuid JsonReply::clientId() const { - return m_connectionId; + return m_clientId; } -void JsonReply::setClientId(const QUuid &connectionId) +void JsonReply::setClientId(const QUuid &clientId) { - m_connectionId = connectionId; + m_clientId = clientId; } int JsonReply::commandId() const @@ -55,6 +57,16 @@ void JsonReply::setCommandId(int commandId) m_commandId = commandId; } +bool JsonReply::success() const +{ + return m_success; +} + +void JsonReply::setSuccess(bool success) +{ + m_success = success; +} + bool JsonReply::timedOut() const { return m_timedOut; @@ -71,12 +83,16 @@ void JsonReply::timeout() emit finished(); } -JsonReply::JsonReply(JsonReply::Type type, JsonHandler *handler, const QString &method, const QVariantMap &data): +JsonReply::JsonReply(JsonReply::Type type, JsonHandler *handler, const QString &method, const QVariantMap &data, bool success): m_type(type), m_data(data), m_handler(handler), m_method(method), - m_timedOut(false) + m_success(success) { connect(&m_timeout, &QTimer::timeout, this, &JsonReply::timeout); } + + + +} diff --git a/libnymea-remoteproxy/jsonrpc/jsonreply.h b/libnymea-remoteproxy/jsonrpc/jsonreply.h index afab2a5..d349dbe 100644 --- a/libnymea-remoteproxy/jsonrpc/jsonreply.h +++ b/libnymea-remoteproxy/jsonrpc/jsonreply.h @@ -7,6 +7,8 @@ #include "jsonhandler.h" +namespace remoteproxy { + class JsonReply: public QObject { Q_OBJECT @@ -16,22 +18,29 @@ public: TypeAsync }; + friend class JsonRpcServer; + static JsonReply *createReply(JsonHandler *handler, const QVariantMap &data); static JsonReply *createAsyncReply(JsonHandler *handler, const QString &method); Type type() const; + QVariantMap data() const; void setData(const QVariantMap &data); JsonHandler *handler() const; + QString method() const; - QUuid connectionId() const; - void setClientId(const QUuid &connectionId); + QUuid clientId() const; + void setClientId(const QUuid &clientId); int commandId() const; void setCommandId(int commandId); + bool success() const; + void setSuccess(bool success); + bool timedOut() const; public slots: @@ -44,17 +53,22 @@ private slots: void timeout(); private: - JsonReply(Type type, JsonHandler *handler, const QString &method, const QVariantMap &data = QVariantMap()); - Type m_type; + JsonReply(Type type, JsonHandler *handler, const QString &method, const QVariantMap &data = QVariantMap(), bool success = true); + + Type m_type = TypeSync; QVariantMap m_data; - JsonHandler *m_handler; + JsonHandler *m_handler = nullptr; + QString m_method; - QUuid m_connectionId; + QUuid m_clientId; int m_commandId; - bool m_timedOut; + bool m_timedOut = false; + bool m_success = false; QTimer m_timeout; }; +} + #endif // JSONRPCREPLY_H diff --git a/libnymea-remoteproxy/jsonrpc/jsontypes.cpp b/libnymea-remoteproxy/jsonrpc/jsontypes.cpp index e8d8f48..c9a467a 100644 --- a/libnymea-remoteproxy/jsonrpc/jsontypes.cpp +++ b/libnymea-remoteproxy/jsonrpc/jsontypes.cpp @@ -5,6 +5,8 @@ #include "loggingcategories.h" +namespace remoteproxy { + bool JsonTypes::s_initialized = false; QString JsonTypes::s_lastError; @@ -198,6 +200,9 @@ QPair JsonTypes::validateList(const QVariantList &templateList, c QPair JsonTypes::validateBasicType(const QVariant &variant) { if (variant.canConvert(QVariant::Uuid) && QVariant(variant).convert(QVariant::Uuid)) { + if (QUuid(variant.toString()).isNull()) { + return report(false, "Invalid uuid format."); + } return report(true, ""); } if (variant.canConvert(QVariant::String) && QVariant(variant).convert(QVariant::String)) { @@ -259,3 +264,5 @@ QVariantList JsonTypes::enumToStrings(const QMetaObject &metaObject, const QStri } return enumStrings; } + +} diff --git a/libnymea-remoteproxy/jsonrpc/jsontypes.h b/libnymea-remoteproxy/jsonrpc/jsontypes.h index a814d0c..9752d33 100644 --- a/libnymea-remoteproxy/jsonrpc/jsontypes.h +++ b/libnymea-remoteproxy/jsonrpc/jsontypes.h @@ -8,6 +8,8 @@ #include "authentication/authenticator.h" +namespace remoteproxy { + #define DECLARE_OBJECT(typeName, jsonName) \ public: \ static QString typeName##Ref() { return QStringLiteral("$ref:") + QStringLiteral(jsonName); if (!s_initialized) { init(); } } \ @@ -87,4 +89,6 @@ private: static QVariantList enumToStrings(const QMetaObject &metaObject, const QString &enumName); }; +} + #endif // JSONTYPES_H diff --git a/libnymea-remoteproxy/jsonrpcserver.cpp b/libnymea-remoteproxy/jsonrpcserver.cpp index 583bd3c..89d1fbf 100644 --- a/libnymea-remoteproxy/jsonrpcserver.cpp +++ b/libnymea-remoteproxy/jsonrpcserver.cpp @@ -4,8 +4,11 @@ #include "jsonrpc/jsontypes.h" #include +#include #include +namespace remoteproxy { + JsonRpcServer::JsonRpcServer(QObject *parent) : JsonHandler(parent) { @@ -13,7 +16,8 @@ JsonRpcServer::JsonRpcServer(QObject *parent) : QVariantMap params; QVariantMap returns; params.clear(); returns.clear(); - setDescription("Hello", "Once connected to this server, a client can get information about the server by saying Hello. The response informs the client about the server."); + setDescription("Hello", "Once connected to this server, a client can get information about the server by saying Hello. " + "The response informs the client about the server."); setParams("Hello", params); returns.insert("server", JsonTypes::basicTypeToString(JsonTypes::String)); returns.insert("name", JsonTypes::basicTypeToString(JsonTypes::String)); @@ -35,6 +39,9 @@ JsonRpcServer::JsonRpcServer(QObject *parent) : JsonRpcServer::~JsonRpcServer() { qCDebug(dcJsonRpc()) << "Shutting down Json RPC server"; + foreach (JsonHandler *handler, m_handlers.values()) { + unregisterHandler(handler); + } } QString JsonRpcServer::name() const @@ -42,33 +49,10 @@ QString JsonRpcServer::name() const return "RemoteProxy"; } -QHash JsonRpcServer::handlers() const -{ - return m_handlers; -} - -void JsonRpcServer::registerTransportInterface(TransportInterface *interface) -{ - if (m_interfaces.contains(interface)) - return; - - connect(interface, &TransportInterface::clientConnected, this, &JsonRpcServer::clientConnected); - connect(interface, &TransportInterface::clientDisconnected, this, &JsonRpcServer::clientDisconnected); - connect(interface, &TransportInterface::dataAvailable, this, &JsonRpcServer::processData); - m_interfaces.append(interface); -} - -void JsonRpcServer::unregisterTransportInterface(TransportInterface *interface) -{ - disconnect(interface, &TransportInterface::clientConnected, this, &JsonRpcServer::clientConnected); - disconnect(interface, &TransportInterface::clientDisconnected, this, &JsonRpcServer::clientDisconnected); - disconnect(interface, &TransportInterface::dataAvailable, this, &JsonRpcServer::processData); - m_interfaces.removeOne(interface); -} - -JsonReply *JsonRpcServer::Hello(const QVariantMap ¶ms) const +JsonReply *JsonRpcServer::Hello(const QVariantMap ¶ms, ProxyClient *proxyClient) const { Q_UNUSED(params) + Q_UNUSED(proxyClient) QVariantMap data; data.insert("server", QCoreApplication::applicationName()); @@ -79,9 +63,12 @@ JsonReply *JsonRpcServer::Hello(const QVariantMap ¶ms) const return createReply(data); } -JsonReply *JsonRpcServer::Introspect(const QVariantMap ¶ms) const +JsonReply *JsonRpcServer::Introspect(const QVariantMap ¶ms, ProxyClient *proxyClient) const { Q_UNUSED(params) + Q_UNUSED(proxyClient) + + qCDebug(dcJsonRpc()) << "Introspect called."; QVariantMap data; data.insert("types", JsonTypes::allTypes()); @@ -103,7 +90,7 @@ JsonReply *JsonRpcServer::Introspect(const QVariantMap ¶ms) const return createReply(data); } -void JsonRpcServer::sendResponse(TransportInterface *interface, const QUuid &clientId, int commandId, const QVariantMap ¶ms) +void JsonRpcServer::sendResponse(ProxyClient *client, int commandId, const QVariantMap ¶ms) { QVariantMap response; response.insert("id", commandId); @@ -112,10 +99,10 @@ void JsonRpcServer::sendResponse(TransportInterface *interface, const QUuid &cli QByteArray data = QJsonDocument::fromVariant(response).toJson(QJsonDocument::Compact); qCDebug(dcJsonRpcTraffic()) << "Sending data:" << data; - interface->sendData(clientId, data); + client->interface()->sendData(client->clientId(), data); } -void JsonRpcServer::sendErrorResponse(TransportInterface *interface, const QUuid &clientId, int commandId, const QString &error) +void JsonRpcServer::sendErrorResponse(ProxyClient *client, int commandId, const QString &error) { QVariantMap errorResponse; errorResponse.insert("id", commandId); @@ -124,7 +111,7 @@ void JsonRpcServer::sendErrorResponse(TransportInterface *interface, const QUuid QByteArray data = QJsonDocument::fromVariant(errorResponse).toJson(QJsonDocument::Compact); qCDebug(dcJsonRpcTraffic()) << "Sending data:" << data; - interface->sendData(clientId, data); + client->interface()->sendData(client->clientId(), data); } QString JsonRpcServer::formatAssertion(const QString &targetNamespace, const QString &method, JsonHandler *handler, const QVariantMap &data) const @@ -149,25 +136,149 @@ void JsonRpcServer::registerHandler(JsonHandler *handler) } } +void JsonRpcServer::unregisterHandler(JsonHandler *handler) +{ + m_handlers.remove(handler->name()); + qCDebug(dcJsonRpc()) << "Unregister handler" << handler->name(); + + for (int i = 0; i < handler->metaObject()->methodCount(); ++i) { + QMetaMethod method = handler->metaObject()->method(i); + if (method.methodType() == QMetaMethod::Signal && QString(method.name()).contains(QRegExp("^[A-Z]"))) { + QObject::connect(handler, method, this, metaObject()->method(metaObject()->indexOfSlot("sendNotification(QVariantMap)"))); + } + } +} + void JsonRpcServer::setup() { registerHandler(this); registerHandler(new AuthenticationHandler(this)); } -void JsonRpcServer::clientConnected(const QUuid &clientId) +void JsonRpcServer::asyncReplyFinished() { - Q_UNUSED(clientId) + JsonReply *reply = static_cast(sender()); + ProxyClient *proxyClient = m_asyncReplies.take(reply); + + reply->deleteLater(); + + if (!proxyClient) { + qCWarning(dcJsonRpc()) << "Got an async reply but the client does not exist any more"; + return; + } + + if (!reply->timedOut()) { + Q_ASSERT_X(reply->handler()->validateReturns(reply->method(), reply->data()).first + ,"validating return value", formatAssertion(reply->handler()->name(), reply->method(), reply->handler(), reply->data()).toLatin1().data()); + sendResponse(proxyClient, reply->commandId(), reply->data()); + if (!reply->success()) { + // Disconnect this client since the request was not successfully + proxyClient->interface()->killClientConnection(proxyClient->clientId()); + } + } else { + sendErrorResponse(proxyClient, reply->commandId(), "Command timed out"); + // Disconnect this client since he requested something that created a timeout + proxyClient->interface()->killClientConnection(proxyClient->clientId()); + } } -void JsonRpcServer::clientDisconnected(const QUuid &clientId) +void JsonRpcServer::registerClient(ProxyClient *proxyClient) { - Q_UNUSED(clientId) + qCDebug(dcJsonRpc()) << "Register client" << proxyClient; + if (m_clients.contains(proxyClient)) { + qCWarning(dcJsonRpc()) << "Client already registered" << proxyClient; + return; + } + m_clients.append(proxyClient); } -void JsonRpcServer::processData(const QUuid &clientId, const QByteArray &data) +void JsonRpcServer::unregisterClient(ProxyClient *proxyClient) { - Q_UNUSED(clientId) - Q_UNUSED(data) + qCDebug(dcJsonRpc()) << "Unregister client" << proxyClient; + if (!m_clients.contains(proxyClient)) { + qCWarning(dcJsonRpc()) << "Client was not registered" << proxyClient; + return; + } + + m_clients.removeAll(proxyClient); } +void JsonRpcServer::processData(ProxyClient *proxyClient, const QByteArray &data) +{ + if (!m_clients.contains(proxyClient)) + return; + + qCDebug(dcJsonRpcTraffic()) << "Incoming data from" << proxyClient << ": " << data; + + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + + if(error.error != QJsonParseError::NoError) { + qCWarning(dcJsonRpc) << "Failed to parse JSON data" << data << ":" << error.errorString(); + sendErrorResponse(proxyClient, -1, QString("Failed to parse JSON data: %1").arg(error.errorString())); + proxyClient->interface()->killClientConnection(proxyClient->clientId()); + return; + } + + QVariantMap message = jsonDoc.toVariant().toMap(); + + bool success = false; + int commandId = message.value("id").toInt(&success); + if (!success) { + qCWarning(dcJsonRpc()) << "Error parsing command. Missing \"id\":" << message; + sendErrorResponse(proxyClient, -1, "Error parsing command. Missing 'id'"); + proxyClient->interface()->killClientConnection(proxyClient->clientId()); + return; + } + + QStringList commandList = message.value("method").toString().split('.'); + if (commandList.count() != 2) { + qCWarning(dcJsonRpc) << "Error parsing method.\nGot:" << message.value("method").toString() << "\nExpected: \"Namespace.method\""; + sendErrorResponse(proxyClient, commandId, QString("Error parsing method. Got: '%1'', Expected: 'Namespace.method'").arg(message.value("method").toString())); + proxyClient->interface()->killClientConnection(proxyClient->clientId()); + return; + } + + QString targetNamespace = commandList.first(); + QString method = commandList.last(); + + JsonHandler *handler = m_handlers.value(targetNamespace); + if (!handler) { + sendErrorResponse(proxyClient, commandId, "No such namespace"); + proxyClient->interface()->killClientConnection(proxyClient->clientId()); + return; + } + + if (!handler->hasMethod(method)) { + sendErrorResponse(proxyClient, commandId, "No such namespace"); + proxyClient->interface()->killClientConnection(proxyClient->clientId()); + return; + } + + QVariantMap params = message.value("params").toMap(); + + QPair validationResult = handler->validateParams(method, params); + if (!validationResult.first) { + sendErrorResponse(proxyClient, commandId, "Invalid params: " + validationResult.second); + proxyClient->interface()->killClientConnection(proxyClient->clientId()); + return; + } + + JsonReply *reply; + QMetaObject::invokeMethod(handler, method.toLatin1().data(), Q_RETURN_ARG(JsonReply*, reply), Q_ARG(QVariantMap, params), Q_ARG(ProxyClient *, proxyClient)); + if (reply->type() == JsonReply::TypeAsync) { + m_asyncReplies.insert(reply, proxyClient); + reply->setClientId(proxyClient->clientId()); + reply->setCommandId(commandId); + connect(reply, &JsonReply::finished, this, &JsonRpcServer::asyncReplyFinished); + reply->startWait(); + } else { + Q_ASSERT_X((targetNamespace == "RemoteProxy" && method == "Introspect") || handler->validateReturns(method, reply->data()).first + ,"validating return value", formatAssertion(targetNamespace, method, handler, reply->data()).toLatin1().data()); + reply->setClientId(proxyClient->clientId()); + reply->setCommandId(commandId); + sendResponse(proxyClient, commandId, reply->data()); + } +} + +} diff --git a/libnymea-remoteproxy/jsonrpcserver.h b/libnymea-remoteproxy/jsonrpcserver.h index 86e2f70..6d4db50 100644 --- a/libnymea-remoteproxy/jsonrpcserver.h +++ b/libnymea-remoteproxy/jsonrpcserver.h @@ -2,11 +2,16 @@ #define JSONRPCSERVER_H #include +#include +#include "proxyclient.h" +#include "jsonrpc/jsonreply.h" #include "transportinterface.h" #include "jsonrpc/jsonhandler.h" #include "jsonrpc/authenticationhandler.h" +namespace remoteproxy { + class JsonRpcServer : public JsonHandler { Q_OBJECT @@ -16,39 +21,40 @@ public: QString name() const override; - QHash handlers() const; + Q_INVOKABLE JsonReply *Hello(const QVariantMap ¶ms, ProxyClient *proxyClient = nullptr) const; + Q_INVOKABLE JsonReply *Introspect(const QVariantMap ¶ms, ProxyClient *proxyClient = nullptr) const; - void registerTransportInterface(TransportInterface *interface); - void unregisterTransportInterface(TransportInterface *interface); - - Q_INVOKABLE JsonReply *Hello(const QVariantMap ¶ms) const; - Q_INVOKABLE JsonReply *Introspect(const QVariantMap ¶ms) const; +signals: + void TunnelEstablished(const QVariantMap ¶ms, ProxyClient *proxyClient = nullptr); private: - QList m_interfaces; QHash m_handlers; - QHash m_asyncReplies; - QHash m_clientTransports; - QHash m_pairingRequests; - + QHash m_asyncReplies; + QList m_clients; int m_notificationId; - void sendResponse(TransportInterface *interface, const QUuid &clientId, int commandId, const QVariantMap ¶ms = QVariantMap()); - void sendErrorResponse(TransportInterface *interface, const QUuid &clientId, int commandId, const QString &error); + void sendResponse(ProxyClient *client, int commandId, const QVariantMap ¶ms = QVariantMap()); + void sendErrorResponse(ProxyClient *client, int commandId, const QString &error); + QString formatAssertion(const QString &targetNamespace, const QString &method, JsonHandler *handler, const QVariantMap &data) const; void registerHandler(JsonHandler *handler); - - + void unregisterHandler(JsonHandler *handler); private slots: void setup(); - void clientConnected(const QUuid &clientId); - void clientDisconnected(const QUuid &clientId); - void processData(const QUuid &clientId, const QByteArray &data); -// void sendNotification(const QVariantMap ¶ms); -// void asyncReplyFinished(); + void asyncReplyFinished(); + +public slots: + // Client registration for JSON RPC traffic + void registerClient(ProxyClient *proxyClient); + void unregisterClient(ProxyClient *proxyClient); + + // Process data from client + void processData(ProxyClient *proxyClient, const QByteArray &data); }; +} + #endif // JSONRPCSERVER_H diff --git a/libnymea-remoteproxy/proxyclient.cpp b/libnymea-remoteproxy/proxyclient.cpp index 28f750a..0025667 100644 --- a/libnymea-remoteproxy/proxyclient.cpp +++ b/libnymea-remoteproxy/proxyclient.cpp @@ -1,7 +1,10 @@ #include "proxyclient.h" -ProxyClient::ProxyClient(const QUuid &clientId, QObject *parent) : +namespace remoteproxy { + +ProxyClient::ProxyClient(TransportInterface *interface, const QUuid &clientId, QObject *parent) : QObject(parent), + m_interface(interface), m_clientId(clientId) { @@ -17,7 +20,77 @@ bool ProxyClient::authenticated() const return m_authenticated; } +void ProxyClient::setAuthenticated(bool authenticated) +{ + if (m_authenticated == authenticated) + return; + + m_authenticated = authenticated; + emit authenticatedChanged(m_authenticated); +} + bool ProxyClient::tunnelConnected() const { return m_tunnelConnected; } + +void ProxyClient::setTunnelConnected(bool tunnelConnected) +{ + if (m_tunnelConnected == tunnelConnected) + return; + + m_tunnelConnected = tunnelConnected; + emit tunnelConnectedChanged(m_tunnelConnected); +} + +TransportInterface *ProxyClient::interface() const +{ + return m_interface; +} + +QString ProxyClient::uuid() const +{ + return m_uuid; +} + +void ProxyClient::setUuid(const QString &uuid) +{ + m_uuid = uuid; +} + +QString ProxyClient::name() const +{ + return m_name; +} + +void ProxyClient::setName(const QString &name) +{ + m_name = name; +} + +QString ProxyClient::token() const +{ + return m_token; +} + +void ProxyClient::setToken(const QString &token) +{ + m_token = token; +} + +QDebug operator<<(QDebug debug, ProxyClient *proxyClient) +{ + debug.nospace() << "ProxyClient(" << proxyClient->interface()->serverName(); + debug.nospace() << ", " << proxyClient->clientId().toString() << ") :" << endl; + debug.nospace() << " tunnel: " << proxyClient->tunnelConnected() << endl; + debug.nospace() << " authenticated: " << proxyClient->authenticated() << endl; + if (!proxyClient->name().isEmpty() && !proxyClient->token().isEmpty() && !proxyClient->uuid().isEmpty()) { + debug.nospace() << " name: " << proxyClient->name() << endl; + debug.nospace() << " uuid: " << proxyClient->uuid() << endl; + debug.nospace() << " token: " << proxyClient->token() << endl; + + } + return debug; +} + +} diff --git a/libnymea-remoteproxy/proxyclient.h b/libnymea-remoteproxy/proxyclient.h index e2de8e0..447e605 100644 --- a/libnymea-remoteproxy/proxyclient.h +++ b/libnymea-remoteproxy/proxyclient.h @@ -2,25 +2,58 @@ #define PROXYCLIENT_H #include +#include #include +#include "transportinterface.h" + +namespace remoteproxy { + class ProxyClient : public QObject { Q_OBJECT + public: - explicit ProxyClient(const QUuid &clientId, QObject *parent = nullptr); + explicit ProxyClient(TransportInterface *interface, const QUuid &clientId, QObject *parent = nullptr); QUuid clientId() const; bool authenticated() const; + void setAuthenticated(bool authenticated); + bool tunnelConnected() const; + void setTunnelConnected(bool tunnelConnected); + + TransportInterface *interface() const; + + // Properties from auth request + QString uuid() const; + void setUuid(const QString &uuid); + + QString name() const; + void setName(const QString &name); + + QString token() const; + void setToken(const QString &token); + private: + TransportInterface *m_interface = nullptr; QUuid m_clientId; bool m_authenticated = false; bool m_tunnelConnected = false; + QString m_uuid; + QString m_name; + QString m_token; +signals: + void authenticatedChanged(bool authenticated); + void tunnelConnectedChanged(bool tunnelConnected); }; +QDebug operator<< (QDebug debug, ProxyClient *proxyClient); + +} + #endif // PROXYCLIENT_H diff --git a/libnymea-remoteproxy/proxyconfiguration.cpp b/libnymea-remoteproxy/proxyconfiguration.cpp index a04af30..84cfc55 100644 --- a/libnymea-remoteproxy/proxyconfiguration.cpp +++ b/libnymea-remoteproxy/proxyconfiguration.cpp @@ -1,6 +1,10 @@ #include "proxyconfiguration.h" +namespace remoteproxy { + ProxyConfiguration::ProxyConfiguration(QObject *parent) : QObject(parent) { } + +} diff --git a/libnymea-remoteproxy/proxyconfiguration.h b/libnymea-remoteproxy/proxyconfiguration.h index 4ec1df0..24329b5 100644 --- a/libnymea-remoteproxy/proxyconfiguration.h +++ b/libnymea-remoteproxy/proxyconfiguration.h @@ -3,6 +3,8 @@ #include +namespace remoteproxy { + class ProxyConfiguration : public QObject { Q_OBJECT @@ -15,4 +17,6 @@ public slots: }; +} + #endif // PROXYCONFIGURATION_H diff --git a/libnymea-remoteproxy/proxyserver.cpp b/libnymea-remoteproxy/proxyserver.cpp index 248dc73..7d6c556 100644 --- a/libnymea-remoteproxy/proxyserver.cpp +++ b/libnymea-remoteproxy/proxyserver.cpp @@ -1,11 +1,21 @@ #include "proxyserver.h" #include "loggingcategories.h" +#include + +namespace remoteproxy { + ProxyServer::ProxyServer(QObject *parent) : QObject(parent) { + m_jsonRpcServer = new JsonRpcServer(this); } +ProxyServer::~ProxyServer() +{ + qCDebug(dcProxyServer()) << "Shutting down proxy server"; +} + void ProxyServer::registerTransportInterface(TransportInterface *interface) { qCDebug(dcProxyServer()) << "Register transport interface" << interface->serverName(); @@ -22,12 +32,23 @@ void ProxyServer::registerTransportInterface(TransportInterface *interface) m_transportInterfaces.append(interface); } +void ProxyServer::sendResponse(TransportInterface *interface, const QUuid &clientId, const QVariantMap &response) +{ + QByteArray data = QJsonDocument::fromVariant(response).toJson(QJsonDocument::Compact); + qCDebug(dcJsonRpcTraffic()) << "Sending data:" << data; + interface->sendData(clientId, data); +} + void ProxyServer::onClientConnected(const QUuid &clientId) { TransportInterface *interface = static_cast(sender()); + qCDebug(dcProxyServer()) << "New client connected" << interface->serverName() << clientId.toString(); - m_unauthenticatedClients.append(clientId); + ProxyClient *proxyClient = new ProxyClient(interface, clientId, this); + m_proxyClients.insert(clientId, proxyClient); + + m_jsonRpcServer->registerClient(proxyClient); } void ProxyServer::onClientDisconnected(const QUuid &clientId) @@ -35,14 +56,33 @@ void ProxyServer::onClientDisconnected(const QUuid &clientId) TransportInterface *interface = static_cast(sender()); qCDebug(dcProxyServer()) << "Client disconnected" << interface->serverName() << clientId.toString(); - // Clean up this client since it does not exist any more - m_unauthenticatedClients.removeAll(clientId); + if (m_proxyClients.contains(clientId)) { + ProxyClient *proxyClient = m_proxyClients.take(clientId); + m_jsonRpcServer->unregisterClient(proxyClient); + proxyClient->deleteLater(); + // Check if client is in tunnel and clean up tunnel + // Disconnect also the other tunnel client + + } + + // TODO: Clean up this client since it does not exist any more } void ProxyServer::onClientDataAvailable(const QUuid &clientId, const QByteArray &data) { - TransportInterface *interface = static_cast(sender()); - qCDebug(dcProxyServer()) << "Client data available" << interface->serverName() << clientId.toString() << qUtf8Printable(data); + ProxyClient *proxyClient = m_proxyClients.value(clientId); + if (!proxyClient) { + qCWarning(dcProxyServer()) << "Could not find client for uuid" << clientId; + return; + } + + qCDebug(dcProxyServer()) << "Client data available" << proxyClient << qUtf8Printable(data); + if (proxyClient->tunnelConnected()) { + // Pipe the data + } else { + // Pipe data into json rpc server + m_jsonRpcServer->processData(proxyClient, data); + } } void ProxyServer::startServer() @@ -60,3 +100,5 @@ void ProxyServer::stopServer() interface->stopServer(); } } + +} diff --git a/libnymea-remoteproxy/proxyserver.h b/libnymea-remoteproxy/proxyserver.h index 870337e..aa3f45e 100644 --- a/libnymea-remoteproxy/proxyserver.h +++ b/libnymea-remoteproxy/proxyserver.h @@ -2,22 +2,32 @@ #define PROXYSERVER_H #include +#include #include #include "proxyclient.h" +#include "jsonrpcserver.h" #include "transportinterface.h" +namespace remoteproxy { + class ProxyServer : public QObject { Q_OBJECT public: explicit ProxyServer(QObject *parent = nullptr); + ~ProxyServer(); void registerTransportInterface(TransportInterface *interface); private: + JsonRpcServer *m_jsonRpcServer = nullptr; QList m_transportInterfaces; - QList m_unauthenticatedClients; + + QHash m_proxyClients; + QHash m_tunnels; + + void sendResponse(TransportInterface *interface, const QUuid &clientId, const QVariantMap &response = QVariantMap()); private slots: void onClientConnected(const QUuid &clientId); @@ -30,4 +40,6 @@ public slots: }; +} + #endif // PROXYSERVER_H diff --git a/libnymea-remoteproxy/transportinterface.cpp b/libnymea-remoteproxy/transportinterface.cpp index d5b0c12..31445ab 100644 --- a/libnymea-remoteproxy/transportinterface.cpp +++ b/libnymea-remoteproxy/transportinterface.cpp @@ -1,5 +1,7 @@ #include "transportinterface.h" +namespace remoteproxy { + TransportInterface::TransportInterface(QObject *parent) : QObject(parent) { @@ -15,3 +17,5 @@ TransportInterface::~TransportInterface() { } + +} diff --git a/libnymea-remoteproxy/transportinterface.h b/libnymea-remoteproxy/transportinterface.h index 8de7bc7..3137ef2 100644 --- a/libnymea-remoteproxy/transportinterface.h +++ b/libnymea-remoteproxy/transportinterface.h @@ -3,6 +3,8 @@ #include +namespace remoteproxy { + class TransportInterface : public QObject { Q_OBJECT @@ -15,6 +17,8 @@ public: virtual void sendData(const QUuid &clientId, const QByteArray &data) = 0; virtual void sendData(const QList &clients, const QByteArray &data) = 0; + virtual void killClientConnection(const QUuid &clientId) = 0; + signals: void clientConnected(const QUuid &clientId); void clientDisconnected(const QUuid &clientId); @@ -29,4 +33,6 @@ public slots: }; +} + #endif // TRANSPORTINTERFACE_H diff --git a/libnymea-remoteproxy/websocketserver.cpp b/libnymea-remoteproxy/websocketserver.cpp index 325f03f..9796f9c 100644 --- a/libnymea-remoteproxy/websocketserver.cpp +++ b/libnymea-remoteproxy/websocketserver.cpp @@ -3,6 +3,8 @@ #include +namespace remoteproxy { + WebSocketServer::WebSocketServer(const QSslConfiguration &sslConfiguration, QObject *parent) : TransportInterface(parent), m_sslConfiguration(sslConfiguration) @@ -54,6 +56,16 @@ void WebSocketServer::sendData(const QList &clients, const QByteArray &da } } +void WebSocketServer::killClientConnection(const QUuid &clientId) +{ + QWebSocket *client = m_clientList.value(clientId); + if (!client) + return; + + qCWarning(dcWebSocketServer()) << "Kill client connection" << clientId.toString(); + client->close(QWebSocketProtocol::CloseCodeBadOperation); +} + void WebSocketServer::onClientConnected() { // Got a new client connected @@ -69,7 +81,7 @@ void WebSocketServer::onClientConnected() // Create new uuid for this connection QUuid clientId = QUuid::createUuid(); - qCDebug(dcWebSocketServer()) << "New client connected:" << client->peerAddress().toString() << clientId; + qCDebug(dcWebSocketServer()) << "New client connected:" << client << client->peerAddress().toString() << clientId.toString(); // Append the new client to the client list m_clientList.insert(clientId, client); @@ -88,7 +100,7 @@ void WebSocketServer::onClientDisconnected() QWebSocket *client = static_cast(sender()); QUuid clientId = m_clientList.key(client); - qCDebug(dcWebSocketServer()) << "Client disconnected:" << client->peerAddress().toString() << clientId; + qCDebug(dcWebSocketServer()) << "Client disconnected:" << client << client->peerAddress().toString() << clientId.toString(); m_clientList.take(clientId)->deleteLater(); emit clientDisconnected(clientId); @@ -159,3 +171,5 @@ bool WebSocketServer::stopServer() return true; } + +} diff --git a/libnymea-remoteproxy/websocketserver.h b/libnymea-remoteproxy/websocketserver.h index 8d10769..4bffe68 100644 --- a/libnymea-remoteproxy/websocketserver.h +++ b/libnymea-remoteproxy/websocketserver.h @@ -10,6 +10,8 @@ #include "transportinterface.h" +namespace remoteproxy { + class WebSocketServer : public TransportInterface { Q_OBJECT @@ -27,6 +29,8 @@ public: void sendData(const QUuid &clientId, const QByteArray &data) override; void sendData(const QList &clients, const QByteArray &data) override; + void killClientConnection(const QUuid &clientId) override; + private: QUrl m_serverUrl; QWebSocketServer *m_server = nullptr; @@ -50,4 +54,6 @@ public slots: }; +} + #endif // WEBSOCKETSERVER_H diff --git a/libnymea-remoteproxyclient/jsonrpcclient.cpp b/libnymea-remoteproxyclient/jsonrpcclient.cpp new file mode 100644 index 0000000..c1d7bdd --- /dev/null +++ b/libnymea-remoteproxyclient/jsonrpcclient.cpp @@ -0,0 +1,6 @@ +#include "jsonrpcclient.h" + +JsonRpcClient::JsonRpcClient(QObject *parent) : QObject(parent) +{ + +} diff --git a/libnymea-remoteproxyclient/jsonrpcclient.h b/libnymea-remoteproxyclient/jsonrpcclient.h new file mode 100644 index 0000000..4ff81b9 --- /dev/null +++ b/libnymea-remoteproxyclient/jsonrpcclient.h @@ -0,0 +1,17 @@ +#ifndef JSONRPCCLIENT_H +#define JSONRPCCLIENT_H + +#include + +class JsonRpcClient : public QObject +{ + Q_OBJECT +public: + explicit JsonRpcClient(QObject *parent = nullptr); + +signals: + +public slots: +}; + +#endif // JSONRPCCLIENT_H \ No newline at end of file diff --git a/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pro b/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pro index 667855d..87eb294 100644 --- a/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pro +++ b/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pro @@ -5,12 +5,16 @@ TARGET = nymea-remoteproxyclient HEADERS += \ remoteproxyconnector.h \ - websocketconnector.h + websocketconnector.h \ + socketconnector.h \ + jsonrpcclient.h SOURCES += \ remoteproxyconnector.cpp \ - websocketconnector.cpp + websocketconnector.cpp \ + socketconnector.cpp \ + jsonrpcclient.cpp # install header file with relative subdirectory diff --git a/libnymea-remoteproxyclient/remoteproxyconnector.cpp b/libnymea-remoteproxyclient/remoteproxyconnector.cpp index af24c11..2b5ce7c 100644 --- a/libnymea-remoteproxyclient/remoteproxyconnector.cpp +++ b/libnymea-remoteproxyclient/remoteproxyconnector.cpp @@ -119,6 +119,12 @@ bool RemoteProxyConnector::sendData(const QByteArray &data) return false; } + if (!isConnected()) { + qCWarning(dcRemoteProxyConnector()) << "Not connected"; + return false; + } + + qCDebug(dcRemoteProxyConnector()) << "Sending data:" << data; qint64 dataSendCount = m_webSocket->sendTextMessage(QString::fromUtf8(data)); if (dataSendCount != data.count()) { qCWarning(dcRemoteProxyConnector()) << "Could not send all data to" << serverUrl().toString(); diff --git a/libnymea-remoteproxyclient/socketconnector.cpp b/libnymea-remoteproxyclient/socketconnector.cpp new file mode 100644 index 0000000..af9c170 --- /dev/null +++ b/libnymea-remoteproxyclient/socketconnector.cpp @@ -0,0 +1,11 @@ +#include "socketconnector.h" + +SocketConnector::SocketConnector(QObject *parent) : QObject(parent) +{ + +} + +SocketConnector::~SocketConnector() +{ + +} diff --git a/libnymea-remoteproxyclient/socketconnector.h b/libnymea-remoteproxyclient/socketconnector.h new file mode 100644 index 0000000..2d6d840 --- /dev/null +++ b/libnymea-remoteproxyclient/socketconnector.h @@ -0,0 +1,29 @@ +#ifndef SOCKETCONNECTOR_H +#define SOCKETCONNECTOR_H + +#include +#include + +class SocketConnector : public QObject +{ + Q_OBJECT +public: + explicit SocketConnector(QObject *parent = nullptr); + virtual ~SocketConnector() = 0; + + virtual void sendData(const QByteArray &data) = 0; + virtual bool isConnected() = 0; + +signals: + void connected(); + void disconnected(); + + void dataReceived(const QByteArray &data); + +public slots: + virtual void connectServer(const QHostAddress &address, quint16 port) = 0; + virtual void disconnectServer() = 0; + +}; + +#endif // SOCKETCONNECTOR_H diff --git a/libnymea-remoteproxyclient/websocketconnector.cpp b/libnymea-remoteproxyclient/websocketconnector.cpp index d9f9cc2..cf6c1fc 100644 --- a/libnymea-remoteproxyclient/websocketconnector.cpp +++ b/libnymea-remoteproxyclient/websocketconnector.cpp @@ -1,6 +1,12 @@ #include "websocketconnector.h" -WebSocketConnector::WebSocketConnector(QObject *parent) : QObject(parent) +WebSocketConnector::WebSocketConnector(QObject *parent) : + SocketConnector(parent) { } + +void WebSocketConnector::sendData(const QByteArray &data) +{ + Q_UNUSED(data) +} diff --git a/libnymea-remoteproxyclient/websocketconnector.h b/libnymea-remoteproxyclient/websocketconnector.h index 11f84ab..cdebdba 100644 --- a/libnymea-remoteproxyclient/websocketconnector.h +++ b/libnymea-remoteproxyclient/websocketconnector.h @@ -3,15 +3,19 @@ #include -class WebSocketConnector : public QObject +#include "socketconnector.h" + +class WebSocketConnector : public SocketConnector { Q_OBJECT public: explicit WebSocketConnector(QObject *parent = nullptr); + void sendData(const QByteArray &data) override; + signals: public slots: }; -#endif // WEBSOCKETCONNECTOR_H \ No newline at end of file +#endif // WEBSOCKETCONNECTOR_H diff --git a/server/main.cpp b/server/main.cpp index 05379a2..27017c1 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -21,6 +21,7 @@ #include "loggingcategories.h" #include "authentication/awsauthenticator.h" +using namespace remoteproxy; static QHash s_loggingFilters; diff --git a/tests/mockauthenticator.cpp b/tests/mockauthenticator.cpp index 19cbde7..2163369 100644 --- a/tests/mockauthenticator.cpp +++ b/tests/mockauthenticator.cpp @@ -1,25 +1,59 @@ +#include "proxyclient.h" #include "mockauthenticator.h" #include "loggingcategories.h" #include "authentication/authenticationreply.h" - MockAuthenticator::MockAuthenticator(QObject *parent) : Authenticator(parent) { - m_timer = new QTimer(this); - m_timer->setSingleShot(true); - connect(m_timer, &QTimer::timeout, this, &MockAuthenticator::onTimeout); } -void MockAuthenticator::onTimeout() +QString MockAuthenticator::name() const { - + return "Mock authenticator"; } -AuthenticationReply *MockAuthenticator::authenticate(const QUuid &clientId, const QString &token) +void MockAuthenticator::setTimeoutDuration(int timeout) { - qCDebug(dcAuthenticator()) << "MockAuthenticator: Start authentication for" << clientId << "using token" << token; - AuthenticationReply *reply = new AuthenticationReply(clientId, token, this); - return reply; + m_timeoutDuration = timeout; } + +void MockAuthenticator::setExpectedAuthenticationError(Authenticator::AuthenticationError error) +{ + m_expectedError = error; +} + +void MockAuthenticator::replyFinished() +{ + MockAuthenticationReply *reply = static_cast(sender()); + reply->deleteLater(); + + qCDebug(dcAuthenticator()) << name() << "Authentication finished."; + + setReplyError(reply->authenticationReply(), reply->error()); + setReplyFinished(reply->authenticationReply()); +} + + +AuthenticationReply *MockAuthenticator::authenticate(ProxyClient *proxyClient) +{ + qCDebug(dcAuthenticator()) << name() << "Start authentication for" << proxyClient << "using token" << proxyClient->token(); + + AuthenticationReply *authenticationReply = createAuthenticationReply(proxyClient, this); + + MockAuthenticationReply *reply = new MockAuthenticationReply(m_timeoutDuration, m_expectedError, authenticationReply, this); + connect(reply, &MockAuthenticationReply::finished, this, &MockAuthenticator::replyFinished); + + return authenticationReply; +} + +MockAuthenticationReply::MockAuthenticationReply(int timeout, Authenticator::AuthenticationError error, AuthenticationReply *authenticationReply, QObject *parent) : + QObject(parent), + m_error(error), + m_authenticationReply(authenticationReply) + +{ + QTimer::singleShot(timeout, this, &MockAuthenticationReply::finished); +} + diff --git a/tests/mockauthenticator.h b/tests/mockauthenticator.h index 00f5d24..91c8869 100644 --- a/tests/mockauthenticator.h +++ b/tests/mockauthenticator.h @@ -6,25 +6,47 @@ #include "authentication/authenticator.h" +using namespace remoteproxy; + +class MockAuthenticationReply : public QObject +{ + Q_OBJECT +public: + explicit MockAuthenticationReply(int timeout, Authenticator::AuthenticationError error, AuthenticationReply *authenticationReply, QObject *parent = nullptr); + + AuthenticationReply *authenticationReply() const { return m_authenticationReply; } + Authenticator::AuthenticationError error() const { return m_error; } + +private: + Authenticator::AuthenticationError m_error; + AuthenticationReply *m_authenticationReply; + +signals: + void finished(); + +}; + + class MockAuthenticator : public Authenticator { Q_OBJECT public: explicit MockAuthenticator(QObject *parent = nullptr); + QString name() const override; + void setTimeoutDuration(int timeout); - void setExpectedAuthenticationError(Authenticator::AuthenticationError error); + void setExpectedAuthenticationError(Authenticator::AuthenticationError error = AuthenticationErrorNoError); private: - QTimer * m_timer = nullptr; int m_timeoutDuration = 1000; Authenticator::AuthenticationError m_expectedError; private slots: - void onTimeout(); + void replyFinished(); public slots: - AuthenticationReply *authenticate(const QUuid &clientId, const QString &token) override; + AuthenticationReply *authenticate(ProxyClient *proxyClient) override; }; #endif // MOCKAUTHENTICATOR_H diff --git a/tests/nymea-remoteproxy-tests.cpp b/tests/nymea-remoteproxy-tests.cpp index dd295da..b2ee06c 100644 --- a/tests/nymea-remoteproxy-tests.cpp +++ b/tests/nymea-remoteproxy-tests.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include RemoteProxyTests::RemoteProxyTests(QObject *parent) : @@ -40,6 +41,7 @@ RemoteProxyTests::RemoteProxyTests(QObject *parent) : void RemoteProxyTests::cleanUpEngine() { if (Engine::exists()) { + Engine::instance()->stop(); Engine::instance()->destroy(); QVERIFY(!Engine::exists()); } @@ -48,16 +50,22 @@ void RemoteProxyTests::cleanUpEngine() void RemoteProxyTests::restartEngine() { cleanUpEngine(); - - QVERIFY(Engine::instance() != nullptr); - QVERIFY(Engine::exists()); + startEngine(); } void RemoteProxyTests::startEngine() { if (!Engine::exists()) { - Engine::instance(); + QString serverName = "nymea-remoteproxy-testserver"; + Engine::instance()->setAuthenticator(m_authenticator); + Engine::instance()->setAuthenticationServerUrl(QUrl("https://localhost")); + Engine::instance()->setServerName(serverName); + Engine::instance()->setWebSocketServerPort(m_port); + Engine::instance()->setWebSocketServerHostAddress(QHostAddress::LocalHost); + Engine::instance()->setSslConfiguration(m_sslConfiguration); + QVERIFY(Engine::exists()); + QVERIFY(Engine::instance()->serverName() == serverName); } } @@ -65,16 +73,7 @@ void RemoteProxyTests::startServer() { startEngine(); - QString serverName = "nymea-remoteproxy-testserver"; - Engine::instance()->setAuthenticationServerUrl(QUrl("https://localhost")); - Engine::instance()->setServerName(serverName); - Engine::instance()->setAuthenticator(m_authenticator); - Engine::instance()->setWebSocketServerPort(m_port); - Engine::instance()->setWebSocketServerHostAddress(QHostAddress::LocalHost); - Engine::instance()->setSslConfiguration(m_sslConfiguration); Engine::instance()->start(); - - QVERIFY(Engine::instance()->serverName() == serverName); QVERIFY(Engine::instance()->running()); QVERIFY(Engine::instance()->webSocketServer()->running()); } @@ -88,6 +87,99 @@ void RemoteProxyTests::stopServer() QVERIFY(!Engine::instance()->running()); } +QVariant RemoteProxyTests::invokeApiCall(const QString &method, const QVariantMap params, bool remainsConnected) +{ + Q_UNUSED(remainsConnected) + + QVariantMap request; + request.insert("id", m_commandCounter); + request.insert("method", method); + request.insert("params", params); + QJsonDocument jsonDoc = QJsonDocument::fromVariant(request); + + QWebSocket *socket = new QWebSocket("proxy-testclient", QWebSocketProtocol::Version13); + connect(socket, &QWebSocket::sslErrors, this, &RemoteProxyTests::sslErrors); + QSignalSpy spyConnection(socket, SIGNAL(connected())); + socket->open(Engine::instance()->webSocketServer()->serverUrl()); + spyConnection.wait(); + if (spyConnection.count() == 0) { + return QVariant(); + } + +// QSignalSpy disconnectedSpy(socket, SIGNAL(disconnected())); + QSignalSpy dataSpy(socket, SIGNAL(textMessageReceived(QString))); + socket->sendTextMessage(QString(jsonDoc.toJson(QJsonDocument::Compact))); + dataSpy.wait(); +// if (remainsConnected) { +// disconnectedSpy.wait(1000); +// if (socket->state() != QAbstractSocket::UnconnectedState) { +// qWarning() << "!!!!!!!!!!!!! socket still connected but should be disconnected!"; +// } +// } else { +// disconnectedSpy.wait(); +// if (socket->state() != QAbstractSocket::ConnectedState) { +// qWarning() << "!!!!!!!!!!!!! socket not connected but should be!"; +// } +// } + + socket->close(); + socket->deleteLater(); + + for (int i = 0; i < dataSpy.count(); i++) { + // Make sure the response it a valid JSON string + QJsonParseError error; + jsonDoc = QJsonDocument::fromJson(dataSpy.at(i).last().toByteArray(), &error); + if (error.error != QJsonParseError::NoError) { + qWarning() << "JSON parser error" << error.errorString(); + return QVariant(); + } + QVariantMap response = jsonDoc.toVariant().toMap(); + + // skip notifications + if (response.contains("notification")) + continue; + + if (response.value("id").toInt() == m_commandCounter) { + m_commandCounter++; + return jsonDoc.toVariant(); + } + } + m_commandCounter++; + return QVariant(); +} + +QVariant RemoteProxyTests::injectSocketData(const QByteArray &data) +{ + QWebSocket *socket = new QWebSocket("proxy-testclient", QWebSocketProtocol::Version13); + connect(socket, &QWebSocket::sslErrors, this, &RemoteProxyTests::sslErrors); + QSignalSpy spyConnection(socket, SIGNAL(connected())); + socket->open(Engine::instance()->webSocketServer()->serverUrl()); + spyConnection.wait(); + if (spyConnection.count() == 0) { + return QVariant(); + } + + QSignalSpy spy(socket, SIGNAL(textMessageReceived(QString))); + socket->sendTextMessage(QString(data)); + spy.wait(); + + socket->close(); + socket->deleteLater(); + + for (int i = 0; i < spy.count(); i++) { + // Make sure the response it a valid JSON string + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(spy.at(i).last().toByteArray(), &error); + if (error.error != QJsonParseError::NoError) { + qWarning() << "JSON parser error" << error.errorString(); + return QVariant(); + } + return jsonDoc.toVariant(); + } + m_commandCounter++; + return QVariant(); +} + void RemoteProxyTests::initTestCase() { qRegisterMetaType(); @@ -104,10 +196,8 @@ void RemoteProxyTests::cleanupTestCase() void RemoteProxyTests::startStopServer() { - startEngine(); startServer(); stopServer(); - cleanUpEngine(); } void RemoteProxyTests::webserverConnectionBlocked() @@ -134,7 +224,6 @@ void RemoteProxyTests::webserverConnectionBlocked() // Clean up stopServer(); - cleanUpEngine(); } void RemoteProxyTests::webserverSocketVersion() @@ -153,20 +242,11 @@ void RemoteProxyTests::webserverSocketVersion() // Clean up stopServer(); - cleanUpEngine(); } void RemoteProxyTests::webserverConnection() { - // Start the server - startServer(); - - - - // Clean up - stopServer(); - cleanUpEngine(); } void RemoteProxyTests::sslConfigurations() @@ -199,25 +279,143 @@ void RemoteProxyTests::sslConfigurations() QVERIFY(!connector->isConnected()); connector->deleteLater(); - Engine::instance()->stop(); + stopServer(); +} + +void RemoteProxyTests::getIntrospect() +{ + // Start the server + startServer(); + + QVariant response = invokeApiCall("RemoteProxy.Introspect"); + qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); + + // Clean up + stopServer(); +} + +void RemoteProxyTests::getHello() +{ + // Start the server + startServer(); + + QVariant response = invokeApiCall("RemoteProxy.Hello"); + qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); + + // Clean up + stopServer(); +} + +void RemoteProxyTests::apiBasicCalls_data() +{ + QTest::addColumn("data"); + QTest::addColumn("valid"); + + QTest::newRow("valid call") << QByteArray("{\"id\":42, \"method\":\"RemoteProxy.Hello\"}") << true; + QTest::newRow("missing id") << QByteArray("{\"method\":\"RemoteProxy.Hello\"}")<< false; + QTest::newRow("missing method") << QByteArray("{\"id\":42}") << false; + QTest::newRow("borked") << QByteArray("{\"id\":42, \"method\":\"RemoteProx")<< false; + QTest::newRow("invalid function") << QByteArray("{\"id\":42, \"method\":\"RemoteProxy.Explode\"}") << false; + QTest::newRow("invalid namespace") << QByteArray("{\"id\":42, \"method\":\"ProxyRemote.Hello\"}") << false; + QTest::newRow("missing dot") << QByteArray("{\"id\":42, \"method\":\"RemoteProxyHello\"}") << false; + QTest::newRow("invalid params") << QByteArray("{\"id\":42, \"method\":\"RemoteProxy.Hello\", \"params\":{\"törööö\":\"chooo-chooo\"}}") << false; +} + +void RemoteProxyTests::apiBasicCalls() +{ + QFETCH(QByteArray, data); + QFETCH(bool, valid); + + // Start the server + startServer(); + + QVariant response = injectSocketData(data); + if (valid) { + QVERIFY2(response.toMap().value("status").toString() == "success", "Call wasn't parsed correctly by nymea."); + } + + // Clean up + stopServer(); +} + +void RemoteProxyTests::authenticate_data() +{ + QTest::addColumn("uuid"); + QTest::addColumn("name"); + QTest::addColumn("token"); + + QTest::addColumn("timeout"); + QTest::addColumn("expectedError"); + + QTest::newRow("success") << QUuid::createUuid().toString() << "Testclient, hello form the test!" << "Hh5JrkdFVstVVyCnfE3vVT3zG_JTacXEhPLZhCei" + << 100 << Authenticator::AuthenticationErrorNoError; + + QTest::newRow("failed") << QUuid::createUuid().toString() << "Testclient, hello form the test!" << "invalid_token_42" + << 100 << Authenticator::AuthenticationErrorAuthenticationFailed; + + QTest::newRow("not responding") << QUuid::createUuid().toString() << "Testclient, hello form the test!" << "invalid_token_42" + << 200 << Authenticator::AuthenticationErrorAuthenticationServerNotResponding; + + QTest::newRow("aborted") << QUuid::createUuid().toString() << "Testclient, hello form the test!" << "invalid_token_42" + << 100 << Authenticator::AuthenticationErrorAborted; + + QTest::newRow("unknown") << QUuid::createUuid().toString() << "Testclient, hello form the test!" << "invalid_token_42" + << 100 << Authenticator::AuthenticationErrorUnknown; + } void RemoteProxyTests::authenticate() { -// // Start the server -// startServer(); + QFETCH(QString, uuid); + QFETCH(QString, name); + QFETCH(QString, token); + QFETCH(int, timeout); + QFETCH(Authenticator::AuthenticationError, expectedError); -// // Connect to the server -// RemoteProxyConnector *connector = new RemoteProxyConnector(this); -// connector->setInsecureConnection(true); + // Start the server + startServer(); -// QSignalSpy spy(connector, &RemoteProxyConnector::connected); -// connector->connectServer(QHostAddress::LocalHost, m_port); -// spy.wait(); + // Configure result + m_authenticator->setExpectedAuthenticationError(expectedError); + m_authenticator->setTimeoutDuration(timeout); -// connector->disconnectServer(); -// connector->deleteLater(); -// Engine::instance()->stop(); + // Create request + QVariantMap params; + params.insert("uuid", uuid); + params.insert("name", name); + params.insert("token", token); + + QVariant response = invokeApiCall("Authentication.Authenticate", params); + qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); + verifyAuthenticationError(response, expectedError); + + // Clean up + stopServer(); +} + +void RemoteProxyTests::timeout() +{ + // Start the server + startServer(); + + // Configure result + // Start the server + startServer(); + + // Configure result + m_authenticator->setExpectedAuthenticationError(); + m_authenticator->setTimeoutDuration(6000); + + // Create request + QVariantMap params; + params.insert("uuid", "uuid"); + params.insert("name", "name"); + params.insert("token", "token"); + + QVariant response = invokeApiCall("Authentication.Authenticate", params); + qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); + // Clean up + stopServer(); } diff --git a/tests/nymea-remoteproxy-tests.h b/tests/nymea-remoteproxy-tests.h index 81e3531..16b5cf8 100644 --- a/tests/nymea-remoteproxy-tests.h +++ b/tests/nymea-remoteproxy-tests.h @@ -5,12 +5,16 @@ #include #include #include +#include #include #include #include +#include "jsonrpc/jsontypes.h" #include "mockauthenticator.h" +using namespace remoteproxy; + class RemoteProxyTests : public QObject { Q_OBJECT @@ -23,25 +27,71 @@ private: QSslConfiguration m_sslConfiguration; MockAuthenticator *m_authenticator = nullptr; + int m_commandCounter = 0; + void cleanUpEngine(); void restartEngine(); void startEngine(); void startServer(); void stopServer(); + QVariant invokeApiCall(const QString &method, const QVariantMap params = QVariantMap(), bool remainsConnected = true); + QVariant injectSocketData(const QByteArray &data); + protected slots: void initTestCase(); void cleanupTestCase(); - private slots: + // Basic stuff void startStopServer(); + + // WebSocket connection void webserverConnectionBlocked(); void webserverSocketVersion(); void webserverConnection(); void sslConfigurations(); + + // Api + void getIntrospect(); + void getHello(); + + void apiBasicCalls_data(); + void apiBasicCalls(); + + void authenticate_data(); void authenticate(); + void timeout(); + +public slots: + void sslErrors(const QList &) { + QWebSocket *socket = static_cast(sender()); + socket->ignoreSslErrors(); + } + + inline void verifyError(const QVariant &response, const QString &fieldName, const QString &error) + { + QJsonDocument jsonDoc = QJsonDocument::fromVariant(response); + QVERIFY2(response.toMap().value("status").toString() == QString("success"), + QString("\nExpected status: \"success\"\nGot: %2\nFull message: %3") + .arg(response.toMap().value("status").toString()) + .arg(jsonDoc.toJson().data()) + .toLatin1().data()); + QVERIFY2(response.toMap().value("params").toMap().value(fieldName).toString() == error, + QString("\nExpected: %1\nGot: %2\nFull message: %3\n") + .arg(error) + .arg(response.toMap().value("params").toMap().value(fieldName).toString()) + .arg(jsonDoc.toJson().data()) + .toLatin1().data()); + } + + + inline void verifyAuthenticationError(const QVariant &response, Authenticator::AuthenticationError error = Authenticator::AuthenticationErrorNoError) { + verifyError(response, "authenticationError", JsonTypes::authenticationErrorToString(error)); + } + + }; #endif // NYMEA_REMOTEPROXY_TESTS_H