Implement basic functionality of JSON RPC server
This commit is contained in:
parent
f20173df9f
commit
de6c757a26
@ -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.
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -4,6 +4,9 @@
|
||||
#include <QUuid>
|
||||
#include <QObject>
|
||||
|
||||
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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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<AuthenticationReply *>(sender());
|
||||
//JsonReply *jsonReply = m_runningAuthentications.take(authReply);
|
||||
AuthenticationReply *authenticationReply = static_cast<AuthenticationReply *>(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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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<AuthenticationReply *, JsonReply *> m_runningAuthentications;
|
||||
@ -26,4 +28,6 @@ private slots:
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // AUTHENTICATIONHANDLER_H
|
||||
|
||||
@ -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<JsonHandler*>(this), data);
|
||||
@ -115,3 +124,5 @@ JsonReply *JsonHandler::createAsyncReply(const QString &method) const
|
||||
{
|
||||
return JsonReply::createAsyncReply(const_cast<JsonHandler*>(this), method);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -7,6 +7,10 @@
|
||||
#include <QVariantMap>
|
||||
#include <QMetaMethod>
|
||||
|
||||
#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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -5,6 +5,8 @@
|
||||
|
||||
#include "loggingcategories.h"
|
||||
|
||||
namespace remoteproxy {
|
||||
|
||||
bool JsonTypes::s_initialized = false;
|
||||
QString JsonTypes::s_lastError;
|
||||
|
||||
@ -198,6 +200,9 @@ QPair<bool, QString> JsonTypes::validateList(const QVariantList &templateList, c
|
||||
QPair<bool, QString> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -4,8 +4,11 @@
|
||||
#include "jsonrpc/jsontypes.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonParseError>
|
||||
#include <QCoreApplication>
|
||||
|
||||
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<QString, JsonHandler *> 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<JsonReply *>(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<bool, QString> 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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -2,11 +2,16 @@
|
||||
#define JSONRPCSERVER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QVariant>
|
||||
|
||||
#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<QString, JsonHandler *> 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<TransportInterface *> m_interfaces;
|
||||
QHash<QString, JsonHandler *> m_handlers;
|
||||
QHash<JsonReply *, TransportInterface *> m_asyncReplies;
|
||||
QHash<QUuid, TransportInterface*> m_clientTransports;
|
||||
QHash<QString, JsonReply*> m_pairingRequests;
|
||||
|
||||
QHash<JsonReply *, ProxyClient *> m_asyncReplies;
|
||||
QList<ProxyClient *> 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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -2,25 +2,58 @@
|
||||
#define PROXYCLIENT_H
|
||||
|
||||
#include <QUuid>
|
||||
#include <QDebug>
|
||||
#include <QObject>
|
||||
|
||||
#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
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
#include "proxyconfiguration.h"
|
||||
|
||||
namespace remoteproxy {
|
||||
|
||||
ProxyConfiguration::ProxyConfiguration(QObject *parent) : QObject(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
|
||||
#include <QObject>
|
||||
|
||||
namespace remoteproxy {
|
||||
|
||||
class ProxyConfiguration : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -15,4 +17,6 @@ public slots:
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // PROXYCONFIGURATION_H
|
||||
|
||||
@ -1,11 +1,21 @@
|
||||
#include "proxyserver.h"
|
||||
#include "loggingcategories.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
|
||||
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<TransportInterface *>(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<TransportInterface *>(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<TransportInterface *>(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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -2,22 +2,32 @@
|
||||
#define PROXYSERVER_H
|
||||
|
||||
#include <QUuid>
|
||||
#include <QHash>
|
||||
#include <QObject>
|
||||
|
||||
#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<TransportInterface *> m_transportInterfaces;
|
||||
QList<QUuid> m_unauthenticatedClients;
|
||||
|
||||
QHash<QUuid, ProxyClient *> m_proxyClients;
|
||||
QHash<ProxyClient *, ProxyClient *> 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
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
#include "transportinterface.h"
|
||||
|
||||
namespace remoteproxy {
|
||||
|
||||
TransportInterface::TransportInterface(QObject *parent) :
|
||||
QObject(parent)
|
||||
{
|
||||
@ -15,3 +17,5 @@ TransportInterface::~TransportInterface()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
|
||||
#include <QObject>
|
||||
|
||||
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<QUuid> &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
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
namespace remoteproxy {
|
||||
|
||||
WebSocketServer::WebSocketServer(const QSslConfiguration &sslConfiguration, QObject *parent) :
|
||||
TransportInterface(parent),
|
||||
m_sslConfiguration(sslConfiguration)
|
||||
@ -54,6 +56,16 @@ void WebSocketServer::sendData(const QList<QUuid> &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<QWebSocket *>(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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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<QUuid> &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
|
||||
|
||||
6
libnymea-remoteproxyclient/jsonrpcclient.cpp
Normal file
6
libnymea-remoteproxyclient/jsonrpcclient.cpp
Normal file
@ -0,0 +1,6 @@
|
||||
#include "jsonrpcclient.h"
|
||||
|
||||
JsonRpcClient::JsonRpcClient(QObject *parent) : QObject(parent)
|
||||
{
|
||||
|
||||
}
|
||||
17
libnymea-remoteproxyclient/jsonrpcclient.h
Normal file
17
libnymea-remoteproxyclient/jsonrpcclient.h
Normal file
@ -0,0 +1,17 @@
|
||||
#ifndef JSONRPCCLIENT_H
|
||||
#define JSONRPCCLIENT_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class JsonRpcClient : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit JsonRpcClient(QObject *parent = nullptr);
|
||||
|
||||
signals:
|
||||
|
||||
public slots:
|
||||
};
|
||||
|
||||
#endif // JSONRPCCLIENT_H
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
|
||||
11
libnymea-remoteproxyclient/socketconnector.cpp
Normal file
11
libnymea-remoteproxyclient/socketconnector.cpp
Normal file
@ -0,0 +1,11 @@
|
||||
#include "socketconnector.h"
|
||||
|
||||
SocketConnector::SocketConnector(QObject *parent) : QObject(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
SocketConnector::~SocketConnector()
|
||||
{
|
||||
|
||||
}
|
||||
29
libnymea-remoteproxyclient/socketconnector.h
Normal file
29
libnymea-remoteproxyclient/socketconnector.h
Normal file
@ -0,0 +1,29 @@
|
||||
#ifndef SOCKETCONNECTOR_H
|
||||
#define SOCKETCONNECTOR_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QHostAddress>
|
||||
|
||||
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
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -3,15 +3,19 @@
|
||||
|
||||
#include <QObject>
|
||||
|
||||
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
|
||||
#endif // WEBSOCKETCONNECTOR_H
|
||||
|
||||
@ -21,6 +21,7 @@
|
||||
#include "loggingcategories.h"
|
||||
#include "authentication/awsauthenticator.h"
|
||||
|
||||
using namespace remoteproxy;
|
||||
|
||||
static QHash<QString, bool> s_loggingFilters;
|
||||
|
||||
|
||||
@ -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<MockAuthenticationReply *>(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);
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
#include <QMetaType>
|
||||
#include <QSignalSpy>
|
||||
#include <QWebSocket>
|
||||
#include <QJsonDocument>
|
||||
#include <QWebSocketServer>
|
||||
|
||||
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<RemoteProxyConnector::Error>();
|
||||
@ -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<QByteArray>("data");
|
||||
QTest::addColumn<bool>("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<QString>("uuid");
|
||||
QTest::addColumn<QString>("name");
|
||||
QTest::addColumn<QString>("token");
|
||||
|
||||
QTest::addColumn<int>("timeout");
|
||||
QTest::addColumn<Authenticator::AuthenticationError>("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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -5,12 +5,16 @@
|
||||
#include <QtTest>
|
||||
#include <QSslKey>
|
||||
#include <QObject>
|
||||
#include <QWebSocket>
|
||||
#include <QHostAddress>
|
||||
#include <QSslCertificate>
|
||||
#include <QSslConfiguration>
|
||||
|
||||
#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<QSslError> &) {
|
||||
QWebSocket *socket = static_cast<QWebSocket*>(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
|
||||
|
||||
Reference in New Issue
Block a user