From bd7653433fb36e93bd864296230c352b185c5b95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 2 Aug 2018 14:04:42 +0200 Subject: [PATCH] Add basic structure of the server --- libnymea-remoteproxy/engine.cpp | 94 ++++++++ libnymea-remoteproxy/engine.h | 46 ++++ libnymea-remoteproxy/libnymea-remoteproxy.pro | 12 + libnymea-remoteproxy/loggingcategories.cpp | 10 + libnymea-remoteproxy/loggingcategories.h | 15 ++ main.cpp | 8 - nymea-remoteproxy.pri | 10 + nymea-remoteproxy.pro | 25 +- server/main.cpp | 216 ++++++++++++++++++ server/server.pro | 13 ++ tests/nymea-remoteproxy-tests.cpp | 59 +++++ tests/nymea-remoteproxy-tests.h | 30 +++ tests/tests.pro | 15 ++ 13 files changed, 525 insertions(+), 28 deletions(-) create mode 100644 libnymea-remoteproxy/engine.cpp create mode 100644 libnymea-remoteproxy/engine.h create mode 100644 libnymea-remoteproxy/libnymea-remoteproxy.pro create mode 100644 libnymea-remoteproxy/loggingcategories.cpp create mode 100644 libnymea-remoteproxy/loggingcategories.h delete mode 100644 main.cpp create mode 100644 nymea-remoteproxy.pri create mode 100644 server/main.cpp create mode 100644 server/server.pro create mode 100644 tests/nymea-remoteproxy-tests.cpp create mode 100644 tests/nymea-remoteproxy-tests.h create mode 100644 tests/tests.pro diff --git a/libnymea-remoteproxy/engine.cpp b/libnymea-remoteproxy/engine.cpp new file mode 100644 index 0000000..64431d9 --- /dev/null +++ b/libnymea-remoteproxy/engine.cpp @@ -0,0 +1,94 @@ +#include "engine.h" +#include "loggingcategories.h" + +Engine *Engine::s_instance = nullptr; + +Engine *Engine::instance() +{ + if (!s_instance) { + qCDebug(dcEngine()) << "Create server engine"; + s_instance = new Engine(); + } + + return s_instance; +} + +bool Engine::exists() +{ + return s_instance != nullptr; +} + +void Engine::destroy() +{ + qCDebug(dcEngine()) << "Destroy server engine"; + if (s_instance) { + delete s_instance; + } + + s_instance = nullptr; +} + +void Engine::start() +{ + qCDebug(dcEngine()) << "Start server engine"; + QUrl proxyUrl; + proxyUrl.setScheme("wss"); + proxyUrl.setHost("0.0.0.0"); + proxyUrl.setPort(static_cast(m_port)); + qCDebug(dcApplication()) << "Authentication server" << m_authenticationServerUrl.toString(); + qCDebug(dcApplication()) << "Start server" << proxyUrl.toString(); + + // TODO: init stuff + + setRunning(true); +} + +void Engine::stop() +{ + qCDebug(dcEngine()) << "Stop server engine"; + + // TODO: deinit stuff + + setRunning(false); +} + +bool Engine::running() const +{ + return m_running; +} + +void Engine::setHost(const QHostAddress &hostAddress) +{ + m_hostAddress = hostAddress; +} + +void Engine::setAuthenticationServerUrl(const QUrl &url) +{ + m_authenticationServerUrl = url; +} + +void Engine::setPort(const quint16 &port) +{ + m_port = port; +} + +void Engine::setSslConfiguration(const QSslConfiguration &configuration) +{ + m_sslConfiguration = configuration; +} + +Engine::Engine(QObject *parent) : + QObject(parent) +{ + +} + +void Engine::setRunning(bool running) +{ + if (m_running == running) + return; + + qCDebug(dcEngine()) << "Engine is" << (running ? "now running." : "not running any more."); + m_running = running; + emit runningChanged(m_running); +} diff --git a/libnymea-remoteproxy/engine.h b/libnymea-remoteproxy/engine.h new file mode 100644 index 0000000..59029e0 --- /dev/null +++ b/libnymea-remoteproxy/engine.h @@ -0,0 +1,46 @@ +#ifndef ENGINE_H +#define ENGINE_H + +#include +#include +#include +#include + +class Engine : public QObject +{ + Q_OBJECT +public: + static Engine *instance(); + static bool exists(); + void destroy(); + + void start(); + void stop(); + + bool running() const; + + void setHost(const QHostAddress &hostAddress); + void setAuthenticationServerUrl(const QUrl &url); + void setPort(const quint16 &port); + void setSslConfiguration(const QSslConfiguration &configuration); + +private: + explicit Engine(QObject *parent = nullptr); + static Engine *s_instance; + + bool m_running = false; + + quint16 m_port = 0; + QHostAddress m_hostAddress; + QSslConfiguration m_sslConfiguration; + + QUrl m_authenticationServerUrl; + + void setRunning(bool running); + +signals: + void runningChanged(bool running); + +}; + +#endif // ENGINE_H diff --git a/libnymea-remoteproxy/libnymea-remoteproxy.pro b/libnymea-remoteproxy/libnymea-remoteproxy.pro new file mode 100644 index 0000000..0d0aa84 --- /dev/null +++ b/libnymea-remoteproxy/libnymea-remoteproxy.pro @@ -0,0 +1,12 @@ +include(../nymea-remoteproxy.pri) + +TEMPLATE = lib +TARGET = nymea-remoteproxy + +HEADERS += \ + engine.h \ + loggingcategories.h + +SOURCES += \ + engine.cpp \ + loggingcategories.cpp diff --git a/libnymea-remoteproxy/loggingcategories.cpp b/libnymea-remoteproxy/loggingcategories.cpp new file mode 100644 index 0000000..193f15c --- /dev/null +++ b/libnymea-remoteproxy/loggingcategories.cpp @@ -0,0 +1,10 @@ +#include "loggingcategories.h" + +Q_LOGGING_CATEGORY(dcApplication, "Application") +Q_LOGGING_CATEGORY(dcEngine, "Engine") +Q_LOGGING_CATEGORY(dcJsonRpc, "JsonRpc") +Q_LOGGING_CATEGORY(dcWebSocketServer, "WebSocketServer") +Q_LOGGING_CATEGORY(dcAuthenticator, "Authenticator") +Q_LOGGING_CATEGORY(dcDebug, "Debug") +Q_LOGGING_CATEGORY(dcConnectionManager, "ConnectionManager") + diff --git a/libnymea-remoteproxy/loggingcategories.h b/libnymea-remoteproxy/loggingcategories.h new file mode 100644 index 0000000..c0891f1 --- /dev/null +++ b/libnymea-remoteproxy/loggingcategories.h @@ -0,0 +1,15 @@ +#ifndef LOGGINGCATEGORIES_H +#define LOGGINGCATEGORIES_H + +#include +#include + +Q_DECLARE_LOGGING_CATEGORY(dcApplication) +Q_DECLARE_LOGGING_CATEGORY(dcEngine) +Q_DECLARE_LOGGING_CATEGORY(dcJsonRpc) +Q_DECLARE_LOGGING_CATEGORY(dcWebSocketServer) +Q_DECLARE_LOGGING_CATEGORY(dcAuthenticator) +Q_DECLARE_LOGGING_CATEGORY(dcConnectionManager) +Q_DECLARE_LOGGING_CATEGORY(dcDebug) + +#endif // LOGGINGCATEGORIES_H diff --git a/main.cpp b/main.cpp deleted file mode 100644 index 470a030..0000000 --- a/main.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include - -int main(int argc, char *argv[]) -{ - QCoreApplication a(argc, argv); - - return a.exec(); -} diff --git a/nymea-remoteproxy.pri b/nymea-remoteproxy.pri new file mode 100644 index 0000000..cccb9ae --- /dev/null +++ b/nymea-remoteproxy.pri @@ -0,0 +1,10 @@ +QT *= network +QT -= gui + +CONFIG += c++11 console + +QMAKE_CXXFLAGS *= -Werror -std=c++11 -g +QMAKE_LFLAGS *= -std=c++11 + +top_srcdir=$$PWD +top_builddir=$$shadowed($$PWD) diff --git a/nymea-remoteproxy.pro b/nymea-remoteproxy.pro index a493861..1e6ecac 100644 --- a/nymea-remoteproxy.pro +++ b/nymea-remoteproxy.pro @@ -1,23 +1,8 @@ -QT -= gui +include(nymea-remoteproxy.pri) -CONFIG += c++11 console -CONFIG -= app_bundle +TEMPLATE=subdirs +SUBDIRS += server libnymea-remoteproxy tests -# The following define makes your compiler emit warnings if you use -# any feature of Qt which as been marked deprecated (the exact warnings -# depend on your compiler). Please consult the documentation of the -# deprecated API in order to know how to port your code away from it. -DEFINES += QT_DEPRECATED_WARNINGS +server.depends = libnymea-remoteproxy +tests.depends = libnymea-remoteproxy -# You can also make your code fail to compile if you use deprecated APIs. -# In order to do so, uncomment the following line. -# You can also select to disable deprecated APIs only up to a certain version of Qt. -#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 - -SOURCES += \ - main.cpp - -# Default rules for deployment. -qnx: target.path = /tmp/$${TARGET}/bin -else: unix:!android: target.path = /opt/$${TARGET}/bin -!isEmpty(target.path): INSTALLS += target diff --git a/server/main.cpp b/server/main.cpp new file mode 100644 index 0000000..8be4d87 --- /dev/null +++ b/server/main.cpp @@ -0,0 +1,216 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "engine.h" +#include "loggingcategories.h" + + +static QHash s_loggingFilters; + +static QFile s_logFile; +static bool s_loggingEnabled = false; + +static const char *const normal = "\033[0m"; +static const char *const warning = "\e[33m"; +static const char *const error = "\e[31m"; + +static void loggingCategoryFilter(QLoggingCategory *category) +{ + if (s_loggingFilters.contains(category->categoryName())) { + bool debugEnabled = s_loggingFilters.value(category->categoryName()); + category->setEnabled(QtDebugMsg, debugEnabled); + category->setEnabled(QtWarningMsg, debugEnabled || s_loggingFilters.value("Warnings")); + } else { + category->setEnabled(QtDebugMsg, true); + category->setEnabled(QtWarningMsg, s_loggingFilters.value("Warnings")); + } +} + +static void consoleLogHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) +{ + QString messageString; + QString timeString = QDateTime::currentDateTime().toString("yyyy.MM.dd hh:mm:ss.zzz"); + switch (type) { + case QtInfoMsg: + messageString = QString(" I %1 | %2: %3").arg(timeString).arg(context.category).arg(message); + fprintf(stdout, " I | %s: %s\n", context.category, message.toUtf8().data()); + break; + case QtDebugMsg: + messageString = QString(" I %1 | %2: %3").arg(timeString).arg(context.category).arg(message); + fprintf(stdout, " I | %s: %s\n", context.category, message.toUtf8().data()); + break; + case QtWarningMsg: + messageString = QString(" W %1 | %2: %3").arg(timeString).arg(context.category).arg(message); + fprintf(stdout, "%s W | %s: %s%s\n", warning, context.category, message.toUtf8().data(), normal); + break; + case QtCriticalMsg: + messageString = QString(" C %1 | %2: %3").arg(timeString).arg(context.category).arg(message); + fprintf(stdout, "%s C | %s: %s%s\n", error, context.category, message.toUtf8().data(), normal); + break; + case QtFatalMsg: + messageString = QString(" F %1 | %2: %3").arg(timeString).arg(context.category).arg(message); + fprintf(stdout, "%s F | %s: %s%s\n", error, context.category, message.toUtf8().data(), normal); + break; + } + fflush(stdout); + + if (s_logFile.isOpen()) { + QTextStream textStream(&s_logFile); + textStream << messageString << endl; + } +} + + +int main(int argc, char *argv[]) +{ + qInstallMessageHandler(consoleLogHandler); + + QCoreApplication application(argc, argv); + application.setApplicationName("nymea-remoteproxy"); + application.setOrganizationName("guh"); + application.setApplicationVersion("0.0.1"); + + s_loggingFilters.insert("Application", true); + s_loggingFilters.insert("Engine", true); + s_loggingFilters.insert("JsonRpc", true); + s_loggingFilters.insert("WebSocketServer", true); + s_loggingFilters.insert("Authenticator", true); + s_loggingFilters.insert("ConnectionManager", true); + s_loggingFilters.insert("Debug", false); + + // command line parser + QCommandLineParser parser; + parser.addHelpOption(); + parser.setApplicationDescription(QString("\nThe nymea remote proxy server.\n\n" + "Copyright %1 2018 Simon Stürz ").arg(QChar(0xA9))); + + QCommandLineOption logfileOption(QStringList() << "l" << "logging", "Write log file to the given logfile.", "logfile", "/var/log/nymea-remoteproxy.log"); + parser.addOption(logfileOption); + + QCommandLineOption serverOption(QStringList() << "s" << "server", "The authentication server url.", "url", "http://localhost:8000"); + parser.addOption(serverOption); + + QCommandLineOption portOption(QStringList() << "p" << "port", "The proxy server port.", "port", "1212"); + parser.addOption(portOption); + + QCommandLineOption certOption(QStringList() << "c" <<"certificate", "The path to the SSL certificate.", "certificate"); + parser.addOption(certOption); + + QCommandLineOption certKeyOption(QStringList() << "k" << "certificate-key", "The path to the SSL certificate key (KEY).", "certificate-key"); + parser.addOption(certKeyOption); + + QCommandLineOption verboseOption(QStringList() << "v" << "verbose", "Print the whole traffic."); + parser.addOption(verboseOption); + + parser.process(application); + + if (parser.isSet(verboseOption)) + s_loggingFilters["Debug"] = true; + + QLoggingCategory::installFilter(loggingCategoryFilter); + + // Open the logfile, if any specified + if (parser.isSet(logfileOption)) { + QFileInfo fi(parser.value(logfileOption)); + QDir logDir(fi.absolutePath()); + if (!logDir.exists() && !logDir.mkpath(logDir.absolutePath())) { + qCCritical(dcApplication()) << "Error opening log file" << parser.value(logfileOption); + return 1; + } + s_logFile.setFileName(parser.value(logfileOption)); + if (!s_logFile.open(QFile::WriteOnly | QFile::Append)) { + qCCritical(dcApplication()) << "Error opening log file" << parser.value(logfileOption); + return 1; + } + s_loggingEnabled = true; + } + + qCDebug(dcApplication()) << "=============================================="; + qCDebug(dcApplication()) << "Starting" << application.applicationName() << application.applicationVersion(); + qCDebug(dcApplication()) << "=============================================="; + + if (s_loggingEnabled) + qCDebug(dcApplication()) << "Logging enabled. Writing logs to" << s_logFile.fileName(); + + // Authentication server url + QUrl serverUrl("http://localhost:8000"); + if (parser.isSet(serverOption)) + serverUrl = QUrl(parser.value(serverOption)); + + if (!serverUrl.isValid()) { + qCCritical(dcApplication()) << "Invalid authentication server url:" << parser.value(serverOption); + exit(-1); + } + + // Port + uint port = 1212; + if (parser.isSet(portOption)) { + bool ok = false; + port = parser.value(portOption).toUInt(&ok); + if (!ok) { + qCCritical(dcApplication()) << "Invalud port value:" << parser.value(portOption); + exit(-1); + } + + if (port > 65535) { + qCCritical(dcApplication()) << "Port value is out of range:" << parser.value(portOption); + exit(-1); + } + } + + // SSL certificate + QSslConfiguration sslConfiguration; + if (parser.isSet(certOption)) { + // Load certificate + QFile certFile(parser.value(certOption)); + if (!certFile.open(QIODevice::ReadOnly)) { + qCCritical(dcApplication()) << "Could not open certificate file:" << parser.value(certOption) << certFile.errorString(); + exit(-1); + } + + QSslCertificate certificate(&certFile, QSsl::Pem); + qCDebug(dcApplication()) << "Loaded successfully certificate" << parser.value(certOption); + certFile.close(); + + // Create SSL configuration + sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone); + sslConfiguration.setLocalCertificate(certificate); + sslConfiguration.setProtocol(QSsl::TlsV1_2OrLater); + } + + // SSL key + if (parser.isSet(certKeyOption)) { + QFile certKeyFile(parser.value(certKeyOption)); + if (!certKeyFile.open(QIODevice::ReadOnly)) { + qCCritical(dcApplication()) << "ERROR: Could not open certificate key file:" << parser.value(certKeyOption) << certKeyFile.errorString(); + exit(-1); + } + QSslKey sslKey(&certKeyFile, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey); + qCDebug(dcApplication()) << "Loaded successfully certificate key" << parser.value(certKeyOption); + certKeyFile.close(); + sslConfiguration.setPrivateKey(sslKey); + } + + Engine::instance()->setAuthenticationServerUrl(serverUrl); + Engine::instance()->setPort(static_cast(port)); + Engine::instance()->setSslConfiguration(sslConfiguration); + Engine::instance()->start(); + + return application.exec(); +} diff --git a/server/server.pro b/server/server.pro new file mode 100644 index 0000000..5a87a7d --- /dev/null +++ b/server/server.pro @@ -0,0 +1,13 @@ +include(../nymea-remoteproxy.pri) + +TARGET = nymea-remoteproxy +TEMPLATE = app + +INCLUDEPATH += ../libnymea-remoteproxy + +LIBS += -L$$top_builddir/libnymea-remoteproxy/ -lnymea-remoteproxy + +SOURCES += main.cpp + +target.path = /usr/bin +INSTALLS += target diff --git a/tests/nymea-remoteproxy-tests.cpp b/tests/nymea-remoteproxy-tests.cpp new file mode 100644 index 0000000..0c51e95 --- /dev/null +++ b/tests/nymea-remoteproxy-tests.cpp @@ -0,0 +1,59 @@ +#include "nymea-remoteproxy-tests.h" + +#include "engine.h" +#include "loggingcategories.h" + +RemoteProxyTests::RemoteProxyTests(QObject *parent) : + QObject(parent) +{ + +} + +void RemoteProxyTests::cleanUpEngine() +{ + if (Engine::exists()) { + Engine::instance()->destroy(); + QVERIFY(!Engine::exists()); + } +} + +void RemoteProxyTests::restartEngine() +{ + cleanUpEngine(); + + QVERIFY(Engine::instance() != nullptr); + QVERIFY(Engine::exists()); +} + +void RemoteProxyTests::initTestCase() +{ + qCDebug(dcApplication()) << "Init test case."; + + restartEngine(); +} + +void RemoteProxyTests::cleanupTestCase() +{ + qCDebug(dcApplication()) << "Clean up test case."; + + cleanUpEngine(); +} + +void RemoteProxyTests::startStopServer() +{ + 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()); +} + +QTEST_MAIN(RemoteProxyTests) diff --git a/tests/nymea-remoteproxy-tests.h b/tests/nymea-remoteproxy-tests.h new file mode 100644 index 0000000..9d72ac4 --- /dev/null +++ b/tests/nymea-remoteproxy-tests.h @@ -0,0 +1,30 @@ +#ifndef NYMEA_REMOTEPROXY_TESTS_H +#define NYMEA_REMOTEPROXY_TESTS_H + +#include +#include + +class RemoteProxyTests : public QObject +{ + Q_OBJECT +public: + explicit RemoteProxyTests(QObject *parent = nullptr); + +private: + void cleanUpEngine(); + void restartEngine(); + +protected slots: + void initTestCase(); + void cleanupTestCase(); + + +private slots: + void startStopServer(); + + + + +}; + +#endif // NYMEA_REMOTEPROXY_TESTS_H diff --git a/tests/tests.pro b/tests/tests.pro new file mode 100644 index 0000000..64d4782 --- /dev/null +++ b/tests/tests.pro @@ -0,0 +1,15 @@ +include(../nymea-remoteproxy.pri) + +CONFIG += testcase +QT += testlib + +TARGET = nymea-remoteproxy-tests + +INCLUDEPATH += ../libnymea-remoteproxy +LIBS += -L$$top_builddir/libnymea-remoteproxy/ -lnymea-remoteproxy + +HEADERS += nymea-remoteproxy-tests.h +SOURCES += nymea-remoteproxy-tests.cpp + +target.path = /usr/bin +INSTALLS += target