Start implementing JsonRpcServer
This commit is contained in:
parent
43ebf38f30
commit
538cb3d799
@ -110,6 +110,8 @@ The first data a client **must** send to the proxy server is the authentication
|
||||
"id": 0,
|
||||
"method": "Authentication.Authenticate"
|
||||
"params": {
|
||||
"name": "string",
|
||||
"id": "uuid",
|
||||
"token": "tokenstring"
|
||||
}
|
||||
}
|
||||
|
||||
46
libnymea-remoteproxy/authentication/authenticationreply.cpp
Normal file
46
libnymea-remoteproxy/authentication/authenticationreply.cpp
Normal file
@ -0,0 +1,46 @@
|
||||
#include "authenticationreply.h"
|
||||
#include "authentication/authenticator.h"
|
||||
|
||||
AuthenticationReply::AuthenticationReply(const QUuid clientId, const QString &token, QObject *parent) :
|
||||
QObject(parent),
|
||||
m_clientId(clientId),
|
||||
m_token(token)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QUuid AuthenticationReply::clientId() const
|
||||
{
|
||||
return m_clientId;
|
||||
}
|
||||
|
||||
QString AuthenticationReply::token() const
|
||||
{
|
||||
return m_token;
|
||||
}
|
||||
|
||||
bool AuthenticationReply::isTimedOut() const
|
||||
{
|
||||
return m_timedOut;
|
||||
}
|
||||
|
||||
bool AuthenticationReply::isFinished() const
|
||||
{
|
||||
return m_finished;
|
||||
}
|
||||
|
||||
void AuthenticationReply::setError(Authenticator::AuthenticationError error)
|
||||
{
|
||||
m_error = error;
|
||||
}
|
||||
|
||||
void AuthenticationReply::onTimeout()
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
void AuthenticationReply::abort()
|
||||
{
|
||||
|
||||
}
|
||||
49
libnymea-remoteproxy/authentication/authenticationreply.h
Normal file
49
libnymea-remoteproxy/authentication/authenticationreply.h
Normal file
@ -0,0 +1,49 @@
|
||||
#ifndef AUTHENTICATIONREPLY_H
|
||||
#define AUTHENTICATIONREPLY_H
|
||||
|
||||
#include <QUuid>
|
||||
#include <QTimer>
|
||||
#include <QObject>
|
||||
#include <QElapsedTimer>
|
||||
|
||||
#include "authenticator.h"
|
||||
|
||||
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;
|
||||
|
||||
bool isTimedOut() const;
|
||||
bool isFinished() const;
|
||||
|
||||
Authenticator::AuthenticationError error() const;
|
||||
|
||||
private:
|
||||
QUuid m_clientId;
|
||||
QString m_token;
|
||||
QTimer m_timer;
|
||||
|
||||
bool m_timedOut = false;
|
||||
bool m_finished = false;
|
||||
Authenticator::AuthenticationError m_error;
|
||||
|
||||
void setError(Authenticator::AuthenticationError error);
|
||||
|
||||
signals:
|
||||
void finished();
|
||||
|
||||
private slots:
|
||||
void onTimeout();
|
||||
|
||||
public slots:
|
||||
void abort();
|
||||
|
||||
};
|
||||
|
||||
#endif // AUTHENTICATIONREPLY_H
|
||||
13
libnymea-remoteproxy/authentication/authenticator.cpp
Normal file
13
libnymea-remoteproxy/authentication/authenticator.cpp
Normal file
@ -0,0 +1,13 @@
|
||||
#include "authenticator.h"
|
||||
#include "authenticationreply.h"
|
||||
|
||||
Authenticator::Authenticator(QObject *parent) :
|
||||
QObject(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Authenticator::~Authenticator()
|
||||
{
|
||||
|
||||
}
|
||||
29
libnymea-remoteproxy/authentication/authenticator.h
Normal file
29
libnymea-remoteproxy/authentication/authenticator.h
Normal file
@ -0,0 +1,29 @@
|
||||
#ifndef AUTHENTICATOR_H
|
||||
#define AUTHENTICATOR_H
|
||||
|
||||
#include <QUuid>
|
||||
#include <QObject>
|
||||
|
||||
class AuthenticationReply;
|
||||
|
||||
class Authenticator : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum AuthenticationError {
|
||||
AuthenticationErrorNoError,
|
||||
AuthenticationErrorTimeout,
|
||||
AuthenticationErrorAborted,
|
||||
AuthenticationErrorAuthenticationFailed,
|
||||
AuthenticationErrorAuthenticationServerNotResponding
|
||||
};
|
||||
Q_ENUM(AuthenticationError)
|
||||
|
||||
explicit Authenticator(QObject *parent = nullptr);
|
||||
virtual ~Authenticator() = 0;
|
||||
|
||||
public slots:
|
||||
virtual AuthenticationReply *authenticate(const QUuid &clientId, const QString &token) = 0;
|
||||
};
|
||||
|
||||
#endif // AUTHENTICATOR_H
|
||||
17
libnymea-remoteproxy/authentication/awsauthenticator.cpp
Normal file
17
libnymea-remoteproxy/authentication/awsauthenticator.cpp
Normal file
@ -0,0 +1,17 @@
|
||||
#include "awsauthenticator.h"
|
||||
#include "loggingcategories.h"
|
||||
|
||||
AwsAuthenticator::AwsAuthenticator(QObject *parent) :
|
||||
Authenticator(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
AuthenticationReply *AwsAuthenticator::authenticate(const QUuid &clientId, const QString &token)
|
||||
{
|
||||
qCDebug(dcAuthenticator()) << "Start authenticating" << clientId << "using token" << token;
|
||||
|
||||
AuthenticationReply *reply = new AuthenticationReply(clientId, token, this);
|
||||
|
||||
return reply;
|
||||
}
|
||||
21
libnymea-remoteproxy/authentication/awsauthenticator.h
Normal file
21
libnymea-remoteproxy/authentication/awsauthenticator.h
Normal file
@ -0,0 +1,21 @@
|
||||
#ifndef AWSAUTHENTICATOR_H
|
||||
#define AWSAUTHENTICATOR_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "authenticator.h"
|
||||
#include "authenticationreply.h"
|
||||
|
||||
class AwsAuthenticator : public Authenticator
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AwsAuthenticator(QObject *parent = nullptr);
|
||||
~AwsAuthenticator() override = default;
|
||||
|
||||
public slots:
|
||||
AuthenticationReply *authenticate(const QUuid &clientId, const QString &token) override;
|
||||
|
||||
};
|
||||
|
||||
#endif // AWSAUTHENTICATOR_H
|
||||
@ -13,11 +13,6 @@ Engine *Engine::instance()
|
||||
return s_instance;
|
||||
}
|
||||
|
||||
bool Engine::exists()
|
||||
{
|
||||
return s_instance != nullptr;
|
||||
}
|
||||
|
||||
void Engine::destroy()
|
||||
{
|
||||
qCDebug(dcEngine()) << "Destroy server engine";
|
||||
@ -28,12 +23,24 @@ void Engine::destroy()
|
||||
s_instance = nullptr;
|
||||
}
|
||||
|
||||
bool Engine::exists()
|
||||
{
|
||||
return s_instance != nullptr;
|
||||
}
|
||||
|
||||
void Engine::start()
|
||||
{
|
||||
if (!m_running)
|
||||
qCDebug(dcEngine()) << "Start server engine";
|
||||
|
||||
qCDebug(dcEngine()) << "Starting websocket server";
|
||||
// Init proxy server
|
||||
if (m_proxyServer) {
|
||||
delete m_proxyServer;
|
||||
m_proxyServer = nullptr;
|
||||
}
|
||||
|
||||
m_proxyServer = new ProxyServer(this);
|
||||
|
||||
// Init WebSocketServer
|
||||
if (m_webSocketServer) {
|
||||
delete m_webSocketServer;
|
||||
@ -47,7 +54,14 @@ void Engine::start()
|
||||
|
||||
m_webSocketServer = new WebSocketServer(m_sslConfiguration, this);
|
||||
m_webSocketServer->setServerUrl(websocketServerUrl);
|
||||
m_webSocketServer->startServer();
|
||||
|
||||
m_proxyServer->registerTransportInterface(m_webSocketServer);
|
||||
|
||||
// Make sure an authenticator was registered
|
||||
Q_ASSERT_X(m_authenticator != nullptr, "Engine", "There is no authenticator registerd.");
|
||||
|
||||
qCDebug(dcEngine()) << "Starting proxy server";
|
||||
m_proxyServer->startServer();
|
||||
|
||||
setRunning(true);
|
||||
}
|
||||
@ -57,8 +71,13 @@ void Engine::stop()
|
||||
if (m_running)
|
||||
qCDebug(dcEngine()) << "Stop server engine";
|
||||
|
||||
if (m_proxyServer) {
|
||||
m_proxyServer->stopServer();
|
||||
m_proxyServer->deleteLater();
|
||||
m_proxyServer = nullptr;
|
||||
}
|
||||
|
||||
if (m_webSocketServer) {
|
||||
m_webSocketServer->stopServer();
|
||||
m_webSocketServer->deleteLater();
|
||||
m_webSocketServer = nullptr;
|
||||
}
|
||||
@ -71,6 +90,16 @@ bool Engine::running() const
|
||||
return m_running;
|
||||
}
|
||||
|
||||
QString Engine::serverName() const
|
||||
{
|
||||
return m_serverName;
|
||||
}
|
||||
|
||||
void Engine::setServerName(const QString &serverName)
|
||||
{
|
||||
m_serverName = serverName;
|
||||
}
|
||||
|
||||
void Engine::setWebSocketServerHostAddress(const QHostAddress &hostAddress)
|
||||
{
|
||||
qCDebug(dcEngine()) << "Websocket server host address:" << hostAddress;
|
||||
@ -103,6 +132,32 @@ void Engine::setAuthenticationServerUrl(const QUrl &url)
|
||||
m_authenticationServerUrl = url;
|
||||
}
|
||||
|
||||
void Engine::setAuthenticator(Authenticator *authenticator)
|
||||
{
|
||||
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;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
Authenticator *Engine::authenticator() const
|
||||
{
|
||||
return m_authenticator;
|
||||
}
|
||||
|
||||
ProxyServer *Engine::proxyServer() const
|
||||
{
|
||||
return m_proxyServer;
|
||||
}
|
||||
|
||||
WebSocketServer *Engine::webSocketServer() const
|
||||
{
|
||||
return m_webSocketServer;
|
||||
|
||||
@ -6,27 +6,36 @@
|
||||
#include <QHostAddress>
|
||||
#include <QSslConfiguration>
|
||||
|
||||
#include "proxyserver.h"
|
||||
#include "websocketserver.h"
|
||||
#include "authentication/authenticator.h"
|
||||
|
||||
class Engine : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static Engine *instance();
|
||||
static bool exists();
|
||||
void destroy();
|
||||
|
||||
static bool exists();
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
bool running() const;
|
||||
|
||||
QString serverName() const;
|
||||
void setServerName(const QString &serverName);
|
||||
|
||||
void setWebSocketServerHostAddress(const QHostAddress &hostAddress);
|
||||
void setWebSocketServerPort(const quint16 &port);
|
||||
|
||||
void setSslConfiguration(const QSslConfiguration &configuration);
|
||||
void setAuthenticationServerUrl(const QUrl &url);
|
||||
|
||||
void setAuthenticator(Authenticator *authenticator);
|
||||
|
||||
Authenticator *authenticator() const;
|
||||
ProxyServer *proxyServer() const;
|
||||
WebSocketServer *webSocketServer() const;
|
||||
|
||||
private:
|
||||
@ -35,13 +44,15 @@ private:
|
||||
static Engine *s_instance;
|
||||
|
||||
bool m_running = false;
|
||||
QString m_serverName;
|
||||
|
||||
quint16 m_webSocketServerPort = 1212;
|
||||
QHostAddress m_webSocketServerHostAddress = QHostAddress::LocalHost;
|
||||
|
||||
QSslConfiguration m_sslConfiguration;
|
||||
QUrl m_authenticationServerUrl;
|
||||
|
||||
Authenticator *m_authenticator = nullptr;
|
||||
ProxyServer *m_proxyServer = nullptr;
|
||||
WebSocketServer *m_webSocketServer = nullptr;
|
||||
|
||||
void setRunning(bool running);
|
||||
|
||||
51
libnymea-remoteproxy/jsonrpc/authenticationhandler.cpp
Normal file
51
libnymea-remoteproxy/jsonrpc/authenticationhandler.cpp
Normal file
@ -0,0 +1,51 @@
|
||||
#include "jsontypes.h"
|
||||
#include "loggingcategories.h"
|
||||
#include "authenticationhandler.h"
|
||||
|
||||
#include "engine.h"
|
||||
|
||||
AuthenticationHandler::AuthenticationHandler(QObject *parent) :
|
||||
JsonHandler(parent)
|
||||
{
|
||||
// Methods
|
||||
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("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)
|
||||
{
|
||||
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);
|
||||
|
||||
JsonReply *jsonReply = createAsyncReply("Authenticate");
|
||||
AuthenticationReply *authReply = Engine::instance()->authenticator()->authenticate(clientId, clientToken);
|
||||
connect(authReply, &AuthenticationReply::finished, this, &AuthenticationHandler::onAuthenticationFinished);
|
||||
|
||||
m_runningAuthentications.insert(authReply, jsonReply);
|
||||
|
||||
return jsonReply;
|
||||
}
|
||||
|
||||
void AuthenticationHandler::onAuthenticationFinished()
|
||||
{
|
||||
AuthenticationReply *authReply = static_cast<AuthenticationReply *>(sender());
|
||||
JsonReply *jsonReply = m_runningAuthentications.value(authReply);
|
||||
|
||||
}
|
||||
29
libnymea-remoteproxy/jsonrpc/authenticationhandler.h
Normal file
29
libnymea-remoteproxy/jsonrpc/authenticationhandler.h
Normal file
@ -0,0 +1,29 @@
|
||||
#ifndef AUTHENTICATIONHANDLER_H
|
||||
#define AUTHENTICATIONHANDLER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "jsonhandler.h"
|
||||
#include "authentication/authenticationreply.h"
|
||||
|
||||
class AuthenticationHandler : public JsonHandler
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AuthenticationHandler(QObject *parent = nullptr);
|
||||
~AuthenticationHandler() override = default;
|
||||
|
||||
QString name() const override;
|
||||
|
||||
Q_INVOKABLE JsonReply *Authenticate(const QVariantMap ¶ms, const QUuid &clientId);
|
||||
|
||||
private:
|
||||
QHash<AuthenticationReply *, JsonReply *> m_runningAuthentications;
|
||||
|
||||
private slots:
|
||||
void onAuthenticationFinished();
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // AUTHENTICATIONHANDLER_H
|
||||
117
libnymea-remoteproxy/jsonrpc/jsonhandler.cpp
Normal file
117
libnymea-remoteproxy/jsonrpc/jsonhandler.cpp
Normal file
@ -0,0 +1,117 @@
|
||||
#include "jsonhandler.h"
|
||||
#include <QMetaMethod>
|
||||
#include <QDebug>
|
||||
#include <QRegExp>
|
||||
|
||||
#include "jsonreply.h"
|
||||
#include "jsontypes.h"
|
||||
#include "loggingcategories.h"
|
||||
|
||||
JsonHandler::JsonHandler(QObject *parent):
|
||||
QObject(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QVariantMap JsonHandler::introspect(const QMetaMethod::MethodType &type)
|
||||
{
|
||||
QVariantMap data;
|
||||
for (int i = 0; i < metaObject()->methodCount(); ++i) {
|
||||
QMetaMethod method = metaObject()->method(i);
|
||||
|
||||
if (method.methodType() != type)
|
||||
continue;
|
||||
|
||||
switch (method.methodType()) {
|
||||
case QMetaMethod::Method: {
|
||||
if (!m_descriptions.contains(method.name()) || !m_params.contains(method.name()) || !m_returns.contains(method.name())) {
|
||||
continue;
|
||||
}
|
||||
QVariantMap methodData;
|
||||
methodData.insert("description", m_descriptions.value(method.name()));
|
||||
methodData.insert("params", m_params.value(method.name()));
|
||||
methodData.insert("returns", m_returns.value(method.name()));
|
||||
data.insert(name() + "." + method.name(), methodData);
|
||||
break;
|
||||
}
|
||||
case QMetaMethod::Signal: {
|
||||
if (!m_descriptions.contains(method.name()) || !m_params.contains(method.name())) {
|
||||
continue;
|
||||
}
|
||||
if (QString(method.name()).contains(QRegExp("^[A-Z]"))) {
|
||||
QVariantMap methodData;
|
||||
methodData.insert("description", m_descriptions.value(method.name()));
|
||||
methodData.insert("params", m_params.value(method.name()));
|
||||
data.insert(name() + "." + method.name(), methodData);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
;;// Nothing to do for slots
|
||||
}
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
bool JsonHandler::hasMethod(const QString &methodName)
|
||||
{
|
||||
return m_descriptions.contains(methodName) && m_params.contains(methodName) && m_returns.contains(methodName);
|
||||
}
|
||||
|
||||
QPair<bool, QString> JsonHandler::validateParams(const QString &methodName, const QVariantMap ¶ms)
|
||||
{
|
||||
QVariantMap paramTemplate = m_params.value(methodName);
|
||||
return JsonTypes::validateMap(paramTemplate, params);
|
||||
}
|
||||
|
||||
QPair<bool, QString> JsonHandler::validateReturns(const QString &methodName, const QVariantMap &returns)
|
||||
{
|
||||
QVariantMap returnsTemplate = m_returns.value(methodName);
|
||||
return JsonTypes::validateMap(returnsTemplate, returns);
|
||||
}
|
||||
|
||||
void JsonHandler::setDescription(const QString &methodName, const QString &description)
|
||||
{
|
||||
for(int i = 0; i < metaObject()->methodCount(); ++i) {
|
||||
QMetaMethod method = metaObject()->method(i);
|
||||
if (method.name() == methodName) {
|
||||
m_descriptions.insert(methodName, description);
|
||||
return;
|
||||
}
|
||||
}
|
||||
qCWarning(dcJsonRpc()) << "Cannot set description. No such method:" << methodName;
|
||||
}
|
||||
|
||||
void JsonHandler::setParams(const QString &methodName, const QVariantMap ¶ms)
|
||||
{
|
||||
for(int i = 0; i < metaObject()->methodCount(); ++i) {
|
||||
QMetaMethod method = metaObject()->method(i);
|
||||
if (method.name() == methodName) {
|
||||
m_params.insert(methodName, params);
|
||||
return;
|
||||
}
|
||||
}
|
||||
qCWarning(dcJsonRpc()) << "Cannot set params. No such method:" << methodName;
|
||||
}
|
||||
|
||||
void JsonHandler::setReturns(const QString &methodName, const QVariantMap &returns)
|
||||
{
|
||||
for(int i = 0; i < metaObject()->methodCount(); ++i) {
|
||||
QMetaMethod method = metaObject()->method(i);
|
||||
if (method.name() == methodName) {
|
||||
m_returns.insert(methodName, returns);
|
||||
return;
|
||||
}
|
||||
}
|
||||
qCWarning(dcJsonRpc()) << "Cannot set returns. No such method:" << methodName;
|
||||
}
|
||||
|
||||
JsonReply *JsonHandler::createReply(const QVariantMap &data) const
|
||||
{
|
||||
return JsonReply::createReply(const_cast<JsonHandler*>(this), data);
|
||||
}
|
||||
|
||||
JsonReply *JsonHandler::createAsyncReply(const QString &method) const
|
||||
{
|
||||
return JsonReply::createAsyncReply(const_cast<JsonHandler*>(this), method);
|
||||
}
|
||||
43
libnymea-remoteproxy/jsonrpc/jsonhandler.h
Normal file
43
libnymea-remoteproxy/jsonrpc/jsonhandler.h
Normal file
@ -0,0 +1,43 @@
|
||||
#ifndef JSONHANDLER_H
|
||||
#define JSONHANDLER_H
|
||||
|
||||
#include <QUuid>
|
||||
#include <QTimer>
|
||||
#include <QObject>
|
||||
#include <QVariantMap>
|
||||
#include <QMetaMethod>
|
||||
|
||||
class JsonReply;
|
||||
|
||||
class JsonHandler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit JsonHandler(QObject *parent = nullptr);
|
||||
|
||||
virtual QString name() const = 0;
|
||||
|
||||
QVariantMap introspect(const QMetaMethod::MethodType &type);
|
||||
|
||||
bool hasMethod(const QString &methodName);
|
||||
QPair<bool, QString> validateParams(const QString &methodName, const QVariantMap ¶ms);
|
||||
QPair<bool, QString> validateReturns(const QString &methodName, const QVariantMap &returns);
|
||||
|
||||
private:
|
||||
QHash<QString, QString> m_descriptions;
|
||||
QHash<QString, QVariantMap> m_params;
|
||||
QHash<QString, QVariantMap> m_returns;
|
||||
|
||||
signals:
|
||||
void asyncReply(int id, const QVariantMap ¶ms);
|
||||
|
||||
protected:
|
||||
void setDescription(const QString &methodName, const QString &description);
|
||||
void setParams(const QString &methodName, const QVariantMap ¶ms);
|
||||
void setReturns(const QString &methodName, const QVariantMap &returns);
|
||||
|
||||
JsonReply *createReply(const QVariantMap &data) const;
|
||||
JsonReply *createAsyncReply(const QString &method) const;
|
||||
|
||||
};
|
||||
#endif // JSONHANDLER_H
|
||||
82
libnymea-remoteproxy/jsonrpc/jsonreply.cpp
Normal file
82
libnymea-remoteproxy/jsonrpc/jsonreply.cpp
Normal file
@ -0,0 +1,82 @@
|
||||
#include "jsonreply.h"
|
||||
|
||||
JsonReply *JsonReply::createReply(JsonHandler *handler, const QVariantMap &data)
|
||||
{
|
||||
return new JsonReply(TypeSync, handler, QString(), data);
|
||||
}
|
||||
|
||||
JsonReply *JsonReply::createAsyncReply(JsonHandler *handler, const QString &method)
|
||||
{
|
||||
return new JsonReply(TypeAsync, handler, method);
|
||||
}
|
||||
|
||||
JsonReply::Type JsonReply::type() const
|
||||
{
|
||||
return m_type;
|
||||
}
|
||||
|
||||
QVariantMap JsonReply::data() const
|
||||
{
|
||||
return m_data;
|
||||
}
|
||||
|
||||
void JsonReply::setData(const QVariantMap &data)
|
||||
{
|
||||
m_data = data;
|
||||
}
|
||||
|
||||
JsonHandler *JsonReply::handler() const
|
||||
{
|
||||
return m_handler;
|
||||
}
|
||||
|
||||
QString JsonReply::method() const
|
||||
{
|
||||
return m_method;
|
||||
}
|
||||
|
||||
QUuid JsonReply::connectionId() const
|
||||
{
|
||||
return m_connectionId;
|
||||
}
|
||||
|
||||
void JsonReply::setClientId(const QUuid &connectionId)
|
||||
{
|
||||
m_connectionId = connectionId;
|
||||
}
|
||||
|
||||
int JsonReply::commandId() const
|
||||
{
|
||||
return m_commandId;
|
||||
}
|
||||
|
||||
void JsonReply::setCommandId(int commandId)
|
||||
{
|
||||
m_commandId = commandId;
|
||||
}
|
||||
|
||||
bool JsonReply::timedOut() const
|
||||
{
|
||||
return m_timedOut;
|
||||
}
|
||||
|
||||
void JsonReply::startWait()
|
||||
{
|
||||
m_timeout.start(5000);
|
||||
}
|
||||
|
||||
void JsonReply::timeout()
|
||||
{
|
||||
m_timedOut = true;
|
||||
emit finished();
|
||||
}
|
||||
|
||||
JsonReply::JsonReply(JsonReply::Type type, JsonHandler *handler, const QString &method, const QVariantMap &data):
|
||||
m_type(type),
|
||||
m_data(data),
|
||||
m_handler(handler),
|
||||
m_method(method),
|
||||
m_timedOut(false)
|
||||
{
|
||||
connect(&m_timeout, &QTimer::timeout, this, &JsonReply::timeout);
|
||||
}
|
||||
60
libnymea-remoteproxy/jsonrpc/jsonreply.h
Normal file
60
libnymea-remoteproxy/jsonrpc/jsonreply.h
Normal file
@ -0,0 +1,60 @@
|
||||
#ifndef JSONRPCREPLY_H
|
||||
#define JSONRPCREPLY_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
#include <QUuid>
|
||||
|
||||
#include "jsonhandler.h"
|
||||
|
||||
class JsonReply: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Type {
|
||||
TypeSync,
|
||||
TypeAsync
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
int commandId() const;
|
||||
void setCommandId(int commandId);
|
||||
|
||||
bool timedOut() const;
|
||||
|
||||
public slots:
|
||||
void startWait();
|
||||
|
||||
signals:
|
||||
void finished();
|
||||
|
||||
private slots:
|
||||
void timeout();
|
||||
|
||||
private:
|
||||
JsonReply(Type type, JsonHandler *handler, const QString &method, const QVariantMap &data = QVariantMap());
|
||||
Type m_type;
|
||||
QVariantMap m_data;
|
||||
|
||||
JsonHandler *m_handler;
|
||||
QString m_method;
|
||||
QUuid m_connectionId;
|
||||
int m_commandId;
|
||||
bool m_timedOut;
|
||||
|
||||
QTimer m_timeout;
|
||||
};
|
||||
|
||||
#endif // JSONRPCREPLY_H
|
||||
261
libnymea-remoteproxy/jsonrpc/jsontypes.cpp
Normal file
261
libnymea-remoteproxy/jsonrpc/jsontypes.cpp
Normal file
@ -0,0 +1,261 @@
|
||||
#include "jsontypes.h"
|
||||
#include <QStringList>
|
||||
#include <QJsonDocument>
|
||||
#include <QDebug>
|
||||
|
||||
#include "loggingcategories.h"
|
||||
|
||||
bool JsonTypes::s_initialized = false;
|
||||
QString JsonTypes::s_lastError;
|
||||
|
||||
// Types
|
||||
QVariantList JsonTypes::s_basicType;
|
||||
QVariantList JsonTypes::s_authenticationError;
|
||||
|
||||
// Objects
|
||||
|
||||
|
||||
|
||||
QVariantMap JsonTypes::allTypes()
|
||||
{
|
||||
QVariantMap allTypes;
|
||||
|
||||
// Enums
|
||||
allTypes.insert("BasicType", basicType());
|
||||
allTypes.insert("AuthenticationError", authenticationError());
|
||||
|
||||
// Types
|
||||
|
||||
return allTypes;
|
||||
}
|
||||
|
||||
void JsonTypes::init()
|
||||
{
|
||||
// Declare types
|
||||
s_basicType = enumToStrings(JsonTypes::staticMetaObject, "BasicType");
|
||||
s_authenticationError = enumToStrings(Authenticator::staticMetaObject, "AuthenticationError");
|
||||
|
||||
s_initialized = true;
|
||||
}
|
||||
|
||||
|
||||
QPair<bool, QString> JsonTypes::validateMap(const QVariantMap &templateMap, const QVariantMap &map)
|
||||
{
|
||||
s_lastError.clear();
|
||||
|
||||
// Make sure all values defined in the template are around
|
||||
foreach (const QString &key, templateMap.keys()) {
|
||||
QString strippedKey = key;
|
||||
strippedKey.remove(QRegExp("^o:"));
|
||||
if (!key.startsWith("o:") && !map.contains(strippedKey)) {
|
||||
qCWarning(dcJsonRpc()) << "*** missing key" << key;
|
||||
qCWarning(dcJsonRpc()) << "Expected: " << templateMap;
|
||||
qCWarning(dcJsonRpc()) << "Got: " << map;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromVariant(map);
|
||||
return report(false, QString("Missing key %1 in %2").arg(key).arg(QString(jsonDoc.toJson())));
|
||||
}
|
||||
if (map.contains(strippedKey)) {
|
||||
QPair<bool, QString> result = validateVariant(templateMap.value(key), map.value(strippedKey));
|
||||
if (!result.first) {
|
||||
qCWarning(dcJsonRpc()) << "Object not matching template" << templateMap.value(key) << map.value(strippedKey);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure there aren't any other parameters than the allowed ones
|
||||
foreach (const QString &key, map.keys()) {
|
||||
QString optKey = "o:" + key;
|
||||
|
||||
if (!templateMap.contains(key) && !templateMap.contains(optKey)) {
|
||||
qCWarning(dcJsonRpc()) << "Forbidden param" << key << "in params";
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromVariant(map);
|
||||
return report(false, QString("Forbidden key \"%1\" in %2").arg(key).arg(QString(jsonDoc.toJson())));
|
||||
}
|
||||
}
|
||||
|
||||
return report(true, "");
|
||||
}
|
||||
|
||||
QPair<bool, QString> JsonTypes::validateVariant(const QVariant &templateVariant, const QVariant &variant)
|
||||
{
|
||||
switch(templateVariant.type()) {
|
||||
case QVariant::String:
|
||||
if (templateVariant.toString().startsWith("$ref:")) {
|
||||
QString refName = templateVariant.toString();
|
||||
if (refName == basicTypeRef()) {
|
||||
QPair<bool, QString> result = validateBasicType(variant);
|
||||
if (!result.first) {
|
||||
qCWarning(dcJsonRpc()) << QString("Value %1 not allowed in %2").arg(variant.toString()).arg(basicTypeRef());
|
||||
return result;
|
||||
}
|
||||
} else if (refName == authenticationErrorRef()) {
|
||||
QPair<bool, QString> result = validateEnum(s_authenticationError, variant);
|
||||
if (!result.first) {
|
||||
qCWarning(dcJsonRpc()) << QString("Value %1 not allowed in %2").arg(variant.toString()).arg(authenticationErrorRef());
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
Q_ASSERT_X(false, "JsonTypes", QString("Unhandled ref: %1").arg(refName).toLatin1().data());
|
||||
return report(false, QString("Unhandled ref %1. Server implementation incomplete.").arg(refName));
|
||||
}
|
||||
} else {
|
||||
QPair<bool, QString> result = JsonTypes::validateProperty(templateVariant, variant);
|
||||
if (!result.first) {
|
||||
qCWarning(dcJsonRpc()) << "Property not matching:" << templateVariant << "!=" << variant;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case QVariant::Map: {
|
||||
QPair<bool, QString> result = validateMap(templateVariant.toMap(), variant.toMap());
|
||||
if (!result.first) {
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case QVariant::List: {
|
||||
QPair<bool, QString> result = validateList(templateVariant.toList(), variant.toList());
|
||||
if (!result.first) {
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
qCWarning(dcJsonRpc()) << "Unhandled value" << templateVariant;
|
||||
return report(false, QString("Unhandled value %1.").arg(templateVariant.toString()));
|
||||
}
|
||||
return report(true, "");
|
||||
|
||||
}
|
||||
|
||||
QPair<bool, QString> JsonTypes::validateEnum(const QVariantList &enumList, const QVariant &value)
|
||||
{
|
||||
QStringList enumStrings;
|
||||
foreach (const QVariant &variant, enumList) {
|
||||
enumStrings.append(variant.toString());
|
||||
}
|
||||
|
||||
bool valid = enumStrings.contains(value.toString());
|
||||
QString errorMessage = QString("Value %1 not allowed in %2").arg(value.toString()).arg(enumStrings.join(", "));
|
||||
if (!valid)
|
||||
qCWarning(dcJsonRpc()) << errorMessage;
|
||||
|
||||
return report(valid, errorMessage);
|
||||
}
|
||||
|
||||
QPair<bool, QString> JsonTypes::validateProperty(const QVariant &templateValue, const QVariant &value)
|
||||
{
|
||||
QString strippedTemplateValue = templateValue.toString();
|
||||
|
||||
if (strippedTemplateValue == JsonTypes::basicTypeToString(JsonTypes::Variant)) {
|
||||
return report(true, "");
|
||||
}
|
||||
if (strippedTemplateValue == JsonTypes::basicTypeToString(JsonTypes::Object)){
|
||||
return report(true, "");
|
||||
}
|
||||
if (strippedTemplateValue == JsonTypes::basicTypeToString(QVariant::Uuid)) {
|
||||
QString errorString = QString("Param %1 is not a uuid.").arg(value.toString());
|
||||
return report(value.canConvert(QVariant::Uuid), errorString);
|
||||
}
|
||||
if (strippedTemplateValue == JsonTypes::basicTypeToString(QVariant::String)) {
|
||||
QString errorString = QString("Param %1 is not a string.").arg(value.toString());
|
||||
return report(value.canConvert(QVariant::String), errorString);
|
||||
}
|
||||
if (strippedTemplateValue == JsonTypes::basicTypeToString(QVariant::Bool)) {
|
||||
QString errorString = QString("Param %1 is not a bool.").arg(value.toString());
|
||||
return report(value.canConvert(QVariant::Bool), errorString);
|
||||
}
|
||||
if (strippedTemplateValue == JsonTypes::basicTypeToString(QVariant::Int)) {
|
||||
QString errorString = QString("Param %1 is not a int.").arg(value.toString());
|
||||
return report(value.canConvert(QVariant::Int), errorString);
|
||||
}
|
||||
if (strippedTemplateValue == JsonTypes::basicTypeToString(QVariant::UInt)) {
|
||||
QString errorString = QString("Param %1 is not a int.").arg(value.toString());
|
||||
return report(value.canConvert(QVariant::UInt), errorString);
|
||||
}
|
||||
qCWarning(dcJsonRpc()) << QString("Unhandled property type: %1 (expected: %2)").arg(value.toString()).arg(strippedTemplateValue);
|
||||
QString errorString = QString("Unhandled property type: %1 (expected: %2)").arg(value.toString()).arg(strippedTemplateValue);
|
||||
return report(false, errorString);
|
||||
}
|
||||
|
||||
QPair<bool, QString> JsonTypes::validateList(const QVariantList &templateList, const QVariantList &list)
|
||||
{
|
||||
Q_ASSERT(templateList.count() == 1);
|
||||
QVariant entryTemplate = templateList.first();
|
||||
|
||||
for (int i = 0; i < list.count(); ++i) {
|
||||
QVariant listEntry = list.at(i);
|
||||
QPair<bool, QString> result = validateVariant(entryTemplate, listEntry);
|
||||
if (!result.first) {
|
||||
qCWarning(dcJsonRpc()) << "List entry not matching template";
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return report(true, "");
|
||||
}
|
||||
|
||||
QPair<bool, QString> JsonTypes::validateBasicType(const QVariant &variant)
|
||||
{
|
||||
if (variant.canConvert(QVariant::Uuid) && QVariant(variant).convert(QVariant::Uuid)) {
|
||||
return report(true, "");
|
||||
}
|
||||
if (variant.canConvert(QVariant::String) && QVariant(variant).convert(QVariant::String)) {
|
||||
return report(true, "");
|
||||
}
|
||||
if (variant.canConvert(QVariant::Int) && QVariant(variant).convert(QVariant::Int)) {
|
||||
return report(true, "");
|
||||
}
|
||||
if (variant.canConvert(QVariant::Double) && QVariant(variant).convert(QVariant::Double)) {
|
||||
return report(true, "");
|
||||
}
|
||||
if (variant.canConvert(QVariant::Bool && QVariant(variant).convert(QVariant::Bool))) {
|
||||
return report(true, "");
|
||||
}
|
||||
return report(false, QString("Error validating basic type %1.").arg(variant.toString()));
|
||||
}
|
||||
|
||||
QString JsonTypes::basicTypeToString(const QVariant::Type &type)
|
||||
{
|
||||
switch (type) {
|
||||
case QVariant::Uuid:
|
||||
return "Uuid";
|
||||
break;
|
||||
case QVariant::String:
|
||||
return "String";
|
||||
break;
|
||||
case QVariant::Int:
|
||||
return "Int";
|
||||
break;
|
||||
case QVariant::UInt:
|
||||
return "UInt";
|
||||
break;
|
||||
case QVariant::Double:
|
||||
return "Double";
|
||||
break;
|
||||
case QVariant::Bool:
|
||||
return "Bool";
|
||||
break;
|
||||
default:
|
||||
return QVariant::typeToName(static_cast<int>(type));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QPair<bool, QString> JsonTypes::report(bool status, const QString &message)
|
||||
{
|
||||
return qMakePair<bool, QString>(status, message);
|
||||
}
|
||||
|
||||
QVariantList JsonTypes::enumToStrings(const QMetaObject &metaObject, const QString &enumName)
|
||||
{
|
||||
int enumIndex = metaObject.indexOfEnumerator(enumName.toLatin1().data());
|
||||
Q_ASSERT_X(enumIndex >= 0, "JsonTypes", QString("Enumerator %1 not found in %2").arg(enumName).arg(metaObject.className()).toLocal8Bit());
|
||||
QMetaEnum metaEnum = metaObject.enumerator(enumIndex);
|
||||
|
||||
QVariantList enumStrings;
|
||||
for (int i = 0; i < metaEnum.keyCount(); i++) {
|
||||
enumStrings << metaEnum.valueToKey(metaEnum.value(i));
|
||||
}
|
||||
return enumStrings;
|
||||
}
|
||||
90
libnymea-remoteproxy/jsonrpc/jsontypes.h
Normal file
90
libnymea-remoteproxy/jsonrpc/jsontypes.h
Normal file
@ -0,0 +1,90 @@
|
||||
#ifndef JSONTYPES_H
|
||||
#define JSONTYPES_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QVariant>
|
||||
#include <QMetaEnum>
|
||||
#include <QStringList>
|
||||
|
||||
#include "authentication/authenticator.h"
|
||||
|
||||
#define DECLARE_OBJECT(typeName, jsonName) \
|
||||
public: \
|
||||
static QString typeName##Ref() { return QStringLiteral("$ref:") + QStringLiteral(jsonName); if (!s_initialized) { init(); } } \
|
||||
static QVariantMap typeName##Description() { \
|
||||
if (!s_initialized) { init(); } \
|
||||
return s_##typeName; \
|
||||
} \
|
||||
private: \
|
||||
static QVariantMap s_##typeName; \
|
||||
public:
|
||||
|
||||
#define DECLARE_TYPE(typeName, enumString, className, enumName) \
|
||||
public: \
|
||||
static QString typeName##Ref() { return QStringLiteral("$ref:") + QStringLiteral(enumString); if (!s_initialized) { init(); } } \
|
||||
static QVariantList typeName() { \
|
||||
if (!s_initialized) { init(); } \
|
||||
return s_##typeName; \
|
||||
} \
|
||||
static QString typeName##ToString(className::enumName value) { \
|
||||
if (!s_initialized) { init(); } \
|
||||
QMetaObject metaObject = className::staticMetaObject; \
|
||||
int enumIndex = metaObject.indexOfEnumerator(enumString); \
|
||||
QMetaEnum metaEnum = metaObject.enumerator(enumIndex); \
|
||||
return metaEnum.valueToKey(metaEnum.value(value)); \
|
||||
} \
|
||||
private: \
|
||||
static QVariantList s_##typeName; \
|
||||
public:
|
||||
|
||||
|
||||
class JsonTypes
|
||||
{
|
||||
Q_GADGET
|
||||
Q_ENUMS(BasicType)
|
||||
|
||||
public:
|
||||
enum BasicType {
|
||||
Uuid,
|
||||
String,
|
||||
Int,
|
||||
UInt,
|
||||
Double,
|
||||
Bool,
|
||||
Variant,
|
||||
Object
|
||||
};
|
||||
Q_ENUM(BasicType)
|
||||
|
||||
static QVariantMap allTypes();
|
||||
|
||||
// Declare types
|
||||
DECLARE_TYPE(basicType, "BasicType", JsonTypes, BasicType)
|
||||
DECLARE_TYPE(authenticationError, "AuthenticationError", Authenticator, AuthenticationError)
|
||||
|
||||
// Declare objects
|
||||
|
||||
// Pack methods
|
||||
|
||||
// Validation methods
|
||||
static QPair<bool, QString> validateMap(const QVariantMap &templateMap, const QVariantMap &map);
|
||||
static QPair<bool, QString> validateVariant(const QVariant &templateVariant, const QVariant &variant);
|
||||
static QPair<bool, QString> validateEnum(const QVariantList &enumList, const QVariant &value);
|
||||
static QPair<bool, QString> validateProperty(const QVariant &templateValue, const QVariant &value);
|
||||
static QPair<bool, QString> validateList(const QVariantList &templateList, const QVariantList &list);
|
||||
static QPair<bool, QString> validateBasicType(const QVariant &variant);
|
||||
|
||||
// Converter
|
||||
static QString basicTypeToString(const QVariant::Type &type);
|
||||
|
||||
private:
|
||||
static bool s_initialized;
|
||||
static QString s_lastError;
|
||||
|
||||
static void init();
|
||||
|
||||
static QPair<bool, QString> report(bool status, const QString &message);
|
||||
static QVariantList enumToStrings(const QMetaObject &metaObject, const QString &enumName);
|
||||
};
|
||||
|
||||
#endif // JSONTYPES_H
|
||||
157
libnymea-remoteproxy/jsonrpcserver.cpp
Normal file
157
libnymea-remoteproxy/jsonrpcserver.cpp
Normal file
@ -0,0 +1,157 @@
|
||||
#include "engine.h"
|
||||
#include "jsonrpcserver.h"
|
||||
#include "loggingcategories.h"
|
||||
#include "jsonrpc/jsontypes.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QCoreApplication>
|
||||
|
||||
JsonRpcServer::JsonRpcServer(QObject *parent) :
|
||||
JsonHandler(parent)
|
||||
{
|
||||
// Methods
|
||||
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.");
|
||||
setParams("Hello", params);
|
||||
returns.insert("server", JsonTypes::basicTypeToString(JsonTypes::String));
|
||||
returns.insert("name", JsonTypes::basicTypeToString(JsonTypes::String));
|
||||
returns.insert("version", JsonTypes::basicTypeToString(JsonTypes::String));
|
||||
returns.insert("apiVersion", JsonTypes::basicTypeToString(JsonTypes::String));
|
||||
setReturns("Hello", returns);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
setDescription("Introspect", "Introspect this API.");
|
||||
setParams("Introspect", params);
|
||||
returns.insert("methods", JsonTypes::basicTypeToString(JsonTypes::Object));
|
||||
returns.insert("types", JsonTypes::basicTypeToString(JsonTypes::Object));
|
||||
returns.insert("notifications", JsonTypes::basicTypeToString(JsonTypes::Object));
|
||||
setReturns("Introspect", returns);
|
||||
|
||||
QMetaObject::invokeMethod(this, "setup", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
JsonRpcServer::~JsonRpcServer()
|
||||
{
|
||||
qCDebug(dcJsonRpc()) << "Shutting down Json RPC server";
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
Q_UNUSED(params)
|
||||
|
||||
QVariantMap data;
|
||||
data.insert("server", QCoreApplication::applicationName());
|
||||
data.insert("name", Engine::instance()->serverName());
|
||||
data.insert("version", QCoreApplication::applicationVersion());
|
||||
data.insert("apiVersion", API_VERSION_STRING);
|
||||
|
||||
return createReply(data);
|
||||
}
|
||||
|
||||
JsonReply *JsonRpcServer::Introspect(const QVariantMap ¶ms) const
|
||||
{
|
||||
Q_UNUSED(params)
|
||||
|
||||
QVariantMap data;
|
||||
data.insert("types", JsonTypes::allTypes());
|
||||
|
||||
QVariantMap methods;
|
||||
foreach (JsonHandler *handler, m_handlers) {
|
||||
methods.unite(handler->introspect(QMetaMethod::Method));
|
||||
}
|
||||
|
||||
data.insert("methods", methods);
|
||||
|
||||
QVariantMap signalsMap;
|
||||
foreach (JsonHandler *handler, m_handlers) {
|
||||
signalsMap.unite(handler->introspect(QMetaMethod::Signal));
|
||||
}
|
||||
|
||||
data.insert("notifications", signalsMap);
|
||||
|
||||
return createReply(data);
|
||||
}
|
||||
|
||||
void JsonRpcServer::sendResponse(TransportInterface *interface, const QUuid &clientId, int commandId, const QVariantMap ¶ms)
|
||||
{
|
||||
QVariantMap response;
|
||||
response.insert("id", commandId);
|
||||
response.insert("status", "success");
|
||||
response.insert("params", params);
|
||||
|
||||
QByteArray data = QJsonDocument::fromVariant(response).toJson(QJsonDocument::Compact);
|
||||
qCDebug(dcJsonRpcTraffic()) << "Sending data:" << data;
|
||||
interface->sendData(clientId, data);
|
||||
}
|
||||
|
||||
void JsonRpcServer::sendErrorResponse(TransportInterface *interface, const QUuid &clientId, int commandId, const QString &error)
|
||||
{
|
||||
QVariantMap errorResponse;
|
||||
errorResponse.insert("id", commandId);
|
||||
errorResponse.insert("status", "error");
|
||||
errorResponse.insert("error", error);
|
||||
|
||||
QByteArray data = QJsonDocument::fromVariant(errorResponse).toJson(QJsonDocument::Compact);
|
||||
qCDebug(dcJsonRpcTraffic()) << "Sending data:" << data;
|
||||
interface->sendData(clientId, data);
|
||||
}
|
||||
|
||||
QString JsonRpcServer::formatAssertion(const QString &targetNamespace, const QString &method, JsonHandler *handler, const QVariantMap &data) const
|
||||
{
|
||||
QJsonDocument doc = QJsonDocument::fromVariant(handler->introspect(QMetaMethod::Method).value(targetNamespace + "." + method));
|
||||
QJsonDocument doc2 = QJsonDocument::fromVariant(data);
|
||||
return QString("\nMethod: %1\nTemplate: %2\nValue: %3")
|
||||
.arg(targetNamespace + "." + method)
|
||||
.arg(QString(doc.toJson(QJsonDocument::Indented)))
|
||||
.arg(QString(doc2.toJson(QJsonDocument::Indented)));
|
||||
}
|
||||
|
||||
void JsonRpcServer::registerHandler(JsonHandler *handler)
|
||||
{
|
||||
m_handlers.insert(handler->name(), handler);
|
||||
qCDebug(dcJsonRpc()) << "Register 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));
|
||||
}
|
||||
|
||||
54
libnymea-remoteproxy/jsonrpcserver.h
Normal file
54
libnymea-remoteproxy/jsonrpcserver.h
Normal file
@ -0,0 +1,54 @@
|
||||
#ifndef JSONRPCSERVER_H
|
||||
#define JSONRPCSERVER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "transportinterface.h"
|
||||
#include "jsonrpc/jsonhandler.h"
|
||||
#include "jsonrpc/authenticationhandler.h"
|
||||
|
||||
class JsonRpcServer : public JsonHandler
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit JsonRpcServer(QObject *parent = nullptr);
|
||||
~JsonRpcServer() override;
|
||||
|
||||
QString name() const override;
|
||||
|
||||
QHash<QString, JsonHandler *> handlers() 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;
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
QString formatAssertion(const QString &targetNamespace, const QString &method, JsonHandler *handler, const QVariantMap &data) const;
|
||||
|
||||
void registerHandler(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();
|
||||
|
||||
};
|
||||
|
||||
#endif // JSONRPCSERVER_H
|
||||
@ -7,13 +7,35 @@ HEADERS += \
|
||||
engine.h \
|
||||
loggingcategories.h \
|
||||
transportinterface.h \
|
||||
websocketserver.h
|
||||
websocketserver.h \
|
||||
proxyclient.h \
|
||||
proxyserver.h \
|
||||
jsonrpcserver.h \
|
||||
jsonrpc/jsonhandler.h \
|
||||
jsonrpc/jsonreply.h \
|
||||
jsonrpc/jsontypes.h \
|
||||
jsonrpc/authenticationhandler.h \
|
||||
authentication/authenticator.h \
|
||||
authentication/awsauthenticator.h \
|
||||
authentication/authenticationreply.h \
|
||||
proxyconfiguration.h
|
||||
|
||||
SOURCES += \
|
||||
engine.cpp \
|
||||
loggingcategories.cpp \
|
||||
transportinterface.cpp \
|
||||
websocketserver.cpp
|
||||
websocketserver.cpp \
|
||||
proxyclient.cpp \
|
||||
proxyserver.cpp \
|
||||
jsonrpcserver.cpp \
|
||||
jsonrpc/jsonhandler.cpp \
|
||||
jsonrpc/jsonreply.cpp \
|
||||
jsonrpc/jsontypes.cpp \
|
||||
jsonrpc/authenticationhandler.cpp \
|
||||
authentication/authenticator.cpp \
|
||||
authentication/awsauthenticator.cpp \
|
||||
authentication/authenticationreply.cpp \
|
||||
proxyconfiguration.cpp
|
||||
|
||||
|
||||
# install header file with relative subdirectory
|
||||
|
||||
@ -3,9 +3,9 @@
|
||||
Q_LOGGING_CATEGORY(dcApplication, "Application")
|
||||
Q_LOGGING_CATEGORY(dcEngine, "Engine")
|
||||
Q_LOGGING_CATEGORY(dcJsonRpc, "JsonRpc")
|
||||
Q_LOGGING_CATEGORY(dcJsonRpcTraffic, "JsonRpcTraffic")
|
||||
Q_LOGGING_CATEGORY(dcWebSocketServer, "WebSocketServer")
|
||||
Q_LOGGING_CATEGORY(dcWebSocketServerTraffic, "WebSocketServerTraffic")
|
||||
Q_LOGGING_CATEGORY(dcAuthenticator, "Authenticator")
|
||||
Q_LOGGING_CATEGORY(dcDebug, "Debug")
|
||||
Q_LOGGING_CATEGORY(dcConnectionManager, "ConnectionManager")
|
||||
Q_LOGGING_CATEGORY(dcProxyServer, "ProxyServer")
|
||||
|
||||
|
||||
@ -7,10 +7,10 @@
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcApplication)
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcEngine)
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcJsonRpc)
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcJsonRpcTraffic)
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcWebSocketServer)
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcWebSocketServerTraffic)
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcAuthenticator)
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcConnectionManager)
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcDebug)
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcProxyServer)
|
||||
|
||||
#endif // LOGGINGCATEGORIES_H
|
||||
|
||||
23
libnymea-remoteproxy/proxyclient.cpp
Normal file
23
libnymea-remoteproxy/proxyclient.cpp
Normal file
@ -0,0 +1,23 @@
|
||||
#include "proxyclient.h"
|
||||
|
||||
ProxyClient::ProxyClient(const QUuid &clientId, QObject *parent) :
|
||||
QObject(parent),
|
||||
m_clientId(clientId)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QUuid ProxyClient::clientId() const
|
||||
{
|
||||
return m_clientId;
|
||||
}
|
||||
|
||||
bool ProxyClient::authenticated() const
|
||||
{
|
||||
return m_authenticated;
|
||||
}
|
||||
|
||||
bool ProxyClient::tunnelConnected() const
|
||||
{
|
||||
return m_tunnelConnected;
|
||||
}
|
||||
26
libnymea-remoteproxy/proxyclient.h
Normal file
26
libnymea-remoteproxy/proxyclient.h
Normal file
@ -0,0 +1,26 @@
|
||||
#ifndef PROXYCLIENT_H
|
||||
#define PROXYCLIENT_H
|
||||
|
||||
#include <QUuid>
|
||||
#include <QObject>
|
||||
|
||||
class ProxyClient : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ProxyClient(const QUuid &clientId, QObject *parent = nullptr);
|
||||
|
||||
QUuid clientId() const;
|
||||
|
||||
bool authenticated() const;
|
||||
bool tunnelConnected() const;
|
||||
|
||||
private:
|
||||
QUuid m_clientId;
|
||||
bool m_authenticated = false;
|
||||
bool m_tunnelConnected = false;
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // PROXYCLIENT_H
|
||||
6
libnymea-remoteproxy/proxyconfiguration.cpp
Normal file
6
libnymea-remoteproxy/proxyconfiguration.cpp
Normal file
@ -0,0 +1,6 @@
|
||||
#include "proxyconfiguration.h"
|
||||
|
||||
ProxyConfiguration::ProxyConfiguration(QObject *parent) : QObject(parent)
|
||||
{
|
||||
|
||||
}
|
||||
18
libnymea-remoteproxy/proxyconfiguration.h
Normal file
18
libnymea-remoteproxy/proxyconfiguration.h
Normal file
@ -0,0 +1,18 @@
|
||||
#ifndef PROXYCONFIGURATION_H
|
||||
#define PROXYCONFIGURATION_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class ProxyConfiguration : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ProxyConfiguration(QObject *parent = nullptr);
|
||||
|
||||
signals:
|
||||
|
||||
public slots:
|
||||
|
||||
};
|
||||
|
||||
#endif // PROXYCONFIGURATION_H
|
||||
62
libnymea-remoteproxy/proxyserver.cpp
Normal file
62
libnymea-remoteproxy/proxyserver.cpp
Normal file
@ -0,0 +1,62 @@
|
||||
#include "proxyserver.h"
|
||||
#include "loggingcategories.h"
|
||||
|
||||
ProxyServer::ProxyServer(QObject *parent) : QObject(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ProxyServer::registerTransportInterface(TransportInterface *interface)
|
||||
{
|
||||
qCDebug(dcProxyServer()) << "Register transport interface" << interface->serverName();
|
||||
|
||||
if (m_transportInterfaces.contains(interface)) {
|
||||
qCWarning(dcProxyServer()) << "Transport interface already registerd.";
|
||||
return;
|
||||
}
|
||||
|
||||
connect(interface, &TransportInterface::clientConnected, this, &ProxyServer::onClientConnected);
|
||||
connect(interface, &TransportInterface::clientDisconnected, this, &ProxyServer::onClientDisconnected);
|
||||
connect(interface, &TransportInterface::dataAvailable, this, &ProxyServer::onClientDataAvailable);
|
||||
|
||||
m_transportInterfaces.append(interface);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void ProxyServer::startServer()
|
||||
{
|
||||
qCDebug(dcProxyServer()) << "Start proxy server.";
|
||||
foreach (TransportInterface *interface, m_transportInterfaces) {
|
||||
interface->startServer();
|
||||
}
|
||||
}
|
||||
|
||||
void ProxyServer::stopServer()
|
||||
{
|
||||
qCDebug(dcProxyServer()) << "Stop proxy server.";
|
||||
foreach (TransportInterface *interface, m_transportInterfaces) {
|
||||
interface->stopServer();
|
||||
}
|
||||
}
|
||||
33
libnymea-remoteproxy/proxyserver.h
Normal file
33
libnymea-remoteproxy/proxyserver.h
Normal file
@ -0,0 +1,33 @@
|
||||
#ifndef PROXYSERVER_H
|
||||
#define PROXYSERVER_H
|
||||
|
||||
#include <QUuid>
|
||||
#include <QObject>
|
||||
|
||||
#include "proxyclient.h"
|
||||
#include "transportinterface.h"
|
||||
|
||||
class ProxyServer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ProxyServer(QObject *parent = nullptr);
|
||||
|
||||
void registerTransportInterface(TransportInterface *interface);
|
||||
|
||||
private:
|
||||
QList<TransportInterface *> m_transportInterfaces;
|
||||
QList<QUuid> m_unauthenticatedClients;
|
||||
|
||||
private slots:
|
||||
void onClientConnected(const QUuid &clientId);
|
||||
void onClientDisconnected(const QUuid &clientId);
|
||||
void onClientDataAvailable(const QUuid &clientId, const QByteArray &data);
|
||||
|
||||
public slots:
|
||||
void startServer();
|
||||
void stopServer();
|
||||
|
||||
};
|
||||
|
||||
#endif // PROXYSERVER_H
|
||||
@ -6,6 +6,11 @@ TransportInterface::TransportInterface(QObject *parent) :
|
||||
|
||||
}
|
||||
|
||||
QString TransportInterface::serverName() const
|
||||
{
|
||||
return m_serverName;
|
||||
}
|
||||
|
||||
TransportInterface::~TransportInterface()
|
||||
{
|
||||
|
||||
|
||||
@ -10,6 +10,8 @@ public:
|
||||
explicit TransportInterface(QObject *parent = nullptr);
|
||||
virtual ~TransportInterface() = 0;
|
||||
|
||||
QString serverName() const;
|
||||
|
||||
virtual void sendData(const QUuid &clientId, const QByteArray &data) = 0;
|
||||
virtual void sendData(const QList<QUuid> &clients, const QByteArray &data) = 0;
|
||||
|
||||
@ -18,6 +20,9 @@ signals:
|
||||
void clientDisconnected(const QUuid &clientId);
|
||||
void dataAvailable(const QUuid &clientId, const QByteArray &data);
|
||||
|
||||
protected:
|
||||
QString m_serverName;
|
||||
|
||||
public slots:
|
||||
virtual bool startServer() = 0;
|
||||
virtual bool stopServer() = 0;
|
||||
|
||||
@ -7,7 +7,7 @@ WebSocketServer::WebSocketServer(const QSslConfiguration &sslConfiguration, QObj
|
||||
TransportInterface(parent),
|
||||
m_sslConfiguration(sslConfiguration)
|
||||
{
|
||||
|
||||
m_serverName = "Websocket server";
|
||||
}
|
||||
|
||||
WebSocketServer::~WebSocketServer()
|
||||
@ -144,7 +144,6 @@ bool WebSocketServer::startServer()
|
||||
|
||||
bool WebSocketServer::stopServer()
|
||||
{
|
||||
|
||||
// Clean up client connections
|
||||
foreach (QWebSocket *client, m_clientList.values()) {
|
||||
client->close(QWebSocketProtocol::CloseCodeNormal, "Stop server");
|
||||
|
||||
@ -4,11 +4,13 @@ TEMPLATE = lib
|
||||
TARGET = nymea-remoteproxyclient
|
||||
|
||||
HEADERS += \
|
||||
remoteproxyconnector.h
|
||||
remoteproxyconnector.h \
|
||||
websocketconnector.h
|
||||
|
||||
|
||||
SOURCES += \
|
||||
remoteproxyconnector.cpp
|
||||
remoteproxyconnector.cpp \
|
||||
websocketconnector.cpp
|
||||
|
||||
|
||||
# install header file with relative subdirectory
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
#include "remoteproxyconnector.h"
|
||||
|
||||
Q_LOGGING_CATEGORY(dcRemoteProxyConnector, "RemoteProxyConnector")
|
||||
|
||||
RemoteProxyConnector::RemoteProxyConnector(QObject *parent) : QObject(parent)
|
||||
{
|
||||
|
||||
@ -10,6 +12,11 @@ RemoteProxyConnector::~RemoteProxyConnector()
|
||||
disconnectServer();
|
||||
}
|
||||
|
||||
RemoteProxyConnector::State RemoteProxyConnector::state() const
|
||||
{
|
||||
return m_state;
|
||||
}
|
||||
|
||||
RemoteProxyConnector::Error RemoteProxyConnector::error() const
|
||||
{
|
||||
return m_error;
|
||||
@ -66,7 +73,12 @@ QUrl RemoteProxyConnector::serverUrl() const
|
||||
|
||||
bool RemoteProxyConnector::isConnected() const
|
||||
{
|
||||
return m_state == StateConnected;
|
||||
return m_state == StateConnected || m_state == StateAuthenticating || m_state == StateWaitTunnel || m_state == StateTunnelEstablished;
|
||||
}
|
||||
|
||||
bool RemoteProxyConnector::tunnelEstablished() const
|
||||
{
|
||||
return m_state == StateTunnelEstablished;
|
||||
}
|
||||
|
||||
RemoteProxyConnector::ConnectionType RemoteProxyConnector::connectionType() const
|
||||
@ -84,31 +96,32 @@ quint16 RemoteProxyConnector::serverPort() const
|
||||
return m_serverPort;
|
||||
}
|
||||
|
||||
QList<QSslError> RemoteProxyConnector::ignoreSslErrors() const
|
||||
bool RemoteProxyConnector::insecureConnection() const
|
||||
{
|
||||
return m_ignoreSslErrors;
|
||||
return m_insecureConnection;
|
||||
}
|
||||
|
||||
void RemoteProxyConnector::setIgnoreSslErrors(const QList<QSslError> &errors)
|
||||
void RemoteProxyConnector::setInsecureConnection(bool insecureConnection)
|
||||
{
|
||||
m_ignoreSslErrors = errors;
|
||||
m_insecureConnection = insecureConnection;
|
||||
}
|
||||
|
||||
bool RemoteProxyConnector::sendData(const QByteArray &data)
|
||||
{
|
||||
if (m_state != StateTunnelEstablished) {
|
||||
qWarning() << "RemoteProxyClient: There is no established tunnel for" << serverUrl().toString() << "yet.";
|
||||
return false;
|
||||
}
|
||||
// FIXME: reenable once the auth process is finished
|
||||
// if (m_state != StateTunnelEstablished) {
|
||||
// qWarning() << "RemoteProxyClient: There is no established tunnel for" << serverUrl().toString() << "yet.";
|
||||
// return false;
|
||||
// }
|
||||
|
||||
if (!m_webSocket) {
|
||||
qWarning() << "RemoteProxyClient: There is no websocket";
|
||||
qCWarning(dcRemoteProxyConnector()) << "There is no websocket";
|
||||
return false;
|
||||
}
|
||||
|
||||
qint64 dataSendCount = m_webSocket->sendTextMessage(QString::fromUtf8(data));
|
||||
if (dataSendCount != data.count()) {
|
||||
qWarning() << "RemoteProxyClient: Could not send all data to" << serverUrl().toString();
|
||||
qCWarning(dcRemoteProxyConnector()) << "Could not send all data to" << serverUrl().toString();
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -120,7 +133,7 @@ void RemoteProxyConnector::setState(RemoteProxyConnector::State state)
|
||||
if (m_state == state)
|
||||
return;
|
||||
|
||||
qDebug() << "RemoteProxyClient: State changed" << state;
|
||||
qCDebug(dcRemoteProxyConnector()) << "State changed" << state;
|
||||
m_state = state;
|
||||
emit stateChanged(m_state);
|
||||
}
|
||||
@ -130,7 +143,7 @@ void RemoteProxyConnector::setError(RemoteProxyConnector::Error error)
|
||||
if (m_error == error)
|
||||
return;
|
||||
|
||||
qDebug() << "RemoteProxyClient: Error occured" << error;
|
||||
qCDebug(dcRemoteProxyConnector()) << "Error occured" << error;
|
||||
m_error = error;
|
||||
emit errorOccured(m_error);
|
||||
}
|
||||
@ -154,7 +167,8 @@ void RemoteProxyConnector::setServerPort(quint16 serverPort)
|
||||
void RemoteProxyConnector::onSocketConnected()
|
||||
{
|
||||
setState(StateConnected);
|
||||
qDebug() << "RemoteProxyClient: Connected to" << serverUrl().toString();
|
||||
qCDebug(dcRemoteProxyConnector()) << "Connected to" << serverUrl().toString();
|
||||
emit connected();
|
||||
|
||||
// TODO: start authentication process
|
||||
|
||||
@ -163,32 +177,34 @@ void RemoteProxyConnector::onSocketConnected()
|
||||
|
||||
void RemoteProxyConnector::onSocketDisconnected()
|
||||
{
|
||||
qDebug() << "RemoteProxyClient: Disconnected from" << serverUrl().toString();
|
||||
qCDebug(dcRemoteProxyConnector()) << "Disconnected from" << serverUrl().toString();
|
||||
setState(StateDisconnected);
|
||||
emit disconnected();
|
||||
}
|
||||
|
||||
void RemoteProxyConnector::onSocketError(QAbstractSocket::SocketError error)
|
||||
{
|
||||
qWarning() << "RemoteProxyClient: Socket error occured" << error;
|
||||
qCWarning(dcRemoteProxyConnector()) << "Socket error occured" << error;
|
||||
setError(ErrorSocketError);
|
||||
}
|
||||
|
||||
void RemoteProxyConnector::onSocketSslError(const QList<QSslError> &errors)
|
||||
{
|
||||
qWarning() << "RemoteProxyClient: Socket ssl errors occured" << errors;
|
||||
foreach (const QSslError sslError, errors) {
|
||||
qWarning() << "RemoteProxyClient: " << static_cast<int>(sslError.error()) << sslError.errorString();
|
||||
if (m_insecureConnection) {
|
||||
qCDebug(dcRemoteProxyConnector()) << "Ignore ssl errors because explicit allowed to use an insecure connection.";
|
||||
m_webSocket->ignoreSslErrors();
|
||||
} else {
|
||||
qCWarning(dcRemoteProxyConnector()) << "Socket ssl errors occured:";
|
||||
foreach (const QSslError sslError, errors) {
|
||||
qCWarning(dcRemoteProxyConnector()) << " -->" << static_cast<int>(sslError.error()) << sslError.errorString();
|
||||
}
|
||||
setError(ErrorSslError);
|
||||
}
|
||||
|
||||
qDebug() << m_ignoreSslErrors;
|
||||
|
||||
m_webSocket->ignoreSslErrors();
|
||||
setError(ErrorSslError);
|
||||
}
|
||||
|
||||
void RemoteProxyConnector::onSocketStateChanged(QAbstractSocket::SocketState state)
|
||||
{
|
||||
qDebug() << "RemoteProxyClient: Socket state changed" << state;
|
||||
qCDebug(dcRemoteProxyConnector()) << "Socket state changed" << state;
|
||||
switch (state) {
|
||||
case QAbstractSocket::ConnectingState:
|
||||
case QAbstractSocket::HostLookupState:
|
||||
@ -206,7 +222,7 @@ void RemoteProxyConnector::onSocketStateChanged(QAbstractSocket::SocketState sta
|
||||
void RemoteProxyConnector::onTextMessageReceived(const QString &message)
|
||||
{
|
||||
// TODO: check if tunnel is established, if so, emit data received
|
||||
qDebug() << "RemoteProxyClient: Data recived" << message;
|
||||
qCDebug(dcRemoteProxyConnector()) << "Data received" << message;
|
||||
}
|
||||
|
||||
void RemoteProxyConnector::onBinaryMessageReceived(const QByteArray &message)
|
||||
@ -214,24 +230,20 @@ void RemoteProxyConnector::onBinaryMessageReceived(const QByteArray &message)
|
||||
Q_UNUSED(message);
|
||||
}
|
||||
|
||||
bool RemoteProxyConnector::connectServer(RemoteProxyConnector::ConnectionType type, const QHostAddress &serverAddress, quint16 port)
|
||||
bool RemoteProxyConnector::connectServer(const QHostAddress &serverAddress, quint16 port)
|
||||
{
|
||||
setConnectionType(type);
|
||||
setServerAddress(serverAddress);
|
||||
setServerPort(port);
|
||||
|
||||
switch (m_connectionType) {
|
||||
// TODO: currently only websocket support
|
||||
case ConnectionTypeWebSocket:
|
||||
if (m_webSocket) {
|
||||
delete m_webSocket;
|
||||
m_webSocket = nullptr;
|
||||
}
|
||||
|
||||
setState(StateDisconnected);
|
||||
disconnectServer();
|
||||
|
||||
m_webSocket = new QWebSocket("libnymea-remoteproxyclient", QWebSocketProtocol::VersionLatest, this);
|
||||
m_webSocket->ignoreSslErrors(m_ignoreSslErrors);
|
||||
|
||||
if (m_insecureConnection)
|
||||
m_webSocket->ignoreSslErrors();
|
||||
|
||||
connect(m_webSocket, &QWebSocket::connected, this, &RemoteProxyConnector::onSocketConnected);
|
||||
connect(m_webSocket, &QWebSocket::disconnected, this, &RemoteProxyConnector::onSocketDisconnected);
|
||||
@ -244,7 +256,7 @@ bool RemoteProxyConnector::connectServer(RemoteProxyConnector::ConnectionType ty
|
||||
|
||||
setState(StateConnecting);
|
||||
m_webSocket->open(serverUrl());
|
||||
qDebug() << "RemoteProxyClient: Start connecting to" << serverUrl().toString();
|
||||
qCDebug(dcRemoteProxyConnector()) << "Start connecting to" << serverUrl().toString();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -253,5 +265,12 @@ bool RemoteProxyConnector::connectServer(RemoteProxyConnector::ConnectionType ty
|
||||
|
||||
void RemoteProxyConnector::disconnectServer()
|
||||
{
|
||||
if (!m_webSocket)
|
||||
return;
|
||||
|
||||
qCDebug(dcRemoteProxyConnector()) << "Disconnect from server" << serverUrl().toString();
|
||||
m_webSocket->close(QWebSocketProtocol::CloseCodeNormal, "Bye bye");
|
||||
m_webSocket->deleteLater();
|
||||
m_webSocket = nullptr;
|
||||
setState(StateDisconnected);
|
||||
}
|
||||
|
||||
@ -1,9 +1,13 @@
|
||||
#ifndef REMOTEPROXYCONNECTOR_H
|
||||
#define REMOTEPROXYCONNECTOR_H
|
||||
|
||||
#include <QDebug>
|
||||
#include <QObject>
|
||||
#include <QWebSocket>
|
||||
#include <QHostAddress>
|
||||
#include <QLoggingCategory>
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcRemoteProxyConnector)
|
||||
|
||||
class RemoteProxyConnector : public QObject
|
||||
{
|
||||
@ -53,8 +57,8 @@ public:
|
||||
QHostAddress serverAddress() const;
|
||||
quint16 serverPort() const;
|
||||
|
||||
QList<QSslError> ignoreSslErrors() const;
|
||||
void setIgnoreSslErrors(const QList<QSslError> &errors);
|
||||
bool insecureConnection() const;
|
||||
void setInsecureConnection(bool insecureConnection);
|
||||
|
||||
bool sendData(const QByteArray &data);
|
||||
|
||||
@ -64,7 +68,7 @@ private:
|
||||
quint16 m_serverPort = 443;
|
||||
State m_state = StateDisconnected;
|
||||
Error m_error = ErrorNoError;
|
||||
QList<QSslError> m_ignoreSslErrors;
|
||||
bool m_insecureConnection = false;
|
||||
bool m_tunnelEstablished = false;
|
||||
QWebSocket *m_webSocket = nullptr;
|
||||
|
||||
@ -79,8 +83,8 @@ signals:
|
||||
void connected();
|
||||
void disconnected();
|
||||
void tunnelEstablished();
|
||||
void stateChanged(State state);
|
||||
void errorOccured(Error error);
|
||||
void stateChanged(RemoteProxyConnector::State state);
|
||||
void errorOccured(RemoteProxyConnector::Error error);
|
||||
|
||||
void dataReady(const QByteArray &data);
|
||||
|
||||
@ -95,9 +99,13 @@ private slots:
|
||||
|
||||
|
||||
public slots:
|
||||
bool connectServer(ConnectionType type, const QHostAddress &serverAddress, quint16 port);
|
||||
bool connectServer(const QHostAddress &serverAddress, quint16 port);
|
||||
void disconnectServer();
|
||||
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(RemoteProxyConnector::State);
|
||||
Q_DECLARE_METATYPE(RemoteProxyConnector::Error);
|
||||
Q_DECLARE_METATYPE(RemoteProxyConnector::ConnectionType);
|
||||
|
||||
#endif // REMOTEPROXYCONNECTOR_H
|
||||
|
||||
6
libnymea-remoteproxyclient/websocketconnector.cpp
Normal file
6
libnymea-remoteproxyclient/websocketconnector.cpp
Normal file
@ -0,0 +1,6 @@
|
||||
#include "websocketconnector.h"
|
||||
|
||||
WebSocketConnector::WebSocketConnector(QObject *parent) : QObject(parent)
|
||||
{
|
||||
|
||||
}
|
||||
17
libnymea-remoteproxyclient/websocketconnector.h
Normal file
17
libnymea-remoteproxyclient/websocketconnector.h
Normal file
@ -0,0 +1,17 @@
|
||||
#ifndef WEBSOCKETCONNECTOR_H
|
||||
#define WEBSOCKETCONNECTOR_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class WebSocketConnector : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WebSocketConnector(QObject *parent = nullptr);
|
||||
|
||||
signals:
|
||||
|
||||
public slots:
|
||||
};
|
||||
|
||||
#endif // WEBSOCKETCONNECTOR_H
|
||||
@ -1,6 +1,11 @@
|
||||
QT *= network websockets
|
||||
QT -= gui
|
||||
|
||||
# define protocol versions
|
||||
API_VERSION_MAJOR=0
|
||||
API_VERSION_MINOR=1
|
||||
DEFINES += API_VERSION_STRING=\\\"$${API_VERSION_MAJOR}.$${API_VERSION_MINOR}\\\"
|
||||
|
||||
CONFIG += c++11 console
|
||||
|
||||
QMAKE_CXXFLAGS *= -Werror -std=c++11 -g
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
|
||||
#include "engine.h"
|
||||
#include "loggingcategories.h"
|
||||
#include "authentication/awsauthenticator.h"
|
||||
|
||||
|
||||
static QHash<QString, bool> s_loggingFilters;
|
||||
@ -37,6 +38,7 @@ static void loggingCategoryFilter(QLoggingCategory *category)
|
||||
category->setEnabled(QtDebugMsg, debugEnabled);
|
||||
category->setEnabled(QtWarningMsg, debugEnabled || s_loggingFilters.value("Warnings"));
|
||||
} else {
|
||||
// Enable default debug output
|
||||
category->setEnabled(QtDebugMsg, true);
|
||||
category->setEnabled(QtWarningMsg, s_loggingFilters.value("Warnings"));
|
||||
}
|
||||
@ -86,14 +88,14 @@ int main(int argc, char *argv[])
|
||||
application.setOrganizationName("guh");
|
||||
application.setApplicationVersion("0.0.1");
|
||||
|
||||
s_loggingFilters.insert("Application", true);
|
||||
s_loggingFilters.insert("Engine", true);
|
||||
s_loggingFilters.insert("Application", true);
|
||||
s_loggingFilters.insert("JsonRpc", true);
|
||||
s_loggingFilters.insert("JsonRpcTraffic", true);
|
||||
s_loggingFilters.insert("WebSocketServer", true);
|
||||
s_loggingFilters.insert("WebSocketServerTraffic", false);
|
||||
s_loggingFilters.insert("Authenticator", true);
|
||||
s_loggingFilters.insert("ConnectionManager", true);
|
||||
s_loggingFilters.insert("Debug", false);
|
||||
s_loggingFilters.insert("ProxyServer", true);
|
||||
|
||||
// command line parser
|
||||
QCommandLineParser parser;
|
||||
@ -226,7 +228,11 @@ int main(int argc, char *argv[])
|
||||
if (s_loggingEnabled)
|
||||
qCDebug(dcApplication()) << "Logging enabled. Writing logs to" << s_logFile.fileName();
|
||||
|
||||
// Create default authenticator
|
||||
AwsAuthenticator *authenticator = new AwsAuthenticator(nullptr);
|
||||
|
||||
// Configure and start the engines
|
||||
Engine::instance()->setAuthenticator(authenticator);
|
||||
Engine::instance()->setWebSocketServerHostAddress(serverHostAddress);
|
||||
Engine::instance()->setWebSocketServerPort(static_cast<quint16>(port));
|
||||
Engine::instance()->setSslConfiguration(sslConfiguration);
|
||||
|
||||
25
tests/mockauthenticator.cpp
Normal file
25
tests/mockauthenticator.cpp
Normal file
@ -0,0 +1,25 @@
|
||||
#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()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
AuthenticationReply *MockAuthenticator::authenticate(const QUuid &clientId, const QString &token)
|
||||
{
|
||||
qCDebug(dcAuthenticator()) << "MockAuthenticator: Start authentication for" << clientId << "using token" << token;
|
||||
AuthenticationReply *reply = new AuthenticationReply(clientId, token, this);
|
||||
return reply;
|
||||
}
|
||||
30
tests/mockauthenticator.h
Normal file
30
tests/mockauthenticator.h
Normal file
@ -0,0 +1,30 @@
|
||||
#ifndef MOCKAUTHENTICATOR_H
|
||||
#define MOCKAUTHENTICATOR_H
|
||||
|
||||
#include <QTimer>
|
||||
#include <QObject>
|
||||
|
||||
#include "authentication/authenticator.h"
|
||||
|
||||
class MockAuthenticator : public Authenticator
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MockAuthenticator(QObject *parent = nullptr);
|
||||
|
||||
void setTimeoutDuration(int timeout);
|
||||
void setExpectedAuthenticationError(Authenticator::AuthenticationError error);
|
||||
|
||||
private:
|
||||
QTimer * m_timer = nullptr;
|
||||
int m_timeoutDuration = 1000;
|
||||
Authenticator::AuthenticationError m_expectedError;
|
||||
|
||||
private slots:
|
||||
void onTimeout();
|
||||
|
||||
public slots:
|
||||
AuthenticationReply *authenticate(const QUuid &clientId, const QString &token) override;
|
||||
};
|
||||
|
||||
#endif // MOCKAUTHENTICATOR_H
|
||||
@ -4,6 +4,7 @@
|
||||
#include "loggingcategories.h"
|
||||
#include "remoteproxyconnector.h"
|
||||
|
||||
#include <QMetaType>
|
||||
#include <QSignalSpy>
|
||||
|
||||
RemoteProxyTests::RemoteProxyTests(QObject *parent) :
|
||||
@ -26,6 +27,7 @@ RemoteProxyTests::RemoteProxyTests(QObject *parent) :
|
||||
|
||||
QByteArray keyData = keyFile.readAll();
|
||||
//qDebug() << "Certificate key:" << endl << qUtf8Printable(keyData);
|
||||
m_authenticator = new MockAuthenticator(this);
|
||||
|
||||
m_sslConfiguration.setPrivateKey(QSslKey(keyData, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey));
|
||||
m_sslConfiguration.setLocalCertificate(QSslCertificate(certificateData, QSsl::Pem));
|
||||
@ -53,14 +55,29 @@ void RemoteProxyTests::startServer()
|
||||
{
|
||||
restartEngine();
|
||||
|
||||
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()->running());
|
||||
|
||||
}
|
||||
|
||||
void RemoteProxyTests::stopServer()
|
||||
{
|
||||
if (!Engine::instance()->running())
|
||||
return;
|
||||
|
||||
Engine::instance()->stop();
|
||||
QVERIFY(!Engine::instance()->running());
|
||||
}
|
||||
|
||||
void RemoteProxyTests::initTestCase()
|
||||
{
|
||||
qRegisterMetaType<RemoteProxyConnector::Error>();
|
||||
|
||||
qCDebug(dcApplication()) << "Init test case.";
|
||||
restartEngine();
|
||||
}
|
||||
@ -73,37 +90,28 @@ void RemoteProxyTests::cleanupTestCase()
|
||||
|
||||
void RemoteProxyTests::authenticate()
|
||||
{
|
||||
// Start the server
|
||||
startServer();
|
||||
// // Start the server
|
||||
// startServer();
|
||||
|
||||
// Connect to the server
|
||||
RemoteProxyConnector *connector = new RemoteProxyConnector(this);
|
||||
connector->setIgnoreSslErrors(QList<QSslError>() << QSslError::HostNameMismatch << QSslError::SelfSignedCertificate);
|
||||
// // Connect to the server
|
||||
// RemoteProxyConnector *connector = new RemoteProxyConnector(this);
|
||||
// connector->setInsecureConnection(true);
|
||||
|
||||
QSignalSpy spy(connector, &RemoteProxyConnector::error);
|
||||
connector->connectServer(RemoteProxyConnector::ConnectionTypeWebSocket, QHostAddress::LocalHost, m_port);
|
||||
//spy.wait();
|
||||
// QSignalSpy spy(connector, &RemoteProxyConnector::connected);
|
||||
// connector->connectServer(QHostAddress::LocalHost, m_port);
|
||||
// spy.wait();
|
||||
|
||||
connector->disconnectServer();
|
||||
connector->deleteLater();
|
||||
Engine::instance()->stop();
|
||||
// connector->disconnectServer();
|
||||
// connector->deleteLater();
|
||||
// Engine::instance()->stop();
|
||||
}
|
||||
|
||||
void RemoteProxyTests::startStopServer()
|
||||
{
|
||||
restartEngine();
|
||||
startServer();
|
||||
stopServer();
|
||||
cleanUpEngine();
|
||||
|
||||
QVERIFY(Engine::instance() != nullptr);
|
||||
QVERIFY(Engine::exists());
|
||||
|
||||
Engine::instance()->start();
|
||||
QVERIFY(Engine::instance()->running());
|
||||
|
||||
Engine::instance()->stop();
|
||||
QVERIFY(!Engine::instance()->running());
|
||||
|
||||
Engine::instance()->destroy();
|
||||
QVERIFY(!Engine::exists());
|
||||
}
|
||||
|
||||
void RemoteProxyTests::sslConfigurations()
|
||||
@ -111,15 +119,30 @@ void RemoteProxyTests::sslConfigurations()
|
||||
// Start the server
|
||||
startServer();
|
||||
|
||||
// Connect to the server
|
||||
// Connect to the server (insecure disabled)
|
||||
RemoteProxyConnector *connector = new RemoteProxyConnector(this);
|
||||
connector->setIgnoreSslErrors(QList<QSslError>() << QSslError::HostNameMismatch << QSslError::SelfSignedCertificate);
|
||||
connector->setInsecureConnection(false);
|
||||
|
||||
QSignalSpy spy(connector, &RemoteProxyConnector::connected);
|
||||
connector->connectServer(RemoteProxyConnector::ConnectionTypeWebSocket, QHostAddress::LocalHost, m_port);
|
||||
spy.wait();
|
||||
QSignalSpy spyError(connector, &RemoteProxyConnector::errorOccured);
|
||||
connector->connectServer(QHostAddress::LocalHost, m_port);
|
||||
spyError.wait();
|
||||
|
||||
QCOMPARE(connector->error(), RemoteProxyConnector::ErrorSocketError);
|
||||
QCOMPARE(connector->socketError(), QAbstractSocket::SslHandshakeFailedError);
|
||||
QCOMPARE(connector->state(), RemoteProxyConnector::StateDisconnected);
|
||||
|
||||
// Connect to server (insecue enabled)
|
||||
QSignalSpy spyConnected(connector, &RemoteProxyConnector::connected);
|
||||
connector->setInsecureConnection(true);
|
||||
connector->connectServer(QHostAddress::LocalHost, m_port);
|
||||
spyConnected.wait();
|
||||
|
||||
QVERIFY(connector->isConnected());
|
||||
|
||||
// Disconnect and clean up
|
||||
connector->disconnectServer();
|
||||
QVERIFY(!connector->isConnected());
|
||||
|
||||
connector->deleteLater();
|
||||
Engine::instance()->stop();
|
||||
}
|
||||
|
||||
@ -9,6 +9,8 @@
|
||||
#include <QSslCertificate>
|
||||
#include <QSslConfiguration>
|
||||
|
||||
#include "mockauthenticator.h"
|
||||
|
||||
class RemoteProxyTests : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -19,10 +21,12 @@ private:
|
||||
quint16 m_port = 1212;
|
||||
QHostAddress m_serverAddress = QHostAddress::LocalHost;
|
||||
QSslConfiguration m_sslConfiguration;
|
||||
MockAuthenticator *m_authenticator = nullptr;
|
||||
|
||||
void cleanUpEngine();
|
||||
void restartEngine();
|
||||
void startServer();
|
||||
void stopServer();
|
||||
|
||||
protected slots:
|
||||
void initTestCase();
|
||||
|
||||
@ -11,8 +11,10 @@ LIBS += -L$$top_builddir/libnymea-remoteproxy/ -lnymea-remoteproxy \
|
||||
|
||||
RESOURCES += certificate.qrc
|
||||
|
||||
HEADERS += nymea-remoteproxy-tests.h
|
||||
SOURCES += nymea-remoteproxy-tests.cpp
|
||||
HEADERS += nymea-remoteproxy-tests.h \
|
||||
mockauthenticator.h
|
||||
SOURCES += nymea-remoteproxy-tests.cpp \
|
||||
mockauthenticator.cpp
|
||||
|
||||
target.path = /usr/bin
|
||||
INSTALLS += target
|
||||
|
||||
Reference in New Issue
Block a user