From 43ebf38f30869444b75980fc44f82706f63e90d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 2 Aug 2018 21:20:22 +0200 Subject: [PATCH] Add first version of remote client connector in tests --- libnymea-remoteproxy/engine.cpp | 22 +- libnymea-remoteproxy/engine.h | 4 +- .../remoteproxyconnector.cpp | 251 ++++++++++++++++++ .../remoteproxyconnector.h | 85 ++++++ server/main.cpp | 23 +- tests/nymea-remoteproxy-tests.cpp | 32 ++- tests/nymea-remoteproxy-tests.h | 2 + tests/tests.pro | 5 +- 8 files changed, 403 insertions(+), 21 deletions(-) diff --git a/libnymea-remoteproxy/engine.cpp b/libnymea-remoteproxy/engine.cpp index ce6dcc1..2465ca2 100644 --- a/libnymea-remoteproxy/engine.cpp +++ b/libnymea-remoteproxy/engine.cpp @@ -1,5 +1,4 @@ #include "engine.h" -#include "websocketserver.h" #include "loggingcategories.h" Engine *Engine::s_instance = nullptr; @@ -34,22 +33,20 @@ void Engine::start() if (!m_running) qCDebug(dcEngine()) << "Start server engine"; - QUrl proxyUrl; - proxyUrl.setScheme("wss"); - proxyUrl.setHost(m_webSocketServerHostAddress.toString()); - proxyUrl.setPort(m_webSocketServerPort); - - qCDebug(dcApplication()) << "Authentication server" << m_authenticationServerUrl.toString(); - qCDebug(dcApplication()) << "Start server" << proxyUrl.toString(); - + qCDebug(dcEngine()) << "Starting websocket server"; // Init WebSocketServer if (m_webSocketServer) { delete m_webSocketServer; m_webSocketServer = nullptr; } + QUrl websocketServerUrl; + websocketServerUrl.setScheme("wss"); + websocketServerUrl.setHost(m_webSocketServerHostAddress.toString()); + websocketServerUrl.setPort(m_webSocketServerPort); + m_webSocketServer = new WebSocketServer(m_sslConfiguration, this); - m_webSocketServer->setServerUrl(proxyUrl); + m_webSocketServer->setServerUrl(websocketServerUrl); m_webSocketServer->startServer(); setRunning(true); @@ -106,6 +103,11 @@ void Engine::setAuthenticationServerUrl(const QUrl &url) m_authenticationServerUrl = url; } +WebSocketServer *Engine::webSocketServer() const +{ + return m_webSocketServer; +} + Engine::Engine(QObject *parent) : QObject(parent) { diff --git a/libnymea-remoteproxy/engine.h b/libnymea-remoteproxy/engine.h index b2d1e71..f309298 100644 --- a/libnymea-remoteproxy/engine.h +++ b/libnymea-remoteproxy/engine.h @@ -6,7 +6,7 @@ #include #include -class WebSocketServer; +#include "websocketserver.h" class Engine : public QObject { @@ -27,6 +27,8 @@ public: void setSslConfiguration(const QSslConfiguration &configuration); void setAuthenticationServerUrl(const QUrl &url); + WebSocketServer *webSocketServer() const; + private: explicit Engine(QObject *parent = nullptr); ~Engine(); diff --git a/libnymea-remoteproxyclient/remoteproxyconnector.cpp b/libnymea-remoteproxyclient/remoteproxyconnector.cpp index 5d9d6ba..87f9249 100644 --- a/libnymea-remoteproxyclient/remoteproxyconnector.cpp +++ b/libnymea-remoteproxyclient/remoteproxyconnector.cpp @@ -4,3 +4,254 @@ RemoteProxyConnector::RemoteProxyConnector(QObject *parent) : QObject(parent) { } + +RemoteProxyConnector::~RemoteProxyConnector() +{ + disconnectServer(); +} + +RemoteProxyConnector::Error RemoteProxyConnector::error() const +{ + return m_error; +} + +QString RemoteProxyConnector::errorString() const +{ + QString errorString; + switch (m_error) { + case ErrorNoError: + errorString = ""; + break; + case ErrorSocketError: + errorString = "Socket connection error occured: " + socketErrorString(); + break; + case ErrorSslError: + errorString = "Socket SSL error occured."; + break; + case ErrorProxyNotResponding: + errorString = "The proxy server does not respond."; + break; + case ErrorProxyAuthenticationFailed: + errorString = "The authentication on the proxy server failed."; + break; + } + + return errorString; +} + +QAbstractSocket::SocketError RemoteProxyConnector::socketError() const +{ + if (!m_webSocket) + return QAbstractSocket::UnknownSocketError; + + return m_webSocket->error(); +} + +QString RemoteProxyConnector::socketErrorString() const +{ + if (!m_webSocket) + return QString(); + + return m_webSocket->errorString(); +} + +QUrl RemoteProxyConnector::serverUrl() const +{ + QUrl serverUrl; + serverUrl.setScheme("wss"); + serverUrl.setHost(m_serverAddress.toString()); + serverUrl.setPort(m_serverPort); + return serverUrl; +} + +bool RemoteProxyConnector::isConnected() const +{ + return m_state == StateConnected; +} + +RemoteProxyConnector::ConnectionType RemoteProxyConnector::connectionType() const +{ + return m_connectionType; +} + +QHostAddress RemoteProxyConnector::serverAddress() const +{ + return m_serverAddress; +} + +quint16 RemoteProxyConnector::serverPort() const +{ + return m_serverPort; +} + +QList RemoteProxyConnector::ignoreSslErrors() const +{ + return m_ignoreSslErrors; +} + +void RemoteProxyConnector::setIgnoreSslErrors(const QList &errors) +{ + m_ignoreSslErrors = errors; +} + +bool RemoteProxyConnector::sendData(const QByteArray &data) +{ + 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"; + return false; + } + + qint64 dataSendCount = m_webSocket->sendTextMessage(QString::fromUtf8(data)); + if (dataSendCount != data.count()) { + qWarning() << "RemoteProxyClient: Could not send all data to" << serverUrl().toString(); + return false; + } + + return true; +} + +void RemoteProxyConnector::setState(RemoteProxyConnector::State state) +{ + if (m_state == state) + return; + + qDebug() << "RemoteProxyClient: State changed" << state; + m_state = state; + emit stateChanged(m_state); +} + +void RemoteProxyConnector::setError(RemoteProxyConnector::Error error) +{ + if (m_error == error) + return; + + qDebug() << "RemoteProxyClient: Error occured" << error; + m_error = error; + emit errorOccured(m_error); +} + + +void RemoteProxyConnector::setConnectionType(RemoteProxyConnector::ConnectionType type) +{ + m_connectionType = type; +} + +void RemoteProxyConnector::setServerAddress(const QHostAddress serverAddress) +{ + m_serverAddress = serverAddress; +} + +void RemoteProxyConnector::setServerPort(quint16 serverPort) +{ + m_serverPort = serverPort; +} + +void RemoteProxyConnector::onSocketConnected() +{ + setState(StateConnected); + qDebug() << "RemoteProxyClient: Connected to" << serverUrl().toString(); + + // TODO: start authentication process + + setState(StateAuthenticating); +} + +void RemoteProxyConnector::onSocketDisconnected() +{ + qDebug() << "RemoteProxyClient: Disconnected from" << serverUrl().toString(); + setState(StateDisconnected); +} + +void RemoteProxyConnector::onSocketError(QAbstractSocket::SocketError error) +{ + qWarning() << "RemoteProxyClient: Socket error occured" << error; + setError(ErrorSocketError); +} + +void RemoteProxyConnector::onSocketSslError(const QList &errors) +{ + qWarning() << "RemoteProxyClient: Socket ssl errors occured" << errors; + foreach (const QSslError sslError, errors) { + qWarning() << "RemoteProxyClient: " << static_cast(sslError.error()) << sslError.errorString(); + } + + qDebug() << m_ignoreSslErrors; + + m_webSocket->ignoreSslErrors(); + setError(ErrorSslError); +} + +void RemoteProxyConnector::onSocketStateChanged(QAbstractSocket::SocketState state) +{ + qDebug() << "RemoteProxyClient: Socket state changed" << state; + switch (state) { + case QAbstractSocket::ConnectingState: + case QAbstractSocket::HostLookupState: + setState(StateConnecting); + break; + case QAbstractSocket::ConnectedState: + setState(StateConnected); + break; + default: + setState(StateDisconnected); + break; + } +} + +void RemoteProxyConnector::onTextMessageReceived(const QString &message) +{ + // TODO: check if tunnel is established, if so, emit data received + qDebug() << "RemoteProxyClient: Data recived" << message; +} + +void RemoteProxyConnector::onBinaryMessageReceived(const QByteArray &message) +{ + Q_UNUSED(message); +} + +bool RemoteProxyConnector::connectServer(RemoteProxyConnector::ConnectionType type, 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); + + m_webSocket = new QWebSocket("libnymea-remoteproxyclient", QWebSocketProtocol::VersionLatest, this); + m_webSocket->ignoreSslErrors(m_ignoreSslErrors); + + connect(m_webSocket, &QWebSocket::connected, this, &RemoteProxyConnector::onSocketConnected); + connect(m_webSocket, &QWebSocket::disconnected, this, &RemoteProxyConnector::onSocketDisconnected); + connect(m_webSocket, &QWebSocket::textMessageReceived, this, &RemoteProxyConnector::onTextMessageReceived); + connect(m_webSocket, &QWebSocket::binaryMessageReceived, this, &RemoteProxyConnector::onBinaryMessageReceived); + + connect(m_webSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onSocketError(QAbstractSocket::SocketError))); + connect(m_webSocket, SIGNAL(sslErrors(QList)), this, SLOT(onSocketSslError(QList))); + connect(m_webSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onSocketStateChanged(QAbstractSocket::SocketState))); + + setState(StateConnecting); + m_webSocket->open(serverUrl()); + qDebug() << "RemoteProxyClient: Start connecting to" << serverUrl().toString(); + return true; + } + + return false; +} + +void RemoteProxyConnector::disconnectServer() +{ + +} diff --git a/libnymea-remoteproxyclient/remoteproxyconnector.h b/libnymea-remoteproxyclient/remoteproxyconnector.h index b883259..4dd444a 100644 --- a/libnymea-remoteproxyclient/remoteproxyconnector.h +++ b/libnymea-remoteproxyclient/remoteproxyconnector.h @@ -2,16 +2,101 @@ #define REMOTEPROXYCONNECTOR_H #include +#include +#include class RemoteProxyConnector : public QObject { Q_OBJECT public: + enum ConnectionType { + ConnectionTypeWebSocket + }; + Q_ENUM(ConnectionType) + + enum State { + StateConnecting, + StateConnected, + StateAuthenticating, + StateWaitTunnel, + StateTunnelEstablished, + StateDisconnected + }; + Q_ENUM(State) + + enum Error { + ErrorNoError, + ErrorSocketError, + ErrorSslError, + ErrorProxyNotResponding, + ErrorProxyAuthenticationFailed + }; + Q_ENUM(Error) + explicit RemoteProxyConnector(QObject *parent = nullptr); + ~RemoteProxyConnector(); + + State state() const; + + Error error() const; + QString errorString() const; + + QAbstractSocket::SocketError socketError() const; + QString socketErrorString() const; + + QUrl serverUrl() const; + + bool isConnected() const; + bool tunnelEstablished() const; + + ConnectionType connectionType() const; + QHostAddress serverAddress() const; + quint16 serverPort() const; + + QList ignoreSslErrors() const; + void setIgnoreSslErrors(const QList &errors); + + bool sendData(const QByteArray &data); + +private: + ConnectionType m_connectionType = ConnectionTypeWebSocket; + QHostAddress m_serverAddress; + quint16 m_serverPort = 443; + State m_state = StateDisconnected; + Error m_error = ErrorNoError; + QList m_ignoreSslErrors; + bool m_tunnelEstablished = false; + QWebSocket *m_webSocket = nullptr; + + void setState(State state); + void setError(Error error); + + void setConnectionType(ConnectionType type); + void setServerAddress(const QHostAddress serverAddress); + void setServerPort(quint16 serverPort); signals: + void connected(); + void disconnected(); + void tunnelEstablished(); + void stateChanged(State state); + void errorOccured(Error error); + + void dataReady(const QByteArray &data); + +private slots: + void onSocketConnected(); + void onSocketDisconnected(); + void onSocketError(QAbstractSocket::SocketError error); + void onSocketSslError(const QList &errors); + void onSocketStateChanged(QAbstractSocket::SocketState state); + void onTextMessageReceived(const QString &message); + void onBinaryMessageReceived(const QByteArray &message); + public slots: + bool connectServer(ConnectionType type, const QHostAddress &serverAddress, quint16 port); + void disconnectServer(); }; diff --git a/server/main.cpp b/server/main.cpp index 9635ab0..1449eda 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -90,6 +90,7 @@ int main(int argc, char *argv[]) s_loggingFilters.insert("Engine", true); s_loggingFilters.insert("JsonRpc", 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); @@ -97,25 +98,31 @@ int main(int argc, char *argv[]) // command line parser QCommandLineParser parser; parser.addHelpOption(); - parser.setApplicationDescription(QString("\nThe nymea remote proxy server. This server allowes nymea-cloud users and registered nymea deamons to establish a tunnel connection.\n\n" + parser.setApplicationDescription(QString("\nThe nymea remote proxy server. This server allowes nymea-cloud users and " + "registered nymea deamons to establish a tunnel connection.\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"); + 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 server address this proxy will listen on. Default is 127.0.0.1", "hostaddress", "127.0.0.1"); + QCommandLineOption serverOption(QStringList() << "s" << "server", "The server address this proxy will listen on. " + "Default is 127.0.0.1", "hostaddress", "127.0.0.1"); parser.addOption(serverOption); QCommandLineOption portOption(QStringList() << "p" << "port", "The proxy server port. Default is 1212", "port", "1212"); parser.addOption(portOption); - QCommandLineOption certOption(QStringList() << "c" <<"certificate", "The path to the SSL certificate used for this proxy server.", "certificate"); + QCommandLineOption certOption(QStringList() << "c" <<"certificate", "The path to the SSL certificate used for " + "this proxy server.", "certificate"); parser.addOption(certOption); - QCommandLineOption certKeyOption(QStringList() << "k" << "certificate-key", "The path to the SSL certificate key used for this proxy server.", "certificate-key"); + QCommandLineOption certKeyOption(QStringList() << "k" << "certificate-key", "The path to the SSL certificate key " + "used for this proxy server.", "certificate-key"); parser.addOption(certKeyOption); - QCommandLineOption authenticationUrlOption(QStringList() << "a" << "authentication-server", "The server url of the AWS authentication server.", "url", "https://127.0.0.1"); + QCommandLineOption authenticationUrlOption(QStringList() << "a" << "authentication-server", + "The server url of the AWS authentication server.", "url", "https://127.0.0.1"); parser.addOption(authenticationUrlOption); QCommandLineOption verboseOption(QStringList() << "v" << "verbose", "Print more verbose."); @@ -123,8 +130,10 @@ int main(int argc, char *argv[]) parser.process(application); - if (parser.isSet(verboseOption)) + if (parser.isSet(verboseOption)) { s_loggingFilters["Debug"] = true; + s_loggingFilters["WebSocketServerTraffic"] = true; + } QLoggingCategory::installFilter(loggingCategoryFilter); diff --git a/tests/nymea-remoteproxy-tests.cpp b/tests/nymea-remoteproxy-tests.cpp index 5c1f50a..188fa38 100644 --- a/tests/nymea-remoteproxy-tests.cpp +++ b/tests/nymea-remoteproxy-tests.cpp @@ -2,6 +2,9 @@ #include "engine.h" #include "loggingcategories.h" +#include "remoteproxyconnector.h" + +#include RemoteProxyTests::RemoteProxyTests(QObject *parent) : QObject(parent) @@ -54,7 +57,6 @@ void RemoteProxyTests::startServer() Engine::instance()->setWebSocketServerHostAddress(QHostAddress::LocalHost); Engine::instance()->setSslConfiguration(m_sslConfiguration); Engine::instance()->start(); - } void RemoteProxyTests::initTestCase() @@ -71,9 +73,19 @@ void RemoteProxyTests::cleanupTestCase() void RemoteProxyTests::authenticate() { + // Start the server startServer(); + // Connect to the server + RemoteProxyConnector *connector = new RemoteProxyConnector(this); + connector->setIgnoreSslErrors(QList() << QSslError::HostNameMismatch << QSslError::SelfSignedCertificate); + QSignalSpy spy(connector, &RemoteProxyConnector::error); + connector->connectServer(RemoteProxyConnector::ConnectionTypeWebSocket, QHostAddress::LocalHost, m_port); + //spy.wait(); + + connector->disconnectServer(); + connector->deleteLater(); Engine::instance()->stop(); } @@ -94,4 +106,22 @@ void RemoteProxyTests::startStopServer() QVERIFY(!Engine::exists()); } +void RemoteProxyTests::sslConfigurations() +{ + // Start the server + startServer(); + + // Connect to the server + RemoteProxyConnector *connector = new RemoteProxyConnector(this); + connector->setIgnoreSslErrors(QList() << QSslError::HostNameMismatch << QSslError::SelfSignedCertificate); + + QSignalSpy spy(connector, &RemoteProxyConnector::connected); + connector->connectServer(RemoteProxyConnector::ConnectionTypeWebSocket, QHostAddress::LocalHost, m_port); + spy.wait(); + + connector->disconnectServer(); + connector->deleteLater(); + Engine::instance()->stop(); +} + QTEST_MAIN(RemoteProxyTests) diff --git a/tests/nymea-remoteproxy-tests.h b/tests/nymea-remoteproxy-tests.h index 6bb982d..880d944 100644 --- a/tests/nymea-remoteproxy-tests.h +++ b/tests/nymea-remoteproxy-tests.h @@ -1,6 +1,7 @@ #ifndef NYMEA_REMOTEPROXY_TESTS_H #define NYMEA_REMOTEPROXY_TESTS_H +#include #include #include #include @@ -31,6 +32,7 @@ protected slots: private slots: void startStopServer(); void authenticate(); + void sslConfigurations(); }; diff --git a/tests/tests.pro b/tests/tests.pro index c6134be..4e5a104 100644 --- a/tests/tests.pro +++ b/tests/tests.pro @@ -5,8 +5,9 @@ QT += testlib TARGET = nymea-remoteproxy-tests -INCLUDEPATH += ../libnymea-remoteproxy -LIBS += -L$$top_builddir/libnymea-remoteproxy/ -lnymea-remoteproxy +INCLUDEPATH += ../libnymea-remoteproxy ../libnymea-remoteproxyclient +LIBS += -L$$top_builddir/libnymea-remoteproxy/ -lnymea-remoteproxy \ + -L$$top_builddir/libnymea-remoteproxyclient/ -lnymea-remoteproxyclient \ RESOURCES += certificate.qrc