Start implementing JsonRpcServer

This commit is contained in:
Simon Stürz 2018-08-04 13:54:30 +02:00
parent 43ebf38f30
commit 538cb3d799
43 changed files with 1629 additions and 96 deletions

View File

@ -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"
}
}

View 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()
{
}

View 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

View File

@ -0,0 +1,13 @@
#include "authenticator.h"
#include "authenticationreply.h"
Authenticator::Authenticator(QObject *parent) :
QObject(parent)
{
}
Authenticator::~Authenticator()
{
}

View 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

View 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;
}

View 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

View File

@ -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;

View File

@ -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);

View 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 &params, 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);
}

View 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 &params, const QUuid &clientId);
private:
QHash<AuthenticationReply *, JsonReply *> m_runningAuthentications;
private slots:
void onAuthenticationFinished();
};
#endif // AUTHENTICATIONHANDLER_H

View 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 &params)
{
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 &params)
{
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);
}

View 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 &params);
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 &params);
protected:
void setDescription(const QString &methodName, const QString &description);
void setParams(const QString &methodName, const QVariantMap &params);
void setReturns(const QString &methodName, const QVariantMap &returns);
JsonReply *createReply(const QVariantMap &data) const;
JsonReply *createAsyncReply(const QString &method) const;
};
#endif // JSONHANDLER_H

View 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);
}

View 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

View 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;
}

View 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

View 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 &params) 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 &params) 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 &params)
{
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));
}

View 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 &params) const;
Q_INVOKABLE JsonReply *Introspect(const QVariantMap &params) 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 &params = 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 &params);
void asyncReplyFinished();
};
#endif // JSONRPCSERVER_H

View File

@ -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

View File

@ -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")

View File

@ -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

View 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;
}

View 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

View File

@ -0,0 +1,6 @@
#include "proxyconfiguration.h"
ProxyConfiguration::ProxyConfiguration(QObject *parent) : QObject(parent)
{
}

View 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

View 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();
}
}

View 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

View File

@ -6,6 +6,11 @@ TransportInterface::TransportInterface(QObject *parent) :
}
QString TransportInterface::serverName() const
{
return m_serverName;
}
TransportInterface::~TransportInterface()
{

View File

@ -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;

View File

@ -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");

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -0,0 +1,6 @@
#include "websocketconnector.h"
WebSocketConnector::WebSocketConnector(QObject *parent) : QObject(parent)
{
}

View 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

View File

@ -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

View File

@ -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);

View 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
View 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

View File

@ -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();
}

View File

@ -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();

View File

@ -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