From fcd0e7c105eaa0e1b3a64faceb2b74d9f94850f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Tue, 4 Jun 2019 17:25:39 +0200 Subject: [PATCH 01/16] Add logging file configuration and close sockets on error --- debian/nymea-remoteproxy.install.in | 1 + debian/nymea-remoteproxy.service | 1 + libnymea-remoteproxy/proxyserver.cpp | 2 ++ libnymea-remoteproxy/websocketserver.cpp | 9 ++++++++- nymea-remoteproxy-logging.conf | 15 +++++++++++++++ 5 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 nymea-remoteproxy-logging.conf diff --git a/debian/nymea-remoteproxy.install.in b/debian/nymea-remoteproxy.install.in index 888197e..3522567 100644 --- a/debian/nymea-remoteproxy.install.in +++ b/debian/nymea-remoteproxy.install.in @@ -1,2 +1,3 @@ usr/bin/nymea-remoteproxy nymea-remoteproxy.conf etc/nymea/ +nymea-remoteproxy-logging.conf etc/nymea/ diff --git a/debian/nymea-remoteproxy.service b/debian/nymea-remoteproxy.service index af023ee..ccb8118 100644 --- a/debian/nymea-remoteproxy.service +++ b/debian/nymea-remoteproxy.service @@ -6,6 +6,7 @@ Wants=network-online.target [Service] Type=simple +Environment=QT_LOGGING_CONF=/etc/nymea/nymea-remoteproxy-logging.conf ExecStart=/usr/bin/nymea-remoteproxy -c /etc/nymea/nymea-remoteproxy.conf StandardOutput=journal StandardError=journal diff --git a/libnymea-remoteproxy/proxyserver.cpp b/libnymea-remoteproxy/proxyserver.cpp index 089f9cc..8c451d5 100644 --- a/libnymea-remoteproxy/proxyserver.cpp +++ b/libnymea-remoteproxy/proxyserver.cpp @@ -254,6 +254,8 @@ void ProxyServer::onClientDisconnected(const QUuid &clientId) // Delete the proxy client proxyClient->deleteLater(); + } else { + qCWarning(dcProxyServer()) << "Unknown client disconnected from proxy server." << clientId.toString(); } } diff --git a/libnymea-remoteproxy/websocketserver.cpp b/libnymea-remoteproxy/websocketserver.cpp index 7eb214a..7e49836 100644 --- a/libnymea-remoteproxy/websocketserver.cpp +++ b/libnymea-remoteproxy/websocketserver.cpp @@ -93,6 +93,10 @@ void WebSocketServer::onClientConnected() { // Got a new client connected QWebSocket *client = m_server->nextPendingConnection(); + if (!client) { + qCWarning(dcWebSocketServer()) << "Next pending connection dissapeared. Doing nothing."; + return; + } // Check websocket version if (client->version() != QWebSocketProtocol::Version13) { @@ -149,7 +153,10 @@ void WebSocketServer::onBinaryMessageReceived(const QByteArray &data) void WebSocketServer::onClientError(QAbstractSocket::SocketError error) { QWebSocket *client = static_cast(sender()); - qCWarning(dcWebSocketServer()) << "Client error occurred:" << error << client->errorString(); + qCWarning(dcWebSocketServer()) << "Client error occurred:" << client << client->peerAddress().toString() << error << client->errorString() << "Closing the socket."; + + // Note: on any error which can occure, make sure the socket will be closed in any case + client->close(); } void WebSocketServer::onAcceptError(QAbstractSocket::SocketError error) diff --git a/nymea-remoteproxy-logging.conf b/nymea-remoteproxy-logging.conf new file mode 100644 index 0000000..71477ef --- /dev/null +++ b/nymea-remoteproxy-logging.conf @@ -0,0 +1,15 @@ +[Rules] +*.debug=false +Application.debug=true +Engine.debug=true +JsonRpc.debug=true +JsonRpcTraffic.debug=false +WebSocketServer.debug=true +WebSocketServerTraffic.debug=false +Authentication.debug=true +AuthenticationProcess.debug=false +ProxyServer.debug=true +ProxyServerTraffic.debug=false +MonitorServer.debug=true +AwsCredentialsProvider.debug=false +AwsCredentialsProviderTraffic.debug=false From 4262e6eb9f737aa02ee0e61b9a7472ca027edbcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Tue, 4 Jun 2019 17:40:09 +0200 Subject: [PATCH 02/16] Replace internal logging filter with service logging configuration file --- server/main.cpp | 38 -------------------------------------- 1 file changed, 38 deletions(-) diff --git a/server/main.cpp b/server/main.cpp index a7057e8..9d9d80d 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -62,19 +62,6 @@ 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 { - // Enable default debug output - category->setEnabled(QtDebugMsg, true); - category->setEnabled(QtWarningMsg, s_loggingFilters.value("Warnings")); - } -} - static void consoleLogHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { QString messageString; @@ -119,22 +106,6 @@ int main(int argc, char *argv[]) application.setOrganizationName("nymea"); application.setApplicationVersion(SERVER_VERSION_STRING); - s_loggingFilters.insert("Application", true); - s_loggingFilters.insert("Engine", true); - s_loggingFilters.insert("JsonRpc", true); - s_loggingFilters.insert("WebSocketServer", true); - s_loggingFilters.insert("Authentication", true); - s_loggingFilters.insert("ProxyServer", true); - s_loggingFilters.insert("MonitorServer", true); - s_loggingFilters.insert("AwsCredentialsProvider", true); - - // Only with verbose enabled - s_loggingFilters.insert("JsonRpcTraffic", false); - s_loggingFilters.insert("ProxyServerTraffic", false); - s_loggingFilters.insert("AuthenticationProcess", false); - s_loggingFilters.insert("WebSocketServerTraffic", false); - s_loggingFilters.insert("AwsCredentialsProviderTraffic", false); - QString configFile = "/etc/nymea/nymea-remoteproxy.conf"; // command line parser @@ -186,15 +157,6 @@ int main(int argc, char *argv[]) configuration->setLogFileName(parser.value(logfileOption)); } - if (parser.isSet(verboseOption)) { - s_loggingFilters["JsonRpcTraffic"] = true; - s_loggingFilters["ProxyServerTraffic"] = true; - s_loggingFilters["AuthenticationProcess"] = true; - s_loggingFilters["WebSocketServerTraffic"] = true; - s_loggingFilters["AwsCredentialsProviderTraffic"] = true; - } - QLoggingCategory::installFilter(loggingCategoryFilter); - // Open logfile if configured if (configuration->writeLogFile()) { s_loggingEnabled = true; From 5175052ab339fa8dd73f7657d4c73e580a49382d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Tue, 4 Jun 2019 18:11:22 +0200 Subject: [PATCH 03/16] Add tests to debian packaging script --- debian/rules | 3 +++ nymea-remoteproxy.pro | 3 +++ 2 files changed, 6 insertions(+) diff --git a/debian/rules b/debian/rules index 217cc12..9d63288 100755 --- a/debian/rules +++ b/debian/rules @@ -15,6 +15,9 @@ override_dh_strip: dh_strip --dbg-package=libnymea-remoteproxy-dbg dh_strip --dbg-package=libnymea-remoteproxyclient-dbg +override_dh_auto_test: + make test + override_dh_auto_clean: dh_auto_clean find . -name *.qm -exec rm {} \; diff --git a/nymea-remoteproxy.pro b/nymea-remoteproxy.pro index b42f003..3d44545 100644 --- a/nymea-remoteproxy.pro +++ b/nymea-remoteproxy.pro @@ -15,6 +15,9 @@ server.depends = libnymea-remoteproxy client.depends = libnymea-remoteproxyclient tests.depends = libnymea-remoteproxy libnymea-remoteproxyclient +test.commands = LD_LIBRARY_PATH=$$top_builddir/libnymea-remoteproxy:$$top_builddir/libnymea-remoteproxyclient make check +QMAKE_EXTRA_TARGETS += test + message("----------------------------------------------------------") message("Building nymea-remoteproxy $${SERVER_VERSION}") message("----------------------------------------------------------") From 5ccb84d173a3f4ccdef430f97e5f70e063c09805 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Tue, 4 Jun 2019 18:41:49 +0200 Subject: [PATCH 04/16] Add tunnel debug category for cleaner log reading --- libnymea-remoteproxy/loggingcategories.cpp | 1 + libnymea-remoteproxy/loggingcategories.h | 1 + libnymea-remoteproxy/proxyserver.cpp | 5 ++++- nymea-remoteproxy-logging.conf | 1 + 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/libnymea-remoteproxy/loggingcategories.cpp b/libnymea-remoteproxy/loggingcategories.cpp index b4aaae6..1122fa6 100644 --- a/libnymea-remoteproxy/loggingcategories.cpp +++ b/libnymea-remoteproxy/loggingcategories.cpp @@ -30,6 +30,7 @@ Q_LOGGING_CATEGORY(dcApplication, "Application") Q_LOGGING_CATEGORY(dcEngine, "Engine") Q_LOGGING_CATEGORY(dcJsonRpc, "JsonRpc") +Q_LOGGING_CATEGORY(dcTunnel, "Tunnel") Q_LOGGING_CATEGORY(dcJsonRpcTraffic, "JsonRpcTraffic") Q_LOGGING_CATEGORY(dcWebSocketServer, "WebSocketServer") Q_LOGGING_CATEGORY(dcWebSocketServerTraffic, "WebSocketServerTraffic") diff --git a/libnymea-remoteproxy/loggingcategories.h b/libnymea-remoteproxy/loggingcategories.h index 0d79c02..329be4c 100644 --- a/libnymea-remoteproxy/loggingcategories.h +++ b/libnymea-remoteproxy/loggingcategories.h @@ -34,6 +34,7 @@ Q_DECLARE_LOGGING_CATEGORY(dcApplication) Q_DECLARE_LOGGING_CATEGORY(dcEngine) Q_DECLARE_LOGGING_CATEGORY(dcJsonRpc) +Q_DECLARE_LOGGING_CATEGORY(dcTunnel) Q_DECLARE_LOGGING_CATEGORY(dcJsonRpcTraffic) Q_DECLARE_LOGGING_CATEGORY(dcWebSocketServer) Q_DECLARE_LOGGING_CATEGORY(dcWebSocketServerTraffic) diff --git a/libnymea-remoteproxy/proxyserver.cpp b/libnymea-remoteproxy/proxyserver.cpp index 8c451d5..d4c61d1 100644 --- a/libnymea-remoteproxy/proxyserver.cpp +++ b/libnymea-remoteproxy/proxyserver.cpp @@ -185,6 +185,8 @@ void ProxyServer::establishTunnel(ProxyClient *firstClient, ProxyClient *secondC secondClient->setTunnelConnected(true); qCDebug(dcProxyServer()) << tunnel; + qCDebug(dcTunnel()) << "Tunnel established between" << firstClient->peerAddress().toString() << firstClient->uuid() + << "<-->" << secondClient->peerAddress().toString() << secondClient->uuid(); m_totalTunnelCount += 1; saveStatistics(); @@ -242,12 +244,13 @@ void ProxyServer::onClientDisconnected(const QUuid &clientId) // Check if if (m_tunnels.contains(proxyClient->tunnelIdentifier())) { - // There is a tunnel connection for this client, remove the tunnel and disconnect also the other client ProxyClient *remoteClient = getRemoteClient(proxyClient); TunnelConnection tunnelConnection = m_tunnels.take(proxyClient->tunnelIdentifier()); Engine::instance()->logEngine()->logTunnel(tunnelConnection); if (remoteClient) { + qCDebug(dcTunnel()) << "Remove tunnel between" << proxyClient->peerAddress().toString() << proxyClient->uuid() + << "<-->" << remoteClient->peerAddress().toString() << remoteClient->uuid(); remoteClient->killConnection("Tunnel client disconnected"); } } diff --git a/nymea-remoteproxy-logging.conf b/nymea-remoteproxy-logging.conf index 71477ef..9dbac85 100644 --- a/nymea-remoteproxy-logging.conf +++ b/nymea-remoteproxy-logging.conf @@ -2,6 +2,7 @@ *.debug=false Application.debug=true Engine.debug=true +Tunnel.debug=true JsonRpc.debug=true JsonRpcTraffic.debug=false WebSocketServer.debug=true From 8ff19135d6a1ddf757a28ed819b0a31be83097b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Tue, 4 Jun 2019 19:02:51 +0200 Subject: [PATCH 05/16] Disable tests for default build --- debian/rules | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/debian/rules b/debian/rules index 9d63288..db4027c 100755 --- a/debian/rules +++ b/debian/rules @@ -15,8 +15,8 @@ override_dh_strip: dh_strip --dbg-package=libnymea-remoteproxy-dbg dh_strip --dbg-package=libnymea-remoteproxyclient-dbg -override_dh_auto_test: - make test +#override_dh_auto_test: +# make test override_dh_auto_clean: dh_auto_clean From bdda8da80325152e82dc142866b073b9ab4fbc7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Tue, 4 Jun 2019 19:50:28 +0200 Subject: [PATCH 06/16] Fix tunnel debug print and use client id instead of uuid --- libnymea-remoteproxy/proxyserver.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libnymea-remoteproxy/proxyserver.cpp b/libnymea-remoteproxy/proxyserver.cpp index d4c61d1..c7f20c1 100644 --- a/libnymea-remoteproxy/proxyserver.cpp +++ b/libnymea-remoteproxy/proxyserver.cpp @@ -185,8 +185,8 @@ void ProxyServer::establishTunnel(ProxyClient *firstClient, ProxyClient *secondC secondClient->setTunnelConnected(true); qCDebug(dcProxyServer()) << tunnel; - qCDebug(dcTunnel()) << "Tunnel established between" << firstClient->peerAddress().toString() << firstClient->uuid() - << "<-->" << secondClient->peerAddress().toString() << secondClient->uuid(); + qCDebug(dcTunnel()) << "Tunnel established between" << firstClient->peerAddress().toString() << firstClient->clientId() + << "<-->" << secondClient->peerAddress().toString() << secondClient->clientId(); m_totalTunnelCount += 1; saveStatistics(); @@ -249,8 +249,8 @@ void ProxyServer::onClientDisconnected(const QUuid &clientId) TunnelConnection tunnelConnection = m_tunnels.take(proxyClient->tunnelIdentifier()); Engine::instance()->logEngine()->logTunnel(tunnelConnection); if (remoteClient) { - qCDebug(dcTunnel()) << "Remove tunnel between" << proxyClient->peerAddress().toString() << proxyClient->uuid() - << "<-->" << remoteClient->peerAddress().toString() << remoteClient->uuid(); + qCDebug(dcTunnel()) << "Remove tunnel between" << proxyClient->peerAddress().toString() << proxyClient->clientId() + << "<-->" << remoteClient->peerAddress().toString() << remoteClient->clientId(); remoteClient->killConnection("Tunnel client disconnected"); } } From df5a240189177fbc05478cb8a7765068927c3d33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Tue, 4 Jun 2019 22:26:52 +0200 Subject: [PATCH 07/16] Add flush and abort to each socket close call --- libnymea-remoteproxy/websocketserver.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/libnymea-remoteproxy/websocketserver.cpp b/libnymea-remoteproxy/websocketserver.cpp index 7e49836..9e60d94 100644 --- a/libnymea-remoteproxy/websocketserver.cpp +++ b/libnymea-remoteproxy/websocketserver.cpp @@ -87,6 +87,8 @@ void WebSocketServer::killClientConnection(const QUuid &clientId, const QString qCWarning(dcWebSocketServer()) << "Killing client connection" << clientId.toString() << "Reason:" << killReason; client->close(QWebSocketProtocol::CloseCodeBadOperation, killReason); + client->flush(); + client->abort(); } void WebSocketServer::onClientConnected() @@ -102,6 +104,8 @@ void WebSocketServer::onClientConnected() if (client->version() != QWebSocketProtocol::Version13) { qCWarning(dcWebSocketServer()) << "Client with invalid protocol version" << client->version() << ". Rejecting."; client->close(QWebSocketProtocol::CloseCodeProtocolError, QString("invalid protocol version: %1 != Supported Version 13").arg(client->version())); + client->flush(); + client->abort(); delete client; return; } @@ -130,6 +134,8 @@ void WebSocketServer::onClientDisconnected() // Manually close it in any case client->close(); + client->flush(); + client->abort(); m_clientList.take(clientId)->deleteLater(); emit clientDisconnected(clientId); @@ -148,6 +154,8 @@ void WebSocketServer::onBinaryMessageReceived(const QByteArray &data) qCWarning(dcWebSocketServerTraffic()) << "<-- Binary message from" << client->peerAddress().toString() << ":" << data; // Note: this is not expected, so close this client connection. client->close(QWebSocketProtocol::CloseCodeBadOperation, "Binary message not expected."); + client->flush(); + client->abort(); } void WebSocketServer::onClientError(QAbstractSocket::SocketError error) @@ -157,6 +165,8 @@ void WebSocketServer::onClientError(QAbstractSocket::SocketError error) // Note: on any error which can occure, make sure the socket will be closed in any case client->close(); + client->flush(); + client->abort(); } void WebSocketServer::onAcceptError(QAbstractSocket::SocketError error) @@ -195,6 +205,8 @@ bool WebSocketServer::stopServer() // Clean up client connections foreach (QWebSocket *client, m_clientList.values()) { client->close(QWebSocketProtocol::CloseCodeNormal, "Stop server"); + client->flush(); + client->abort(); } // Delete the server object From 9fd2a3440f17d8fca27bed4b92e7682918bfe459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 6 Jun 2019 15:39:00 +0200 Subject: [PATCH 08/16] Add insecure websocket support and start with tcp server support --- libnymea-remoteproxy/engine.cpp | 4 +- libnymea-remoteproxy/proxyconfiguration.cpp | 12 +++ libnymea-remoteproxy/proxyconfiguration.h | 4 + libnymea-remoteproxy/websocketserver.cpp | 11 ++- libnymea-remoteproxy/websocketserver.h | 3 +- .../libnymea-remoteproxyclient.pri | 2 + .../proxyconnection.cpp | 10 ++ libnymea-remoteproxyclient/proxyconnection.h | 5 +- .../remoteproxyconnection.cpp | 9 ++ .../remoteproxyconnection.h | 3 +- .../tcpsocketconnection.cpp | 95 +++++++++++++++++++ .../tcpsocketconnection.h | 46 +++++++++ .../websocketconnection.cpp | 11 +-- .../websocketconnection.h | 3 - nymea-remoteproxy.conf | 1 + server/main.cpp | 7 +- 16 files changed, 204 insertions(+), 22 deletions(-) create mode 100644 libnymea-remoteproxyclient/tcpsocketconnection.cpp create mode 100644 libnymea-remoteproxyclient/tcpsocketconnection.h diff --git a/libnymea-remoteproxy/engine.cpp b/libnymea-remoteproxy/engine.cpp index 887cd83..0baf9bd 100644 --- a/libnymea-remoteproxy/engine.cpp +++ b/libnymea-remoteproxy/engine.cpp @@ -72,10 +72,10 @@ void Engine::start(ProxyConfiguration *configuration) Q_ASSERT_X(m_authenticator != nullptr, "Engine", "There is no authenticator registerd."); m_proxyServer = new ProxyServer(this); - m_webSocketServer = new WebSocketServer(m_configuration->sslConfiguration(), this); + m_webSocketServer = new WebSocketServer(m_configuration->sslEnabled(), m_configuration->sslConfiguration(), this); QUrl websocketServerUrl; - websocketServerUrl.setScheme("wss"); + websocketServerUrl.setScheme(m_configuration->sslEnabled() ? "wss" : "ws"); websocketServerUrl.setHost(m_configuration->webSocketServerHost().toString()); websocketServerUrl.setPort(m_configuration->webSocketServerPort()); diff --git a/libnymea-remoteproxy/proxyconfiguration.cpp b/libnymea-remoteproxy/proxyconfiguration.cpp index 7d2f061..5d88dc0 100644 --- a/libnymea-remoteproxy/proxyconfiguration.cpp +++ b/libnymea-remoteproxy/proxyconfiguration.cpp @@ -71,6 +71,7 @@ bool ProxyConfiguration::loadConfiguration(const QString &fileName) settings.endGroup(); settings.beginGroup("SSL"); + setSslEnabled(settings.value("enabled", true).toBool()); setSslCertificateFileName(settings.value("certificate", "/etc/ssl/certs/ssl-cert-snakeoil.pem").toString()); setSslCertificateKeyFileName(settings.value("certificateKey", "/etc/ssl/private/ssl-cert-snakeoil.key").toString()); setSslCertificateChainFileName(settings.value("certificateChain", "").toString()); @@ -256,6 +257,16 @@ void ProxyConfiguration::setAwsCredentialsUrl(const QUrl &url) m_awsCredentialsUrl = url; } +bool ProxyConfiguration::sslEnabled() const +{ + return m_sslEnabled; +} + +void ProxyConfiguration::setSslEnabled(bool enabled) +{ + m_sslEnabled = enabled; +} + QString ProxyConfiguration::sslCertificateFileName() const { return m_sslCertificateFileName; @@ -349,6 +360,7 @@ QDebug operator<<(QDebug debug, ProxyConfiguration *configuration) debug.nospace() << " - Authorizer lambda function:" << configuration->awsAuthorizerLambdaFunctionName() << endl; debug.nospace() << " - Credentials URL:" << configuration->awsCredentialsUrl().toString() << endl; debug.nospace() << "SSL configuration" << endl; + debug.nospace() << " - Enabled:" << configuration->sslEnabled() << endl; debug.nospace() << " - Certificate:" << configuration->sslCertificateFileName() << endl; debug.nospace() << " - Certificate key:" << configuration->sslCertificateKeyFileName() << endl; debug.nospace() << " - Certificate chain:" << configuration->sslCertificateChainFileName() << endl; diff --git a/libnymea-remoteproxy/proxyconfiguration.h b/libnymea-remoteproxy/proxyconfiguration.h index 048c74a..2262761 100644 --- a/libnymea-remoteproxy/proxyconfiguration.h +++ b/libnymea-remoteproxy/proxyconfiguration.h @@ -85,6 +85,9 @@ public: void setAwsCredentialsUrl(const QUrl &url); // Ssl + bool sslEnabled() const; + void setSslEnabled(bool enabled); + QString sslCertificateFileName() const; void setSslCertificateFileName(const QString &fileName); @@ -130,6 +133,7 @@ private: QUrl m_awsCredentialsUrl; // Ssl + bool m_sslEnabled = true; QString m_sslCertificateFileName = "/etc/ssl/certs/ssl-cert-snakeoil.pem"; QString m_sslCertificateKeyFileName = "/etc/ssl/private/ssl-cert-snakeoil.key"; QString m_sslCertificateChainFileName; diff --git a/libnymea-remoteproxy/websocketserver.cpp b/libnymea-remoteproxy/websocketserver.cpp index 9e60d94..9a5386f 100644 --- a/libnymea-remoteproxy/websocketserver.cpp +++ b/libnymea-remoteproxy/websocketserver.cpp @@ -32,8 +32,9 @@ namespace remoteproxy { -WebSocketServer::WebSocketServer(const QSslConfiguration &sslConfiguration, QObject *parent) : +WebSocketServer::WebSocketServer(bool sslEnabled, const QSslConfiguration &sslConfiguration, QObject *parent) : TransportInterface(parent), + m_sslEnabled(sslEnabled), m_sslConfiguration(sslConfiguration) { m_serverName = "Websocket server"; @@ -181,8 +182,12 @@ void WebSocketServer::onServerError(QWebSocketProtocol::CloseCode closeCode) bool WebSocketServer::startServer() { - m_server = new QWebSocketServer(QCoreApplication::applicationName(), QWebSocketServer::SecureMode, this); - m_server->setSslConfiguration(sslConfiguration()); + if (m_sslEnabled) { + m_server = new QWebSocketServer(QCoreApplication::applicationName(), QWebSocketServer::SecureMode, this); + m_server->setSslConfiguration(sslConfiguration()); + } else { + m_server = new QWebSocketServer(QCoreApplication::applicationName(), QWebSocketServer::NonSecureMode, this); + } connect (m_server, &QWebSocketServer::newConnection, this, &WebSocketServer::onClientConnected); connect (m_server, &QWebSocketServer::acceptError, this, &WebSocketServer::onAcceptError); diff --git a/libnymea-remoteproxy/websocketserver.h b/libnymea-remoteproxy/websocketserver.h index f4641d2..27e4108 100644 --- a/libnymea-remoteproxy/websocketserver.h +++ b/libnymea-remoteproxy/websocketserver.h @@ -43,7 +43,7 @@ class WebSocketServer : public TransportInterface { Q_OBJECT public: - explicit WebSocketServer(const QSslConfiguration &sslConfiguration, QObject *parent = nullptr); + explicit WebSocketServer(bool sslEnabled, const QSslConfiguration &sslConfiguration, QObject *parent = nullptr); ~WebSocketServer() override; QUrl serverUrl() const; @@ -59,6 +59,7 @@ public: private: QUrl m_serverUrl; QWebSocketServer *m_server = nullptr; + bool m_sslEnabled; QSslConfiguration m_sslConfiguration; bool m_enabled = false; diff --git a/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pri b/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pri index 2a7edce..6ae9be7 100644 --- a/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pri +++ b/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pri @@ -1,6 +1,7 @@ INCLUDEPATH += $${PWD} HEADERS += \ + $$PWD/tcpsocketconnection.h \ $${PWD}/proxyjsonrpcclient.h \ $${PWD}/jsonreply.h \ $${PWD}/remoteproxyconnection.h \ @@ -8,6 +9,7 @@ HEADERS += \ $${PWD}/websocketconnection.h SOURCES += \ + $$PWD/tcpsocketconnection.cpp \ $${PWD}/proxyjsonrpcclient.cpp \ $${PWD}/jsonreply.cpp \ $${PWD}/remoteproxyconnection.cpp \ diff --git a/libnymea-remoteproxyclient/proxyconnection.cpp b/libnymea-remoteproxyclient/proxyconnection.cpp index 075e32f..2dcffd7 100644 --- a/libnymea-remoteproxyclient/proxyconnection.cpp +++ b/libnymea-remoteproxyclient/proxyconnection.cpp @@ -34,6 +34,11 @@ ProxyConnection::ProxyConnection(QObject *parent) : QObject(parent) } +QUrl ProxyConnection::serverUrl() const +{ + return m_serverUrl; +} + bool ProxyConnection::connected() { return m_connected; @@ -48,6 +53,11 @@ void ProxyConnection::setConnected(bool connected) emit connectedChanged(m_connected); } +void ProxyConnection::setServerUrl(const QUrl &serverUrl) +{ + m_serverUrl = serverUrl; +} + ProxyConnection::~ProxyConnection() { diff --git a/libnymea-remoteproxyclient/proxyconnection.h b/libnymea-remoteproxyclient/proxyconnection.h index e23dd83..178fab7 100644 --- a/libnymea-remoteproxyclient/proxyconnection.h +++ b/libnymea-remoteproxyclient/proxyconnection.h @@ -28,6 +28,7 @@ #ifndef SOCKETCONNECTOR_H #define SOCKETCONNECTOR_H +#include #include #include #include @@ -43,7 +44,7 @@ public: virtual void sendData(const QByteArray &data) = 0; - virtual QUrl serverUrl() const = 0; + QUrl serverUrl() const; virtual void ignoreSslErrors() = 0; virtual void ignoreSslErrors(const QList &errors) = 0; @@ -52,9 +53,11 @@ public: private: bool m_connected = false; + QUrl m_serverUrl; protected: void setConnected(bool connected); + void setServerUrl(const QUrl &serverUrl); signals: void connectedChanged(bool connected); diff --git a/libnymea-remoteproxyclient/remoteproxyconnection.cpp b/libnymea-remoteproxyclient/remoteproxyconnection.cpp index e636857..a56f6b6 100644 --- a/libnymea-remoteproxyclient/remoteproxyconnection.cpp +++ b/libnymea-remoteproxyclient/remoteproxyconnection.cpp @@ -43,6 +43,15 @@ RemoteProxyConnection::RemoteProxyConnection(const QUuid &clientUuid, const QStr } +RemoteProxyConnection::RemoteProxyConnection(const QUuid &clientUuid, const QString &clientName, RemoteProxyConnection::ConnectionType connectionType, QObject *parent) : + QObject(parent), + m_clientUuid(clientUuid), + m_clientName(clientName), + m_connectionType(connectionType) +{ + +} + RemoteProxyConnection::~RemoteProxyConnection() { diff --git a/libnymea-remoteproxyclient/remoteproxyconnection.h b/libnymea-remoteproxyclient/remoteproxyconnection.h index 28c92f6..372a114 100644 --- a/libnymea-remoteproxyclient/remoteproxyconnection.h +++ b/libnymea-remoteproxyclient/remoteproxyconnection.h @@ -69,6 +69,7 @@ public: Q_ENUM(State) explicit RemoteProxyConnection(const QUuid &clientUuid, const QString &clientName, QObject *parent = nullptr); + RemoteProxyConnection(const QUuid &clientUuid, const QString &clientName, ConnectionType connectionType, QObject *parent = nullptr); ~RemoteProxyConnection(); RemoteProxyConnection::State state() const; @@ -94,9 +95,9 @@ public: QString tunnelPartnerUuid() const; private: - ConnectionType m_connectionType = ConnectionTypeWebSocket; QUuid m_clientUuid; QString m_clientName; + ConnectionType m_connectionType = ConnectionTypeWebSocket; QUrl m_serverUrl; diff --git a/libnymea-remoteproxyclient/tcpsocketconnection.cpp b/libnymea-remoteproxyclient/tcpsocketconnection.cpp new file mode 100644 index 0000000..4e60266 --- /dev/null +++ b/libnymea-remoteproxyclient/tcpsocketconnection.cpp @@ -0,0 +1,95 @@ +#include "tcpsocketconnection.h" +Q_LOGGING_CATEGORY(dcRemoteProxyClientTcpSocket, "RemoteProxyClientTcpSocket") + +namespace remoteproxyclient { + +TcpSocketConnection::TcpSocketConnection(QObject *parent) : + ProxyConnection(parent) +{ + m_tcpSocket = new QSslSocket(this); + + connect(m_tcpSocket, &QSslSocket::disconnected, this, &TcpSocketConnection::onDisconnected); + connect(m_tcpSocket, &QSslSocket::encrypted, this, &TcpSocketConnection::onEncrypted); + connect(m_tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError))); + connect(m_tcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onStateChanged(QAbstractSocket::SocketState))); + connect(m_tcpSocket, SIGNAL(sslErrors(QList)), this, SIGNAL(sslErrors(QList))); +} + +TcpSocketConnection::~TcpSocketConnection() +{ + m_tcpSocket->close(); +} + +void TcpSocketConnection::sendData(const QByteArray &data) +{ + m_tcpSocket->write(data); +} + +void TcpSocketConnection::ignoreSslErrors() +{ + m_tcpSocket->ignoreSslErrors(); +} + +void TcpSocketConnection::ignoreSslErrors(const QList &errors) +{ + m_tcpSocket->ignoreSslErrors(errors); +} + +void TcpSocketConnection::onDisconnected() +{ + qCDebug(dcRemoteProxyClientTcpSocket()) << "Disconnected from" << serverUrl().toString(); + setConnected(false); +} + +void TcpSocketConnection::onEncrypted() +{ + qCDebug(dcRemoteProxyClientTcpSocket()) << "Connection encrypted"; +} + +void TcpSocketConnection::onError(QAbstractSocket::SocketError error) +{ + qCDebug(dcRemoteProxyClientTcpSocket()) << "Socket error occured" << error << m_tcpSocket->errorString(); + emit errorOccured(error); +} + +void TcpSocketConnection::onStateChanged(QAbstractSocket::SocketState state) +{ + qCDebug(dcRemoteProxyClientTcpSocket()) << "Socket state changed" << state; + + switch (state) { + case QAbstractSocket::ConnectedState: + qCDebug(dcRemoteProxyClientTcpSocket()) << "Connected with" << serverUrl().toString(); + setConnected(true); + break; + default: + setConnected(false); + break; + } + + emit stateChanged(state); +} + +void TcpSocketConnection::onReadyRead() +{ + emit dataReceived(m_tcpSocket->readAll()); +} + +void TcpSocketConnection::connectServer(const QUrl &serverUrl) +{ + qCDebug(dcRemoteProxyClientTcpSocket()) << "Connecting to" << this->serverUrl().toString(); + setServerUrl(serverUrl); + + if (serverUrl.scheme() == "tcp") { + m_tcpSocket->connectToHost(QHostAddress(this->serverUrl().host()), static_cast(this->serverUrl().port())); + } else { + m_tcpSocket->connectToHostEncrypted(this->serverUrl().host(), static_cast(this->serverUrl().port())); + } +} + +void TcpSocketConnection::disconnectServer() +{ + qCDebug(dcRemoteProxyClientTcpSocket()) << "Disconnecting from" << serverUrl().toString(); + m_tcpSocket->close(); +} + +} diff --git a/libnymea-remoteproxyclient/tcpsocketconnection.h b/libnymea-remoteproxyclient/tcpsocketconnection.h new file mode 100644 index 0000000..f9ea374 --- /dev/null +++ b/libnymea-remoteproxyclient/tcpsocketconnection.h @@ -0,0 +1,46 @@ +#ifndef TCPSOCKETCONNECTION_H +#define TCPSOCKETCONNECTION_H + +#include +#include +#include +#include + +#include "proxyconnection.h" + +Q_DECLARE_LOGGING_CATEGORY(dcRemoteProxyClienTcpSocket) + +namespace remoteproxyclient { + +class TcpSocketConnection : public ProxyConnection +{ + Q_OBJECT + +public: + explicit TcpSocketConnection(QObject *parent = nullptr); + ~TcpSocketConnection() override; + + void sendData(const QByteArray &data) override; + + void ignoreSslErrors() override; + void ignoreSslErrors(const QList &errors) override; + +private: + QSslSocket *m_tcpSocket = nullptr; + +private slots: + void onDisconnected(); + void onEncrypted(); + void onError(QAbstractSocket::SocketError error); + void onStateChanged(QAbstractSocket::SocketState state); + void onReadyRead(); + +public slots: + void connectServer(const QUrl &serverUrl) override; + void disconnectServer() override; + +}; + +} + +#endif // TCPSOCKETCONNECTION_H diff --git a/libnymea-remoteproxyclient/websocketconnection.cpp b/libnymea-remoteproxyclient/websocketconnection.cpp index 5177cad..2b4dace 100644 --- a/libnymea-remoteproxyclient/websocketconnection.cpp +++ b/libnymea-remoteproxyclient/websocketconnection.cpp @@ -49,11 +49,6 @@ WebSocketConnection::~WebSocketConnection() m_webSocket->close(); } -QUrl WebSocketConnection::serverUrl() const -{ - return m_serverUrl; -} - void WebSocketConnection::sendData(const QByteArray &data) { m_webSocket->sendTextMessage(QString::fromUtf8(data + '\n')); @@ -108,10 +103,10 @@ void WebSocketConnection::connectServer(const QUrl &serverUrl) if (connected()) { m_webSocket->close(); } + setServerUrl(serverUrl); - m_serverUrl = serverUrl; - qCDebug(dcRemoteProxyClientWebSocket()) << "Connecting to" << m_serverUrl.toString(); - m_webSocket->open(m_serverUrl); + qCDebug(dcRemoteProxyClientWebSocket()) << "Connecting to" << this->serverUrl().toString(); + m_webSocket->open(this->serverUrl()); } void WebSocketConnection::disconnectServer() diff --git a/libnymea-remoteproxyclient/websocketconnection.h b/libnymea-remoteproxyclient/websocketconnection.h index cf7e771..252d0d9 100644 --- a/libnymea-remoteproxyclient/websocketconnection.h +++ b/libnymea-remoteproxyclient/websocketconnection.h @@ -46,15 +46,12 @@ public: explicit WebSocketConnection(QObject *parent = nullptr); ~WebSocketConnection() override; - QUrl serverUrl() const override; - void sendData(const QByteArray &data) override; void ignoreSslErrors() override; void ignoreSslErrors(const QList &errors) override; private: - QUrl m_serverUrl; QWebSocket *m_webSocket = nullptr; private slots: diff --git a/nymea-remoteproxy.conf b/nymea-remoteproxy.conf index 9266c60..639f33c 100644 --- a/nymea-remoteproxy.conf +++ b/nymea-remoteproxy.conf @@ -15,6 +15,7 @@ authorizerLambdaFunction=system-services-authorizer-dev-checkToken awsCredentialsUrl=http://169.254.169.254/latest/meta-data/iam/security-credentials/EC2-Remote-Connection-Proxy-Role [SSL] +enabled=true certificate=/etc/ssl/certs/ssl-cert-snakeoil.pem certificateKey=/etc/ssl/private/ssl-cert-snakeoil.key certificateChain= diff --git a/server/main.cpp b/server/main.cpp index 9d9d80d..0a8a67a 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -186,9 +186,11 @@ int main(int argc, char *argv[]) } // Verify SSL configuration - if (configuration->sslConfiguration().isNull()) { - qCCritical(dcApplication()) << "No SSL configuration specified. The server does not suppoert insecure connections."; + if (configuration->sslEnabled() && configuration->sslConfiguration().isNull()) { + qCCritical(dcApplication()) << "SSL is enabled but no SSL configuration specified."; exit(-1); + } else { + qCDebug(dcApplication()) << "Using SSL version:" << QSslSocket::sslLibraryVersionString(); } qCDebug(dcApplication()) << "=========================================================="; @@ -203,7 +205,6 @@ int main(int argc, char *argv[]) if (s_loggingEnabled) qCDebug(dcApplication()) << "Logging enabled. Writing logs to" << s_logFile.fileName(); - qCDebug(dcApplication()) << "Using SSL version:" << QSslSocket::sslLibraryVersionString(); Authenticator *authenticator = nullptr; if (parser.isSet(mockAuthenticatorOption)) { From deaec8be40bf55cb77db4a172ab64e01d3fcca84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 6 Jun 2019 15:39:00 +0200 Subject: [PATCH 09/16] Add tcp server basic structure --- libnymea-remoteproxy/libnymea-remoteproxy.pro | 2 + libnymea-remoteproxy/tcpsocketserver.cpp | 74 ++++++++++++++ libnymea-remoteproxy/tcpsocketserver.h | 96 +++++++++++++++++++ libnymea-remoteproxy/websocketserver.h | 1 - .../tcpsocketconnection.cpp | 21 ++++ .../tcpsocketconnection.h | 21 ++++ nymea-remoteproxy.pri | 2 +- .../nymea-remoteproxy-tests-offline.h | 6 ++ 8 files changed, 221 insertions(+), 2 deletions(-) create mode 100644 libnymea-remoteproxy/tcpsocketserver.cpp create mode 100644 libnymea-remoteproxy/tcpsocketserver.h diff --git a/libnymea-remoteproxy/libnymea-remoteproxy.pro b/libnymea-remoteproxy/libnymea-remoteproxy.pro index 1edbfa2..373a8e8 100644 --- a/libnymea-remoteproxy/libnymea-remoteproxy.pro +++ b/libnymea-remoteproxy/libnymea-remoteproxy.pro @@ -6,6 +6,7 @@ TARGET = nymea-remoteproxy HEADERS += \ engine.h \ loggingcategories.h \ + tcpsocketserver.h \ transportinterface.h \ websocketserver.h \ proxyclient.h \ @@ -31,6 +32,7 @@ HEADERS += \ SOURCES += \ engine.cpp \ loggingcategories.cpp \ + tcpsocketserver.cpp \ transportinterface.cpp \ websocketserver.cpp \ proxyclient.cpp \ diff --git a/libnymea-remoteproxy/tcpsocketserver.cpp b/libnymea-remoteproxy/tcpsocketserver.cpp new file mode 100644 index 0000000..bf57d65 --- /dev/null +++ b/libnymea-remoteproxy/tcpsocketserver.cpp @@ -0,0 +1,74 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Simon Stürz * + * * + * This file is part of nymea-remoteproxy. * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "tcpsocketserver.h" + +namespace remoteproxy { + +TcpSocketServer::TcpSocketServer(bool sslEnabled, const QSslConfiguration &sslConfiguration, QObject *parent) : + TransportInterface(parent), + m_sslEnabled(sslEnabled), + m_sslConfiguration(sslConfiguration) +{ + +} + +quint16 TcpSocketServer::port() const +{ + return m_port; +} + +void TcpSocketServer::setPort(quint16 port) +{ + m_port = port; +} + +QHostAddress TcpSocketServer::hostAddress() const +{ + return m_hostAddress; +} + +void TcpSocketServer::setHostAddress(const QHostAddress &address) +{ + m_hostAddress = address; +} + +void TcpSocketServer::sendData(const QUuid &clientId, const QByteArray &data) +{ + +} + +void TcpSocketServer::killClientConnection(const QUuid &clientId, const QString &killReason) +{ + +} + +bool TcpSocketServer::startServer() +{ + +} + +bool TcpSocketServer::stopServer() +{ + +} + +} diff --git a/libnymea-remoteproxy/tcpsocketserver.h b/libnymea-remoteproxy/tcpsocketserver.h new file mode 100644 index 0000000..9d2736a --- /dev/null +++ b/libnymea-remoteproxy/tcpsocketserver.h @@ -0,0 +1,96 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Simon Stürz * + * * + * This file is part of nymea-remoteproxy. * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef TCPSOCKETSERVER_H +#define TCPSOCKETSERVER_H + +#include +#include +#include + +#include "transportinterface.h" + +namespace remoteproxy { + + +class SslServer: public QTcpServer +{ + Q_OBJECT +public: + SslServer(bool sslEnabled, const QSslConfiguration &config, QObject *parent = nullptr): + QTcpServer(parent), + m_sslEnabled(sslEnabled), + m_config(config) + { + + } + +signals: + void clientConnected(QSslSocket *socket); + void clientDisconnected(QSslSocket *socket); + void dataAvailable(QSslSocket *socket, const QByteArray &data); + +protected: + void incomingConnection(qintptr socketDescriptor) override; + +private slots: + void onClientDisconnected(); + void onSocketReadyRead(); + +private: + bool m_sslEnabled = false; + QSslConfiguration m_config; +}; + + +class TcpSocketServer : public TransportInterface +{ + Q_OBJECT +public: + explicit TcpSocketServer(bool sslEnabled, const QSslConfiguration &sslConfiguration, QObject *parent = nullptr); + ~TcpSocketServer() override; + + quint16 port() const; + void setPort(quint16 port); + + QHostAddress hostAddress() const; + void setHostAddress(const QHostAddress &address); + + void sendData(const QUuid &clientId, const QByteArray &data) override; + void killClientConnection(const QUuid &clientId, const QString &killReason) override; + +private: + quint16 m_port; + QHostAddress m_hostAddress; + bool m_sslEnabled; + QSslConfiguration m_sslConfiguration; + + QTcpServer *m_server = nullptr; + +public slots: + bool startServer() override; + bool stopServer() override; + +}; + +} + +#endif // TCPSOCKETSERVER_H diff --git a/libnymea-remoteproxy/websocketserver.h b/libnymea-remoteproxy/websocketserver.h index 27e4108..e51fd5b 100644 --- a/libnymea-remoteproxy/websocketserver.h +++ b/libnymea-remoteproxy/websocketserver.h @@ -61,7 +61,6 @@ private: QWebSocketServer *m_server = nullptr; bool m_sslEnabled; QSslConfiguration m_sslConfiguration; - bool m_enabled = false; QHash m_clientList; diff --git a/libnymea-remoteproxyclient/tcpsocketconnection.cpp b/libnymea-remoteproxyclient/tcpsocketconnection.cpp index 4e60266..13bbf13 100644 --- a/libnymea-remoteproxyclient/tcpsocketconnection.cpp +++ b/libnymea-remoteproxyclient/tcpsocketconnection.cpp @@ -1,3 +1,24 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Simon Stürz * + * * + * This file is part of nymea-remoteproxy. * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + #include "tcpsocketconnection.h" Q_LOGGING_CATEGORY(dcRemoteProxyClientTcpSocket, "RemoteProxyClientTcpSocket") diff --git a/libnymea-remoteproxyclient/tcpsocketconnection.h b/libnymea-remoteproxyclient/tcpsocketconnection.h index f9ea374..f4fc187 100644 --- a/libnymea-remoteproxyclient/tcpsocketconnection.h +++ b/libnymea-remoteproxyclient/tcpsocketconnection.h @@ -1,3 +1,24 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Simon Stürz * + * * + * This file is part of nymea-remoteproxy. * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + #ifndef TCPSOCKETCONNECTION_H #define TCPSOCKETCONNECTION_H diff --git a/nymea-remoteproxy.pri b/nymea-remoteproxy.pri index 0d98588..314a20b 100644 --- a/nymea-remoteproxy.pri +++ b/nymea-remoteproxy.pri @@ -5,7 +5,7 @@ QT -= gui SERVER_NAME=nymea-remoteproxy API_VERSION_MAJOR=0 API_VERSION_MINOR=3 -SERVER_VERSION=0.1.7 +SERVER_VERSION=0.1.8 DEFINES += SERVER_NAME_STRING=\\\"$${SERVER_NAME}\\\" \ SERVER_VERSION_STRING=\\\"$${SERVER_VERSION}\\\" \ diff --git a/tests/test-offline/nymea-remoteproxy-tests-offline.h b/tests/test-offline/nymea-remoteproxy-tests-offline.h index aad9c69..af62b3c 100644 --- a/tests/test-offline/nymea-remoteproxy-tests-offline.h +++ b/tests/test-offline/nymea-remoteproxy-tests-offline.h @@ -54,6 +54,9 @@ private slots: void websocketBinaryData(); void websocketPing(); + // TCP socket connection + + // Api void getIntrospect(); void getHello(); @@ -80,6 +83,9 @@ private slots: void authenticationReplyTimeout(); void authenticationReplyConnection(); + // TCP Websocket combinations + + }; #endif // NYMEA_REMOTEPROXY_TESTS_OFFLINE_H From 8fff4c668564ec224eb4a790cb8ce03e8872c071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Fri, 7 Jun 2019 16:31:59 +0200 Subject: [PATCH 10/16] Add tcp/ssl socket server and prepare tests --- libnymea-remoteproxy/engine.cpp | 21 ++- libnymea-remoteproxy/engine.h | 3 + libnymea-remoteproxy/loggingcategories.cpp | 2 + libnymea-remoteproxy/loggingcategories.h | 2 + libnymea-remoteproxy/tcpsocketserver.cpp | 140 +++++++++++++++--- libnymea-remoteproxy/tcpsocketserver.h | 34 ++--- libnymea-remoteproxy/transportinterface.cpp | 20 ++- libnymea-remoteproxy/transportinterface.h | 7 + libnymea-remoteproxy/websocketserver.cpp | 24 +-- libnymea-remoteproxy/websocketserver.h | 6 +- .../remoteproxyconnection.cpp | 4 +- .../remoteproxyconnection.h | 1 + .../nymea-remoteproxy-tests-offline.cpp | 14 +- tests/testbase/basetest.cpp | 6 +- tests/testbase/basetest.h | 4 +- 15 files changed, 210 insertions(+), 78 deletions(-) diff --git a/libnymea-remoteproxy/engine.cpp b/libnymea-remoteproxy/engine.cpp index 0baf9bd..82bee33 100644 --- a/libnymea-remoteproxy/engine.cpp +++ b/libnymea-remoteproxy/engine.cpp @@ -73,19 +73,31 @@ void Engine::start(ProxyConfiguration *configuration) m_proxyServer = new ProxyServer(this); m_webSocketServer = new WebSocketServer(m_configuration->sslEnabled(), m_configuration->sslConfiguration(), this); + m_tcpSocketServer = new TcpSocketServer(m_configuration->sslEnabled(), m_configuration->sslConfiguration(), this); + // Configure websocket server QUrl websocketServerUrl; websocketServerUrl.setScheme(m_configuration->sslEnabled() ? "wss" : "ws"); websocketServerUrl.setHost(m_configuration->webSocketServerHost().toString()); websocketServerUrl.setPort(m_configuration->webSocketServerPort()); - m_webSocketServer->setServerUrl(websocketServerUrl); - m_proxyServer->registerTransportInterface(m_webSocketServer); + // Configure tcp socket server + QUrl tcpSocketServerUrl; + tcpSocketServerUrl.setScheme(m_configuration->sslEnabled() ? "ssl" : "tcp"); + tcpSocketServerUrl.setHost(m_configuration->tcpServerHost().toString()); + tcpSocketServerUrl.setPort(m_configuration->tcpServerPort()); + m_tcpSocketServer->setServerUrl(tcpSocketServerUrl); + // Register the transport interfaces in the proxy server + m_proxyServer->registerTransportInterface(m_webSocketServer); + m_proxyServer->registerTransportInterface(m_tcpSocketServer); + + // Start the server qCDebug(dcEngine()) << "Starting proxy server"; m_proxyServer->startServer(); + // Start the monitor server m_monitorServer = new MonitorServer(configuration->monitorSocketFileName(), this); m_monitorServer->startServer(); @@ -153,6 +165,11 @@ ProxyServer *Engine::proxyServer() const return m_proxyServer; } +TcpSocketServer *Engine::tcpSocketServer() const +{ + return m_tcpSocketServer; +} + WebSocketServer *Engine::webSocketServer() const { return m_webSocketServer; diff --git a/libnymea-remoteproxy/engine.h b/libnymea-remoteproxy/engine.h index 247a268..849ae6f 100644 --- a/libnymea-remoteproxy/engine.h +++ b/libnymea-remoteproxy/engine.h @@ -38,6 +38,7 @@ #include "logengine.h" #include "proxyserver.h" #include "monitorserver.h" +#include "tcpsocketserver.h" #include "websocketserver.h" #include "proxyconfiguration.h" #include "authentication/authenticator.h" @@ -67,6 +68,7 @@ public: ProxyConfiguration *configuration() const; Authenticator *authenticator() const; ProxyServer *proxyServer() const; + TcpSocketServer *tcpSocketServer() const; WebSocketServer *webSocketServer() const; MonitorServer *monitorServer() const; LogEngine *logEngine() const; @@ -88,6 +90,7 @@ private: ProxyConfiguration *m_configuration = nullptr; Authenticator *m_authenticator = nullptr; ProxyServer *m_proxyServer = nullptr; + TcpSocketServer *m_tcpSocketServer = nullptr; WebSocketServer *m_webSocketServer = nullptr; MonitorServer *m_monitorServer = nullptr; LogEngine *m_logEngine = nullptr; diff --git a/libnymea-remoteproxy/loggingcategories.cpp b/libnymea-remoteproxy/loggingcategories.cpp index 1122fa6..5bf36b7 100644 --- a/libnymea-remoteproxy/loggingcategories.cpp +++ b/libnymea-remoteproxy/loggingcategories.cpp @@ -32,6 +32,8 @@ Q_LOGGING_CATEGORY(dcEngine, "Engine") Q_LOGGING_CATEGORY(dcJsonRpc, "JsonRpc") Q_LOGGING_CATEGORY(dcTunnel, "Tunnel") Q_LOGGING_CATEGORY(dcJsonRpcTraffic, "JsonRpcTraffic") +Q_LOGGING_CATEGORY(dcTcpSocketServer, "TcpSocketServer") +Q_LOGGING_CATEGORY(dcTcpSocketServerTraffic, "TcpSocketServerTraffic") Q_LOGGING_CATEGORY(dcWebSocketServer, "WebSocketServer") Q_LOGGING_CATEGORY(dcWebSocketServerTraffic, "WebSocketServerTraffic") Q_LOGGING_CATEGORY(dcAuthentication, "Authentication") diff --git a/libnymea-remoteproxy/loggingcategories.h b/libnymea-remoteproxy/loggingcategories.h index 329be4c..f9b4464 100644 --- a/libnymea-remoteproxy/loggingcategories.h +++ b/libnymea-remoteproxy/loggingcategories.h @@ -38,6 +38,8 @@ Q_DECLARE_LOGGING_CATEGORY(dcTunnel) Q_DECLARE_LOGGING_CATEGORY(dcJsonRpcTraffic) Q_DECLARE_LOGGING_CATEGORY(dcWebSocketServer) Q_DECLARE_LOGGING_CATEGORY(dcWebSocketServerTraffic) +Q_DECLARE_LOGGING_CATEGORY(dcTcpSocketServer) +Q_DECLARE_LOGGING_CATEGORY(dcTcpSocketServerTraffic) Q_DECLARE_LOGGING_CATEGORY(dcAuthentication) Q_DECLARE_LOGGING_CATEGORY(dcAuthenticationProcess) Q_DECLARE_LOGGING_CATEGORY(dcProxyServer) diff --git a/libnymea-remoteproxy/tcpsocketserver.cpp b/libnymea-remoteproxy/tcpsocketserver.cpp index bf57d65..40725f3 100644 --- a/libnymea-remoteproxy/tcpsocketserver.cpp +++ b/libnymea-remoteproxy/tcpsocketserver.cpp @@ -20,6 +20,7 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "tcpsocketserver.h" +#include "loggingcategories.h" namespace remoteproxy { @@ -28,47 +29,150 @@ TcpSocketServer::TcpSocketServer(bool sslEnabled, const QSslConfiguration &sslCo m_sslEnabled(sslEnabled), m_sslConfiguration(sslConfiguration) { - + m_serverName = "TCP"; } -quint16 TcpSocketServer::port() const +TcpSocketServer::~TcpSocketServer() { - return m_port; -} - -void TcpSocketServer::setPort(quint16 port) -{ - m_port = port; -} - -QHostAddress TcpSocketServer::hostAddress() const -{ - return m_hostAddress; -} - -void TcpSocketServer::setHostAddress(const QHostAddress &address) -{ - m_hostAddress = address; + stopServer(); } void TcpSocketServer::sendData(const QUuid &clientId, const QByteArray &data) { + QTcpSocket *client = nullptr; + client = m_clientList.value(clientId); + if (!client) { + qCWarning(dcTcpSocketServer()) << "Client" << clientId << "unknown to this transport"; + return; + } + client->write(data + '\n'); } void TcpSocketServer::killClientConnection(const QUuid &clientId, const QString &killReason) { + QTcpSocket *client = m_clientList.value(clientId); + if (!client) + return; + qCWarning(dcTcpSocketServer()) << "Killing client connection" << clientId.toString() << "Reason:" << killReason; + client->abort(); +} + +bool TcpSocketServer::running() const +{ + if (!m_server) + return false; + + return m_server->isListening(); +} + +void TcpSocketServer::onDataAvailable(QSslSocket *client, const QByteArray &data) +{ + qCDebug(dcTcpSocketServerTraffic()) << "Emitting data available internal."; + QUuid clientId = m_clientList.key(qobject_cast(client)); + emit dataAvailable(clientId, data); +} + +void TcpSocketServer::onClientConnected(QSslSocket *client) +{ + QUuid clientId = QUuid::createUuid(); + qCDebug(dcTcpSocketServer()) << "New client connected:" << client << client->peerAddress().toString() << clientId.toString(); + m_clientList.insert(clientId, client); + emit clientConnected(clientId, client->peerAddress()); +} + +void TcpSocketServer::onClientDisconnected(QSslSocket *client) +{ + QUuid clientId = m_clientList.key(client); + qCDebug(dcWebSocketServer()) << "Client disconnected:" << client << client->peerAddress().toString() << clientId.toString(); + m_clientList.take(clientId); + // Note: the SslServer is deleting the socket object + emit clientDisconnected(clientId); } bool TcpSocketServer::startServer() { + qCDebug(dcTcpSocketServer()) << "Starting TCP server" << m_serverUrl.toString(); + m_server = new SslServer(m_sslEnabled, m_sslConfiguration); + if(!m_server->listen(QHostAddress(m_serverUrl.host()), static_cast(m_serverUrl.port()))) { + qCWarning(dcTcpSocketServer()) << "Tcp server error: can not listen on" << m_serverUrl.toString(); + delete m_server; + m_server = nullptr; + return false; + } + connect(m_server, &SslServer::clientConnected, this, &TcpSocketServer::onClientConnected); + connect(m_server, SIGNAL(clientDisconnected(QSslSocket *)), SLOT(onClientDisconnected(QSslSocket *))); + connect(m_server, &SslServer::dataAvailable, this, &TcpSocketServer::onDataAvailable); + qCDebug(dcTcpSocketServer()) << "Server started successfully."; + return true; } bool TcpSocketServer::stopServer() +{ + // Clean up client connections + foreach (const QUuid &clientId, m_clientList.keys()) { + killClientConnection(clientId, "Stop server"); + } + + if (!m_server) + return true; + + qCDebug(dcTcpSocketServer()) << "Stop server" << m_serverUrl.toString(); + m_server->close(); + delete m_server; + m_server = nullptr; + return true; +} + +SslServer::SslServer(bool sslEnabled, const QSslConfiguration &config, QObject *parent) : + QTcpServer(parent), + m_sslEnabled(sslEnabled), + m_config(config) + { } +void SslServer::incomingConnection(qintptr socketDescriptor) +{ + QSslSocket *sslSocket = new QSslSocket(this); + + qCDebug(dcTcpSocketServer()) << "New client connected:" << sslSocket << sslSocket->peerAddress().toString(); + + connect(sslSocket, &QSslSocket::encrypted, [this, sslSocket](){ emit clientConnected(sslSocket); }); + connect(sslSocket, &QSslSocket::readyRead, this, &SslServer::onSocketReadyRead); + connect(sslSocket, &QSslSocket::disconnected, this, &SslServer::onClientDisconnected); + + if (!sslSocket->setSocketDescriptor(socketDescriptor)) { + qCWarning(dcTcpSocketServer()) << "Failed to set SSL socket descriptor."; + delete sslSocket; + return; + } + + if (m_sslEnabled) { + sslSocket->setSslConfiguration(m_config); + sslSocket->startServerEncryption(); + } else { + emit clientConnected(sslSocket); + } +} + +void SslServer::onClientDisconnected() +{ + QSslSocket *sslSocket = qobject_cast(sender()); + qCDebug(dcTcpSocketServer()) << "Client socket disconnected:" << sslSocket << sslSocket->peerAddress().toString();; + emit clientDisconnected(sslSocket); + sslSocket->deleteLater(); +} + +void SslServer::onSocketReadyRead() +{ + QSslSocket *sslSocket = qobject_cast(sender()); + QByteArray data = sslSocket->readAll(); + qCDebug(dcTcpSocketServerTraffic()) << "Data from socket" << sslSocket->peerAddress().toString() << data; + emit dataAvailable(sslSocket, data); +} + } diff --git a/libnymea-remoteproxy/tcpsocketserver.h b/libnymea-remoteproxy/tcpsocketserver.h index 9d2736a..5d6af91 100644 --- a/libnymea-remoteproxy/tcpsocketserver.h +++ b/libnymea-remoteproxy/tcpsocketserver.h @@ -22,6 +22,7 @@ #ifndef TCPSOCKETSERVER_H #define TCPSOCKETSERVER_H +#include #include #include #include @@ -35,13 +36,12 @@ class SslServer: public QTcpServer { Q_OBJECT public: - SslServer(bool sslEnabled, const QSslConfiguration &config, QObject *parent = nullptr): - QTcpServer(parent), - m_sslEnabled(sslEnabled), - m_config(config) - { + explicit SslServer(bool sslEnabled, const QSslConfiguration &config, QObject *parent = nullptr); + ~SslServer() override = default; - } +private: + bool m_sslEnabled = false; + QSslConfiguration m_config; signals: void clientConnected(QSslSocket *socket); @@ -55,9 +55,6 @@ private slots: void onClientDisconnected(); void onSocketReadyRead(); -private: - bool m_sslEnabled = false; - QSslConfiguration m_config; }; @@ -68,22 +65,23 @@ public: explicit TcpSocketServer(bool sslEnabled, const QSslConfiguration &sslConfiguration, QObject *parent = nullptr); ~TcpSocketServer() override; - quint16 port() const; - void setPort(quint16 port); - - QHostAddress hostAddress() const; - void setHostAddress(const QHostAddress &address); - void sendData(const QUuid &clientId, const QByteArray &data) override; void killClientConnection(const QUuid &clientId, const QString &killReason) override; + bool running() const override; + private: - quint16 m_port; - QHostAddress m_hostAddress; bool m_sslEnabled; QSslConfiguration m_sslConfiguration; - QTcpServer *m_server = nullptr; + QHash m_clientList; + + SslServer *m_server = nullptr; + +private slots: + void onDataAvailable(QSslSocket *client, const QByteArray &data); + void onClientConnected(QSslSocket *client); + void onClientDisconnected(QSslSocket *client); public slots: bool startServer() override; diff --git a/libnymea-remoteproxy/transportinterface.cpp b/libnymea-remoteproxy/transportinterface.cpp index 578c3aa..5e969b1 100644 --- a/libnymea-remoteproxy/transportinterface.cpp +++ b/libnymea-remoteproxy/transportinterface.cpp @@ -35,14 +35,24 @@ TransportInterface::TransportInterface(QObject *parent) : } -QString TransportInterface::serverName() const -{ - return m_serverName; -} - TransportInterface::~TransportInterface() { } +QString TransportInterface::serverName() const +{ + return m_serverName; +} + +QUrl TransportInterface::serverUrl() const +{ + return m_serverUrl; +} + +void TransportInterface::setServerUrl(const QUrl &serverUrl) +{ + m_serverUrl = serverUrl; +} + } diff --git a/libnymea-remoteproxy/transportinterface.h b/libnymea-remoteproxy/transportinterface.h index aa13f7c..b507fcd 100644 --- a/libnymea-remoteproxy/transportinterface.h +++ b/libnymea-remoteproxy/transportinterface.h @@ -28,6 +28,7 @@ #ifndef TRANSPORTINTERFACE_H #define TRANSPORTINTERFACE_H +#include #include #include @@ -45,12 +46,18 @@ public: virtual void sendData(const QUuid &clientId, const QByteArray &data) = 0; virtual void killClientConnection(const QUuid &clientId, const QString &killReason) = 0; + QUrl serverUrl() const; + void setServerUrl(const QUrl &serverUrl); + + virtual bool running() const = 0; + signals: void clientConnected(const QUuid &clientId, const QHostAddress &address); void clientDisconnected(const QUuid &clientId); void dataAvailable(const QUuid &clientId, const QByteArray &data); protected: + QUrl m_serverUrl; QString m_serverName; public slots: diff --git a/libnymea-remoteproxy/websocketserver.cpp b/libnymea-remoteproxy/websocketserver.cpp index 9a5386f..205fd17 100644 --- a/libnymea-remoteproxy/websocketserver.cpp +++ b/libnymea-remoteproxy/websocketserver.cpp @@ -37,7 +37,7 @@ WebSocketServer::WebSocketServer(bool sslEnabled, const QSslConfiguration &sslCo m_sslEnabled(sslEnabled), m_sslConfiguration(sslConfiguration) { - m_serverName = "Websocket server"; + m_serverName = "WebSocket"; } WebSocketServer::~WebSocketServer() @@ -45,16 +45,6 @@ WebSocketServer::~WebSocketServer() stopServer(); } -QUrl WebSocketServer::serverUrl() const -{ - return m_serverUrl; -} - -void WebSocketServer::setServerUrl(const QUrl &serverUrl) -{ - m_serverUrl = serverUrl; -} - bool WebSocketServer::running() const { if (!m_server) @@ -215,13 +205,13 @@ bool WebSocketServer::stopServer() } // Delete the server object - if (m_server) { - qCDebug(dcWebSocketServer()) << "Stop server" << m_server->serverName() << serverUrl().toString(); - m_server->close(); - delete m_server; - m_server = nullptr; - } + if (!m_server) + return true; + qCDebug(dcWebSocketServer()) << "Stop server" << m_server->serverName() << serverUrl().toString(); + m_server->close(); + delete m_server; + m_server = nullptr; return true; } diff --git a/libnymea-remoteproxy/websocketserver.h b/libnymea-remoteproxy/websocketserver.h index e51fd5b..c6d172d 100644 --- a/libnymea-remoteproxy/websocketserver.h +++ b/libnymea-remoteproxy/websocketserver.h @@ -46,10 +46,7 @@ public: explicit WebSocketServer(bool sslEnabled, const QSslConfiguration &sslConfiguration, QObject *parent = nullptr); ~WebSocketServer() override; - QUrl serverUrl() const; - void setServerUrl(const QUrl &serverUrl); - - bool running() const; + bool running() const override; QSslConfiguration sslConfiguration() const; @@ -57,7 +54,6 @@ public: void killClientConnection(const QUuid &clientId, const QString &killReason) override; private: - QUrl m_serverUrl; QWebSocketServer *m_server = nullptr; bool m_sslEnabled; QSslConfiguration m_sslConfiguration; diff --git a/libnymea-remoteproxyclient/remoteproxyconnection.cpp b/libnymea-remoteproxyclient/remoteproxyconnection.cpp index a56f6b6..73e0a10 100644 --- a/libnymea-remoteproxyclient/remoteproxyconnection.cpp +++ b/libnymea-remoteproxyclient/remoteproxyconnection.cpp @@ -27,6 +27,7 @@ #include "proxyconnection.h" #include "proxyjsonrpcclient.h" +#include "tcpsocketconnection.h" #include "websocketconnection.h" #include "remoteproxyconnection.h" @@ -335,8 +336,7 @@ bool RemoteProxyConnection::connectServer(const QUrl &url) m_connection = qobject_cast(new WebSocketConnection(this)); break; case ConnectionTypeTcpSocket: - // FIXME: - //m_connection = qobject_cast(new WebSocketConnection(this)); + m_connection = qobject_cast(new TcpSocketConnection(this)); break; } diff --git a/libnymea-remoteproxyclient/remoteproxyconnection.h b/libnymea-remoteproxyclient/remoteproxyconnection.h index 372a114..1eeeb6d 100644 --- a/libnymea-remoteproxyclient/remoteproxyconnection.h +++ b/libnymea-remoteproxyclient/remoteproxyconnection.h @@ -153,6 +153,7 @@ public slots: bool authenticate(const QString &token, const QString &nonce = QString()); void disconnectServer(); bool sendData(const QByteArray &data); + }; } diff --git a/tests/test-offline/nymea-remoteproxy-tests-offline.cpp b/tests/test-offline/nymea-remoteproxy-tests-offline.cpp index 77d166b..4a4ee39 100644 --- a/tests/test-offline/nymea-remoteproxy-tests-offline.cpp +++ b/tests/test-offline/nymea-remoteproxy-tests-offline.cpp @@ -73,7 +73,7 @@ void RemoteProxyOfflineTests::dummyAuthenticator() params.insert("name", "test"); params.insert("token", "foobar"); - QVariant response = invokeApiCall("Authentication.Authenticate", params); + QVariant response = invokeWebSocketApiCall("Authentication.Authenticate", params); qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); verifyAuthenticationError(response); @@ -291,7 +291,7 @@ void RemoteProxyOfflineTests::getIntrospect() // Start the server startServer(); - QVariant response = invokeApiCall("RemoteProxy.Introspect"); + QVariant response = invokeWebSocketApiCall("RemoteProxy.Introspect"); qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); // Clean up @@ -303,7 +303,7 @@ void RemoteProxyOfflineTests::getHello() // Start the server startServer(); - QVariantMap response = invokeApiCall("RemoteProxy.Hello").toMap(); + QVariantMap response = invokeWebSocketApiCall("RemoteProxy.Hello").toMap(); qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); // Verify data @@ -342,7 +342,7 @@ void RemoteProxyOfflineTests::apiBasicCalls() // Start the server startServer(); - QVariant response = injectSocketData(data); + QVariant response = injectWebSocketData(data); qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); QCOMPARE(response.toMap().value("id").toInt(), responseId); @@ -409,7 +409,7 @@ void RemoteProxyOfflineTests::authenticate() params.insert("token", token); if (!nonce.isEmpty()) params.insert("nonce", nonce); - QVariant response = invokeApiCall("Authentication.Authenticate", params); + QVariant response = invokeWebSocketApiCall("Authentication.Authenticate", params); qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); verifyAuthenticationError(response, expectedError); @@ -925,7 +925,7 @@ void RemoteProxyOfflineTests::jsonRpcTimeout() params.insert("name", "name"); params.insert("token", "token"); - QVariant response = invokeApiCall("Authentication.Authenticate", params); + QVariant response = invokeWebSocketApiCall("Authentication.Authenticate", params); qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); QVERIFY(response.toMap().value("status").toString() == "error"); @@ -976,7 +976,7 @@ void RemoteProxyOfflineTests::authenticationReplyTimeout() params.insert("name", "Sleepy test client"); params.insert("token", "sleepy token zzzZZZ"); - QVariant response = invokeApiCall("Authentication.Authenticate", params); + QVariant response = invokeWebSocketApiCall("Authentication.Authenticate", params); qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); verifyAuthenticationError(response, Authenticator::AuthenticationErrorTimeout); diff --git a/tests/testbase/basetest.cpp b/tests/testbase/basetest.cpp index 1a52857..6b398cd 100644 --- a/tests/testbase/basetest.cpp +++ b/tests/testbase/basetest.cpp @@ -89,6 +89,7 @@ void BaseTest::startServer() QVERIFY(Engine::instance()->running()); QVERIFY(Engine::instance()->developerMode()); QVERIFY(Engine::instance()->webSocketServer()->running()); + QVERIFY(Engine::instance()->tcpSocketServer()->running()); QVERIFY(Engine::instance()->monitorServer()->running()); } @@ -101,7 +102,7 @@ void BaseTest::stopServer() QVERIFY(!Engine::instance()->running()); } -QVariant BaseTest::invokeApiCall(const QString &method, const QVariantMap params, bool remainsConnected) +QVariant BaseTest::invokeWebSocketApiCall(const QString &method, const QVariantMap params, bool remainsConnected) { Q_UNUSED(remainsConnected) @@ -152,11 +153,12 @@ QVariant BaseTest::invokeApiCall(const QString &method, const QVariantMap params return jsonDoc.toVariant(); } } + m_commandCounter++; return QVariant(); } -QVariant BaseTest::injectSocketData(const QByteArray &data) +QVariant BaseTest::injectWebSocketData(const QByteArray &data) { QWebSocket *socket = new QWebSocket("proxy-testclient", QWebSocketProtocol::Version13); connect(socket, &QWebSocket::sslErrors, this, &BaseTest::sslErrors); diff --git a/tests/testbase/basetest.h b/tests/testbase/basetest.h index 09e223f..60c029c 100644 --- a/tests/testbase/basetest.h +++ b/tests/testbase/basetest.h @@ -78,8 +78,8 @@ protected: void startServer(); void stopServer(); - QVariant invokeApiCall(const QString &method, const QVariantMap params = QVariantMap(), bool remainsConnected = true); - QVariant injectSocketData(const QByteArray &data); + QVariant invokeWebSocketApiCall(const QString &method, const QVariantMap params = QVariantMap(), bool remainsConnected = true); + QVariant injectWebSocketData(const QByteArray &data); bool createRemoteConnection(const QString &token, const QString &nonce, QObject *parent); From 52ce0b5084cd79e417b83e954274ee66b78e7f35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 12 Jun 2019 16:40:16 +0200 Subject: [PATCH 11/16] Fix tests and improve proxy client handling in authentication and json layer --- create-coverage-html.sh | 2 +- .../authentication/authenticationreply.cpp | 15 +- .../authentication/authenticationreply.h | 7 +- .../authentication/authenticator.cpp | 10 +- .../jsonrpc/authenticationhandler.cpp | 10 +- libnymea-remoteproxy/jsonrpcserver.cpp | 12 +- libnymea-remoteproxy/proxyclient.cpp | 36 +- libnymea-remoteproxy/proxyclient.h | 11 +- libnymea-remoteproxy/proxyserver.cpp | 13 +- libnymea-remoteproxy/tcpsocketserver.cpp | 21 +- libnymea-remoteproxy/websocketserver.cpp | 12 +- .../libnymea-remoteproxyclient.pri | 4 +- .../remoteproxyconnection.cpp | 9 +- .../tcpsocketconnection.cpp | 9 +- .../tcpsocketconnection.h | 1 + nymea-remoteproxy.pri | 2 +- nymea-remoteproxy.pro | 4 +- tests/resources/test-configuration.conf | 6 +- .../nymea-remoteproxy-tests-offline.cpp | 329 ++++++++++++++++-- .../nymea-remoteproxy-tests-offline.h | 12 +- tests/testbase/basetest.cpp | 161 +++++++-- tests/testbase/basetest.h | 8 +- tests/testbase/mockauthenticator.cpp | 10 +- tests/testbase/mockauthenticator.h | 3 +- 24 files changed, 589 insertions(+), 118 deletions(-) diff --git a/create-coverage-html.sh b/create-coverage-html.sh index cbbc6d1..a6908b8 100755 --- a/create-coverage-html.sh +++ b/create-coverage-html.sh @@ -6,7 +6,7 @@ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(pwd)/libnymea-remoteproxy:$(pwd)/libny # Build qmake CONFIG+=coverage CONFIG+=ccache make -j$(nproc) -#make check +#make test make coverage-html # Clean build diff --git a/libnymea-remoteproxy/authentication/authenticationreply.cpp b/libnymea-remoteproxy/authentication/authenticationreply.cpp index c7fa483..79bf9f4 100644 --- a/libnymea-remoteproxy/authentication/authenticationreply.cpp +++ b/libnymea-remoteproxy/authentication/authenticationreply.cpp @@ -26,23 +26,30 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "engine.h" +#include "loggingcategories.h" #include "authenticationreply.h" #include "authentication/authenticator.h" namespace remoteproxy { AuthenticationReply::AuthenticationReply(ProxyClient *proxyClient, QObject *parent) : - QObject(parent), - m_proxyClient(proxyClient) + QObject(parent) { + m_proxyClient = proxyClient; + m_timer = new QTimer(this); m_timer->setSingleShot(true); connect(m_timer, &QTimer::timeout, this, &AuthenticationReply::onTimeout); - + qCDebug(dcAuthentication) << "Created authentication reply for" << proxyClient << "Timeout:" << Engine::instance()->configuration()->authenticationTimeout() << "[ms]"; m_timer->start(Engine::instance()->configuration()->authenticationTimeout()); } -ProxyClient *AuthenticationReply::proxyClient() const +AuthenticationReply::~AuthenticationReply() +{ + qCCritical(dcAuthentication()) << "Destroy authentication reply"; +} + +QPointer AuthenticationReply::proxyClient() const { return m_proxyClient; } diff --git a/libnymea-remoteproxy/authentication/authenticationreply.h b/libnymea-remoteproxy/authentication/authenticationreply.h index e57bf69..cf4d82f 100644 --- a/libnymea-remoteproxy/authentication/authenticationreply.h +++ b/libnymea-remoteproxy/authentication/authenticationreply.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -44,7 +45,7 @@ class AuthenticationReply : public QObject public: friend class Authenticator; - ProxyClient *proxyClient() const; + QPointer proxyClient() const; bool isTimedOut() const; bool isFinished() const; @@ -53,7 +54,9 @@ public: private: explicit AuthenticationReply(ProxyClient *proxyClient, QObject *parent = nullptr); - ProxyClient *m_proxyClient = nullptr; + ~AuthenticationReply(); + + QPointer m_proxyClient; QTimer *m_timer = nullptr; bool m_timedOut = false; diff --git a/libnymea-remoteproxy/authentication/authenticator.cpp b/libnymea-remoteproxy/authentication/authenticator.cpp index d6c1b7f..329d76e 100644 --- a/libnymea-remoteproxy/authentication/authenticator.cpp +++ b/libnymea-remoteproxy/authentication/authenticator.cpp @@ -37,6 +37,11 @@ Authenticator::Authenticator(QObject *parent) : } +Authenticator::~Authenticator() +{ + +} + void Authenticator::setReplyError(AuthenticationReply *reply, Authenticator::AuthenticationError error) { reply->setError(error); @@ -52,9 +57,4 @@ AuthenticationReply *Authenticator::createAuthenticationReply(ProxyClient *proxy return new AuthenticationReply(proxyClient, parent); } -Authenticator::~Authenticator() -{ - -} - } diff --git a/libnymea-remoteproxy/jsonrpc/authenticationhandler.cpp b/libnymea-remoteproxy/jsonrpc/authenticationhandler.cpp index 127058f..57ab1fb 100644 --- a/libnymea-remoteproxy/jsonrpc/authenticationhandler.cpp +++ b/libnymea-remoteproxy/jsonrpc/authenticationhandler.cpp @@ -89,7 +89,7 @@ void AuthenticationHandler::onAuthenticationFinished() AuthenticationReply *authenticationReply = static_cast(sender()); authenticationReply->deleteLater(); - qCDebug(dcJsonRpc()) << "Authentication response ready for" << authenticationReply->proxyClient() << authenticationReply->error(); + qCDebug(dcJsonRpc()) << "Authentication reply finished"; JsonReply *jsonReply = m_runningAuthentications.take(authenticationReply); if (authenticationReply->error() != Authenticator::AuthenticationErrorNoError) { @@ -100,10 +100,12 @@ void AuthenticationHandler::onAuthenticationFinished() jsonReply->setSuccess(true); } - // Set client authenticated - authenticationReply->proxyClient()->setAuthenticated(authenticationReply->error() == Authenticator::AuthenticationErrorNoError); + // Set client authenticated if still there + if (!authenticationReply->proxyClient().isNull()) { + authenticationReply->proxyClient()->setAuthenticated(authenticationReply->error() == Authenticator::AuthenticationErrorNoError); + jsonReply->setData(errorToReply(authenticationReply->error())); + } - jsonReply->setData(errorToReply(authenticationReply->error())); jsonReply->finished(); } diff --git a/libnymea-remoteproxy/jsonrpcserver.cpp b/libnymea-remoteproxy/jsonrpcserver.cpp index 8b64e0f..b74d3a8 100644 --- a/libnymea-remoteproxy/jsonrpcserver.cpp +++ b/libnymea-remoteproxy/jsonrpcserver.cpp @@ -188,7 +188,7 @@ void JsonRpcServer::asyncReplyFinished() qCDebug(dcJsonRpc()) << "Async reply finished" << reply->handler()->name() << reply->method() << reply->clientId().toString(); if (!proxyClient) { - qCWarning(dcJsonRpc()) << "Got an async reply but the client does not exist any more"; + qCWarning(dcJsonRpc()) << "Got an async reply but the client does not exist any more."; return; } @@ -210,6 +210,7 @@ void JsonRpcServer::asyncReplyFinished() } } else { + qCWarning(dcJsonRpc()) << "The reply timeouted."; sendErrorResponse(proxyClient, reply->commandId(), "Command timed out"); // Disconnect this client since he requested something that created a timeout proxyClient->killConnection("API call timeouted."); @@ -233,8 +234,14 @@ void JsonRpcServer::unregisterClient(ProxyClient *proxyClient) qCWarning(dcJsonRpc()) << "Client was not registered" << proxyClient; return; } - m_clients.removeAll(proxyClient); + + if (m_asyncReplies.values().contains(proxyClient)) { + qCWarning(dcJsonRpc()) << "Client was still waiting for a reply. Clean up reply"; + JsonReply *reply = m_asyncReplies.key(proxyClient); + m_asyncReplies.remove(reply); + // Note: the reply will be deleted in the finished slot + } } void JsonRpcServer::processData(ProxyClient *proxyClient, const QByteArray &data) @@ -246,7 +253,6 @@ void JsonRpcServer::processData(ProxyClient *proxyClient, const QByteArray &data QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); - if(error.error != QJsonParseError::NoError) { qCWarning(dcJsonRpc) << "Failed to parse JSON data" << data << ":" << error.errorString(); sendErrorResponse(proxyClient, -1, QString("Failed to parse JSON data: %1").arg(error.errorString())); diff --git a/libnymea-remoteproxy/proxyclient.cpp b/libnymea-remoteproxy/proxyclient.cpp index 7a95e90..10784f3 100644 --- a/libnymea-remoteproxy/proxyclient.cpp +++ b/libnymea-remoteproxy/proxyclient.cpp @@ -40,9 +40,10 @@ ProxyClient::ProxyClient(TransportInterface *interface, const QUuid &clientId, c { m_creationTimeStamp = QDateTime::currentDateTime().toTime_t(); - connect(&m_timer, &QTimer::timeout, this, &ProxyClient::timeoutOccured); - m_timer.setSingleShot(true); - m_timer.start(Engine::instance()->configuration()->inactiveTimeout()); + m_timer = new QTimer(this); + connect(m_timer, &QTimer::timeout, this, &ProxyClient::timeoutOccured); + m_timer->setSingleShot(true); + resetTimer(); } QUuid ProxyClient::clientId() const @@ -74,8 +75,8 @@ void ProxyClient::setAuthenticated(bool isAuthenticated) { m_authenticated = isAuthenticated; if (m_authenticated) { - m_timer.stop(); - m_timer.start(Engine::instance()->configuration()->aloneTimeout()); + m_timerWaitState = TimerWaitStateAlone; + resetTimer(); emit authenticated(); } } @@ -89,7 +90,7 @@ void ProxyClient::setTunnelConnected(bool isTunnelConnected) { m_tunnelConnected = isTunnelConnected; if (m_tunnelConnected) { - m_timer.stop(); + m_timer->stop(); emit tunnelConnected(); } } @@ -174,6 +175,25 @@ void ProxyClient::addTxDataCount(int dataCount) m_txDataCount += static_cast(dataCount); } +ProxyClient::TimerWaitState ProxyClient::timerWaitState() const +{ + return m_timerWaitState; +} + +void ProxyClient::resetTimer() +{ + switch (m_timerWaitState) { + case TimerWaitStateInactive: + m_timer->stop(); + m_timer->start(Engine::instance()->configuration()->inactiveTimeout()); + break; + case TimerWaitStateAlone: + m_timer->stop(); + m_timer->start(Engine::instance()->configuration()->aloneTimeout()); + break; + } +} + void ProxyClient::sendData(const QByteArray &data) { if (!m_interface) @@ -200,8 +220,8 @@ QDebug operator<<(QDebug debug, ProxyClient *proxyClient) debug.nospace() << ", " << proxyClient->clientId().toString(); debug.nospace() << ", " << proxyClient->userName(); debug.nospace() << ", " << proxyClient->peerAddress().toString(); - debug.nospace() << ", " << proxyClient->creationTimeString() << ") "; - return debug; + debug.nospace() << ", " << proxyClient->creationTimeString() << ")"; + return debug.space(); } } diff --git a/libnymea-remoteproxy/proxyclient.h b/libnymea-remoteproxy/proxyclient.h index 58b83ac..792714f 100644 --- a/libnymea-remoteproxy/proxyclient.h +++ b/libnymea-remoteproxy/proxyclient.h @@ -43,6 +43,12 @@ class ProxyClient : public QObject Q_OBJECT public: + enum TimerWaitState { + TimerWaitStateInactive, + TimerWaitStateAlone + }; + Q_ENUM(TimerWaitState) + explicit ProxyClient(TransportInterface *interface, const QUuid &clientId, const QHostAddress &address, QObject *parent = nullptr); QUuid clientId() const; @@ -84,12 +90,15 @@ public: void addTxDataCount(int dataCount); // Actions for this client + TimerWaitState timerWaitState() const; + void resetTimer(); void sendData(const QByteArray &data); void killConnection(const QString &reason); private: TransportInterface *m_interface = nullptr; - QTimer m_timer; + QTimer *m_timer = nullptr; + TimerWaitState m_timerWaitState = TimerWaitStateInactive; QUuid m_clientId; QHostAddress m_peerAddress; diff --git a/libnymea-remoteproxy/proxyserver.cpp b/libnymea-remoteproxy/proxyserver.cpp index c7f20c1..6048114 100644 --- a/libnymea-remoteproxy/proxyserver.cpp +++ b/libnymea-remoteproxy/proxyserver.cpp @@ -274,6 +274,8 @@ void ProxyServer::onClientDataAvailable(const QUuid &clientId, const QByteArray if (!proxyClient->isAuthenticated() && !proxyClient->isTunnelConnected()) { qCDebug(dcProxyServerTraffic()) << "Client data available" << proxyClient << qUtf8Printable(data); m_jsonRpcServer->processData(proxyClient, data); + // Reset the inactive timer + proxyClient->resetTimer(); return; } @@ -373,8 +375,15 @@ void ProxyServer::onProxyClientAuthenticated() void ProxyServer::onProxyClientTimeoutOccured() { ProxyClient *proxyClient = static_cast(sender()); - qCDebug(dcProxyServer()) << "Timeout occured for" << proxyClient; - proxyClient->killConnection("Proxy timeout occuret"); + qCDebug(dcProxyServer()) << "Timeout occured for" << proxyClient; + switch (proxyClient->timerWaitState()) { + case ProxyClient::TimerWaitStateInactive: + proxyClient->killConnection("Proxy timeout occuret. The socket was inactive."); + break; + case ProxyClient::TimerWaitStateAlone: + proxyClient->killConnection("Proxy timeout occuret. The tunnel partner did not show up."); + break; + } } void ProxyServer::startServer() diff --git a/libnymea-remoteproxy/tcpsocketserver.cpp b/libnymea-remoteproxy/tcpsocketserver.cpp index 40725f3..7fd358d 100644 --- a/libnymea-remoteproxy/tcpsocketserver.cpp +++ b/libnymea-remoteproxy/tcpsocketserver.cpp @@ -46,7 +46,10 @@ void TcpSocketServer::sendData(const QUuid &clientId, const QByteArray &data) return; } - client->write(data + '\n'); + qCDebug(dcTcpSocketServerTraffic()) << "Send data to" << clientId.toString() << data + '\n'; + if (client->write(data + '\n') < 0) { + qCWarning(dcTcpSocketServer()) << "Could not write data to client socket" << clientId.toString(); + } } void TcpSocketServer::killClientConnection(const QUuid &clientId, const QString &killReason) @@ -56,7 +59,7 @@ void TcpSocketServer::killClientConnection(const QUuid &clientId, const QString return; qCWarning(dcTcpSocketServer()) << "Killing client connection" << clientId.toString() << "Reason:" << killReason; - client->abort(); + client->close(); } bool TcpSocketServer::running() const @@ -69,7 +72,7 @@ bool TcpSocketServer::running() const void TcpSocketServer::onDataAvailable(QSslSocket *client, const QByteArray &data) { - qCDebug(dcTcpSocketServerTraffic()) << "Emitting data available internal."; + //qCDebug(dcTcpSocketServerTraffic()) << "Emitting data available internal."; QUuid clientId = m_clientList.key(qobject_cast(client)); emit dataAvailable(clientId, data); } @@ -85,7 +88,7 @@ void TcpSocketServer::onClientConnected(QSslSocket *client) void TcpSocketServer::onClientDisconnected(QSslSocket *client) { QUuid clientId = m_clientList.key(client); - qCDebug(dcWebSocketServer()) << "Client disconnected:" << client << client->peerAddress().toString() << clientId.toString(); + qCDebug(dcTcpSocketServer()) << "Client disconnected:" << client << client->peerAddress().toString() << clientId.toString(); m_clientList.take(clientId); // Note: the SslServer is deleting the socket object emit clientDisconnected(clientId); @@ -138,12 +141,13 @@ SslServer::SslServer(bool sslEnabled, const QSslConfiguration &config, QObject * void SslServer::incomingConnection(qintptr socketDescriptor) { QSslSocket *sslSocket = new QSslSocket(this); - - qCDebug(dcTcpSocketServer()) << "New client connected:" << sslSocket << sslSocket->peerAddress().toString(); - - connect(sslSocket, &QSslSocket::encrypted, [this, sslSocket](){ emit clientConnected(sslSocket); }); + qCDebug(dcTcpSocketServer()) << "Incomming connection" << sslSocket; connect(sslSocket, &QSslSocket::readyRead, this, &SslServer::onSocketReadyRead); connect(sslSocket, &QSslSocket::disconnected, this, &SslServer::onClientDisconnected); + connect(sslSocket, &QSslSocket::encrypted, [this, sslSocket](){ + qCDebug(dcTcpSocketServer()) << "SSL encryption established for" << sslSocket; + emit clientConnected(sslSocket); + }); if (!sslSocket->setSocketDescriptor(socketDescriptor)) { qCWarning(dcTcpSocketServer()) << "Failed to set SSL socket descriptor."; @@ -153,6 +157,7 @@ void SslServer::incomingConnection(qintptr socketDescriptor) if (m_sslEnabled) { sslSocket->setSslConfiguration(m_config); + qCDebug(dcTcpSocketServer()) << "Start SSL encryption for" << sslSocket; sslSocket->startServerEncryption(); } else { emit clientConnected(sslSocket); diff --git a/libnymea-remoteproxy/websocketserver.cpp b/libnymea-remoteproxy/websocketserver.cpp index 205fd17..d7ffa66 100644 --- a/libnymea-remoteproxy/websocketserver.cpp +++ b/libnymea-remoteproxy/websocketserver.cpp @@ -78,8 +78,6 @@ void WebSocketServer::killClientConnection(const QUuid &clientId, const QString qCWarning(dcWebSocketServer()) << "Killing client connection" << clientId.toString() << "Reason:" << killReason; client->close(QWebSocketProtocol::CloseCodeBadOperation, killReason); - client->flush(); - client->abort(); } void WebSocketServer::onClientConnected() @@ -95,9 +93,7 @@ void WebSocketServer::onClientConnected() if (client->version() != QWebSocketProtocol::Version13) { qCWarning(dcWebSocketServer()) << "Client with invalid protocol version" << client->version() << ". Rejecting."; client->close(QWebSocketProtocol::CloseCodeProtocolError, QString("invalid protocol version: %1 != Supported Version 13").arg(client->version())); - client->flush(); - client->abort(); - delete client; + client->deleteLater(); return; } @@ -125,8 +121,6 @@ void WebSocketServer::onClientDisconnected() // Manually close it in any case client->close(); - client->flush(); - client->abort(); m_clientList.take(clientId)->deleteLater(); emit clientDisconnected(clientId); @@ -145,8 +139,6 @@ void WebSocketServer::onBinaryMessageReceived(const QByteArray &data) qCWarning(dcWebSocketServerTraffic()) << "<-- Binary message from" << client->peerAddress().toString() << ":" << data; // Note: this is not expected, so close this client connection. client->close(QWebSocketProtocol::CloseCodeBadOperation, "Binary message not expected."); - client->flush(); - client->abort(); } void WebSocketServer::onClientError(QAbstractSocket::SocketError error) @@ -156,8 +148,6 @@ void WebSocketServer::onClientError(QAbstractSocket::SocketError error) // Note: on any error which can occure, make sure the socket will be closed in any case client->close(); - client->flush(); - client->abort(); } void WebSocketServer::onAcceptError(QAbstractSocket::SocketError error) diff --git a/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pri b/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pri index 6ae9be7..d1d89c8 100644 --- a/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pri +++ b/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pri @@ -1,7 +1,7 @@ INCLUDEPATH += $${PWD} HEADERS += \ - $$PWD/tcpsocketconnection.h \ + $${PWD}/tcpsocketconnection.h \ $${PWD}/proxyjsonrpcclient.h \ $${PWD}/jsonreply.h \ $${PWD}/remoteproxyconnection.h \ @@ -9,7 +9,7 @@ HEADERS += \ $${PWD}/websocketconnection.h SOURCES += \ - $$PWD/tcpsocketconnection.cpp \ + $${PWD}/tcpsocketconnection.cpp \ $${PWD}/proxyjsonrpcclient.cpp \ $${PWD}/jsonreply.cpp \ $${PWD}/remoteproxyconnection.cpp \ diff --git a/libnymea-remoteproxyclient/remoteproxyconnection.cpp b/libnymea-remoteproxyclient/remoteproxyconnection.cpp index 73e0a10..dde5028 100644 --- a/libnymea-remoteproxyclient/remoteproxyconnection.cpp +++ b/libnymea-remoteproxyclient/remoteproxyconnection.cpp @@ -319,23 +319,18 @@ void RemoteProxyConnection::onTunnelEstablished(const QString &clientName, const bool RemoteProxyConnection::connectServer(const QUrl &url) { - if (url.scheme() != "wss") { - // FIXME: support also tcp - qCWarning(dcRemoteProxyClientConnection()) << "Unsupported connection type" << url.scheme() << "Default to wss"; - m_serverUrl.setScheme("wss"); - } - m_serverUrl = url; - m_connectionType = ConnectionTypeWebSocket; m_error = QAbstractSocket::UnknownSocketError; cleanUp(); switch (m_connectionType) { case ConnectionTypeWebSocket: + qCDebug(dcRemoteProxyClientConnection()) << "Creating a web socket connection to" << url.toString(); m_connection = qobject_cast(new WebSocketConnection(this)); break; case ConnectionTypeTcpSocket: + qCDebug(dcRemoteProxyClientConnection()) << "Creating a TCP socket connection to" << url.toString(); m_connection = qobject_cast(new TcpSocketConnection(this)); break; } diff --git a/libnymea-remoteproxyclient/tcpsocketconnection.cpp b/libnymea-remoteproxyclient/tcpsocketconnection.cpp index 13bbf13..4ad69a3 100644 --- a/libnymea-remoteproxyclient/tcpsocketconnection.cpp +++ b/libnymea-remoteproxyclient/tcpsocketconnection.cpp @@ -31,6 +31,7 @@ TcpSocketConnection::TcpSocketConnection(QObject *parent) : connect(m_tcpSocket, &QSslSocket::disconnected, this, &TcpSocketConnection::onDisconnected); connect(m_tcpSocket, &QSslSocket::encrypted, this, &TcpSocketConnection::onEncrypted); + connect(m_tcpSocket, &QSslSocket::readyRead, this, &TcpSocketConnection::onReadyRead); connect(m_tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError))); connect(m_tcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onStateChanged(QAbstractSocket::SocketState))); connect(m_tcpSocket, SIGNAL(sslErrors(QList)), this, SIGNAL(sslErrors(QList))); @@ -65,6 +66,7 @@ void TcpSocketConnection::onDisconnected() void TcpSocketConnection::onEncrypted() { qCDebug(dcRemoteProxyClientTcpSocket()) << "Connection encrypted"; + setConnected(true); } void TcpSocketConnection::onError(QAbstractSocket::SocketError error) @@ -80,7 +82,9 @@ void TcpSocketConnection::onStateChanged(QAbstractSocket::SocketState state) switch (state) { case QAbstractSocket::ConnectedState: qCDebug(dcRemoteProxyClientTcpSocket()) << "Connected with" << serverUrl().toString(); - setConnected(true); + if (!m_ssl) { + setConnected(true); + } break; default: setConnected(false); @@ -97,12 +101,13 @@ void TcpSocketConnection::onReadyRead() void TcpSocketConnection::connectServer(const QUrl &serverUrl) { - qCDebug(dcRemoteProxyClientTcpSocket()) << "Connecting to" << this->serverUrl().toString(); setServerUrl(serverUrl); + qCDebug(dcRemoteProxyClientTcpSocket()) << "Connecting to" << this->serverUrl().toString(); if (serverUrl.scheme() == "tcp") { m_tcpSocket->connectToHost(QHostAddress(this->serverUrl().host()), static_cast(this->serverUrl().port())); } else { + m_ssl = true; m_tcpSocket->connectToHostEncrypted(this->serverUrl().host(), static_cast(this->serverUrl().port())); } } diff --git a/libnymea-remoteproxyclient/tcpsocketconnection.h b/libnymea-remoteproxyclient/tcpsocketconnection.h index f4fc187..934536e 100644 --- a/libnymea-remoteproxyclient/tcpsocketconnection.h +++ b/libnymea-remoteproxyclient/tcpsocketconnection.h @@ -48,6 +48,7 @@ public: private: QSslSocket *m_tcpSocket = nullptr; + bool m_ssl = false; private slots: void onDisconnected(); diff --git a/nymea-remoteproxy.pri b/nymea-remoteproxy.pri index 314a20b..38806df 100644 --- a/nymea-remoteproxy.pri +++ b/nymea-remoteproxy.pri @@ -23,7 +23,7 @@ ccache { QMAKE_CXX = ccache g++ } -coverage {< +coverage { # Note: this works only if you build in the source dir OBJECTS_DIR = MOC_DIR = diff --git a/nymea-remoteproxy.pro b/nymea-remoteproxy.pro index 3d44545..2e38692 100644 --- a/nymea-remoteproxy.pro +++ b/nymea-remoteproxy.pro @@ -15,7 +15,9 @@ server.depends = libnymea-remoteproxy client.depends = libnymea-remoteproxyclient tests.depends = libnymea-remoteproxy libnymea-remoteproxyclient -test.commands = LD_LIBRARY_PATH=$$top_builddir/libnymea-remoteproxy:$$top_builddir/libnymea-remoteproxyclient make check +test.commands = LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:$$top_builddir/libnymea-remoteproxy:$$top_builddir/libnymea-remoteproxyclient \ + LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:$$top_srcdir/libnymea-remoteproxy:$$top_srcdir/libnymea-remoteproxyclient \ + make check QMAKE_EXTRA_TARGETS += test message("----------------------------------------------------------") diff --git a/tests/resources/test-configuration.conf b/tests/resources/test-configuration.conf index e0fb21e..54bec2c 100644 --- a/tests/resources/test-configuration.conf +++ b/tests/resources/test-configuration.conf @@ -3,10 +3,10 @@ name=test-nymea-remoteproxy writeLogs=false logFile=/var/log/nymea-remoteproxy.log monitorSocket=/tmp/nymea-remoteproxy-test.sock -jsonRpcTimeout=1000 -authenticationTimeout=1500 +jsonRpcTimeout=2000 +authenticationTimeout=1000 inactiveTimeout=1500 -aloneTimeout=1000 +aloneTimeout=1500 [SSL] certificate=:/test-certificate.crt diff --git a/tests/test-offline/nymea-remoteproxy-tests-offline.cpp b/tests/test-offline/nymea-remoteproxy-tests-offline.cpp index 4a4ee39..b845662 100644 --- a/tests/test-offline/nymea-remoteproxy-tests-offline.cpp +++ b/tests/test-offline/nymea-remoteproxy-tests-offline.cpp @@ -53,9 +53,14 @@ void RemoteProxyOfflineTests::dummyAuthenticator() { cleanUpEngine(); + m_configuration = new ProxyConfiguration(this); + loadConfiguration(":/test-configuration.conf"); + + m_dummyAuthenticator = new DummyAuthenticator(this); + m_authenticator = qobject_cast(m_dummyAuthenticator); + // Start proxy webserver Engine::instance()->setAuthenticator(m_dummyAuthenticator); - Engine::instance()->setAuthenticator(m_dummyAuthenticator); QSignalSpy runningSpy(Engine::instance(), &Engine::runningChanged); Engine::instance()->start(m_configuration); runningSpy.wait(); @@ -209,16 +214,21 @@ void RemoteProxyOfflineTests::serverPortBlocked() { cleanUpEngine(); + m_configuration = new ProxyConfiguration(this); + loadConfiguration(":/test-configuration.conf"); + + m_mockAuthenticator = new MockAuthenticator(this); + m_authenticator = qobject_cast(m_mockAuthenticator); + // Create a dummy server which blocks the port QWebSocketServer dummyServer("dummy-server", QWebSocketServer::NonSecureMode); - dummyServer.listen(QHostAddress::LocalHost, 1212); + QVERIFY(dummyServer.listen(QHostAddress::LocalHost, m_configuration->webSocketServerPort())); // Start proxy webserver QSignalSpy runningSpy(Engine::instance(), &Engine::runningChanged); Engine::instance()->setAuthenticator(m_authenticator); Engine::instance()->start(m_configuration); runningSpy.wait(); - qDebug() << runningSpy.count(); QVERIFY(runningSpy.count() == 1); // Make sure the server is not running @@ -233,11 +243,49 @@ void RemoteProxyOfflineTests::serverPortBlocked() QVERIFY(closedSpy.count() == 1); // Try again - cleanUpEngine(); startServer(); // Clean up stopServer(); + + // Do the same with the tcp server + cleanUpEngine(); + + m_configuration = new ProxyConfiguration(this); + loadConfiguration(":/test-configuration.conf"); + + m_mockAuthenticator = new MockAuthenticator(this); + m_authenticator = qobject_cast(m_mockAuthenticator); + + // Create a dummy server which blocks the port + QTcpServer *tcpDummyServer = new QTcpServer(this); + QVERIFY(tcpDummyServer->listen(QHostAddress::LocalHost, m_configuration->tcpServerPort())); + + // Start proxy webserver + QSignalSpy runningSpy2(Engine::instance(), &Engine::runningChanged); + Engine::instance()->setAuthenticator(m_authenticator); + Engine::instance()->start(m_configuration); + runningSpy2.wait(); + QVERIFY(runningSpy2.count() == 1); + + // Make sure the engine is running + QVERIFY(Engine::instance()->running()); + + // Make sure the TCP server is not running + QVERIFY(!Engine::instance()->tcpSocketServer()->running()); + + tcpDummyServer->close(); + delete tcpDummyServer; + + // Try again + startServer(); + + // Make sure the TCP server is not running + QVERIFY(Engine::instance()->webSocketServer()->running()); + QVERIFY(Engine::instance()->tcpSocketServer()->running()); + + // Clean up + stopServer(); } void RemoteProxyOfflineTests::websocketBinaryData() @@ -286,13 +334,70 @@ void RemoteProxyOfflineTests::websocketPing() stopServer(); } +//void RemoteProxyOfflineTests::apiBasicCallsTcp_data() +//{ +// QTest::addColumn("data"); +// QTest::addColumn("responseId"); +// QTest::addColumn("responseStatus"); + +// QTest::newRow("valid call") << QByteArray("{\"id\":42, \"method\":\"RemoteProxy.Hello\"}") << 42 << "success"; +// QTest::newRow("missing id") << QByteArray("{\"method\":\"RemoteProxy.Hello\"}") << -1 << "error"; +// QTest::newRow("missing method") << QByteArray("{\"id\":42}") << 42 << "error"; +// QTest::newRow("invalid json") << QByteArray("{\"id\":42, \"method\":\"RemoteProx") << -1 << "error"; +// QTest::newRow("invalid function") << QByteArray("{\"id\":42, \"method\":\"RemoteProxy.Explode\"}") << 42 << "error"; +// QTest::newRow("invalid namespace") << QByteArray("{\"id\":42, \"method\":\"ProxyRemote.Hello\"}") << 42 << "error"; +// QTest::newRow("missing dot") << QByteArray("{\"id\":42, \"method\":\"RemoteProxyHello\"}") << 42 << "error"; +// QTest::newRow("invalid params") << QByteArray("{\"id\":42, \"method\":\"RemoteProxy.Hello\", \"params\":{\"törööö\":\"chooo-chooo\"}}") << 42 << "error"; +// QTest::newRow("invalid authentication params") << QByteArray("{\"id\":42, \"method\":\"Authentication.Authenticate\", \"params\":{\"your\":\"mamma\"}}") << 42 << "error"; +//} + +//void RemoteProxyOfflineTests::apiBasicCallsTcp() +//{ +// QFETCH(QByteArray, data); +// QFETCH(int, responseId); +// QFETCH(QString, responseStatus); + +// // Start the server +// startServer(); + +// QVariant response = injectTcpSocketData(data); +// QVERIFY(!response.isNull()); + +// qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); + +// QCOMPARE(response.toMap().value("id").toInt(), responseId); +// QCOMPARE(response.toMap().value("status").toString(), responseStatus); + +// // Clean up +// stopServer(); +//} + void RemoteProxyOfflineTests::getIntrospect() { // Start the server startServer(); - QVariant response = invokeWebSocketApiCall("RemoteProxy.Introspect"); - qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); + QVariantMap response; + + // WebSocket + response = invokeWebSocketApiCall("RemoteProxy.Introspect").toMap(); + //qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); + QVERIFY(!response.isEmpty()); + QVERIFY(response.value("status").toString() == "success"); + QVERIFY(response.value("params").toMap().contains("methods")); + QVERIFY(response.value("params").toMap().contains("notifications")); + QVERIFY(response.value("params").toMap().contains("types")); + + // Tcp + response.clear(); + response = invokeTcpSocketApiCall("RemoteProxy.Introspect").toMap(); + //qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); + + QVERIFY(!response.isEmpty()); + QVERIFY(response.value("status").toString() == "success"); + QVERIFY(response.value("params").toMap().contains("methods")); + QVERIFY(response.value("params").toMap().contains("notifications")); + QVERIFY(response.value("params").toMap().contains("types")); // Clean up stopServer(); @@ -302,16 +407,32 @@ void RemoteProxyOfflineTests::getHello() { // Start the server startServer(); + QVariantMap response; - QVariantMap response = invokeWebSocketApiCall("RemoteProxy.Hello").toMap(); - qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); + // WebSocket + response = invokeWebSocketApiCall("RemoteProxy.Hello").toMap(); + //qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); // Verify data + QVERIFY(!response.isEmpty()); QCOMPARE(response.value("params").toMap().value("name").toString(), Engine::instance()->configuration()->serverName()); QCOMPARE(response.value("params").toMap().value("server").toString(), QString(SERVER_NAME_STRING)); QCOMPARE(response.value("params").toMap().value("version").toString(), QString(SERVER_VERSION_STRING)); QCOMPARE(response.value("params").toMap().value("apiVersion").toString(), QString(API_VERSION_STRING)); + // TCP + response.clear(); + response = invokeTcpSocketApiCall("RemoteProxy.Hello").toMap(); + //qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); + + // Verify data + QVERIFY(!response.isEmpty()); + QCOMPARE(response.value("params").toMap().value("name").toString(), Engine::instance()->configuration()->serverName()); + QCOMPARE(response.value("params").toMap().value("server").toString(), QString(SERVER_NAME_STRING)); + QCOMPARE(response.value("params").toMap().value("version").toString(), QString(SERVER_VERSION_STRING)); + QCOMPARE(response.value("params").toMap().value("apiVersion").toString(), QString(API_VERSION_STRING)); + + // Clean up stopServer(); } @@ -342,11 +463,22 @@ void RemoteProxyOfflineTests::apiBasicCalls() // Start the server startServer(); - QVariant response = injectWebSocketData(data); - qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); + QVariantMap response; - QCOMPARE(response.toMap().value("id").toInt(), responseId); - QCOMPARE(response.toMap().value("status").toString(), responseStatus); + // Websocket + response = injectWebSocketData(data).toMap(); + //qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); + QVERIFY(!response.isEmpty()); + QCOMPARE(response.value("id").toInt(), responseId); + QCOMPARE(response.value("status").toString(), responseStatus); + + // TCP + response.clear(); + response = injectTcpSocketData(data).toMap(); + //qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); + QVERIFY(!response.isEmpty()); + QCOMPARE(response.value("id").toInt(), responseId); + QCOMPARE(response.value("status").toString(), responseStatus); // Clean up stopServer(); @@ -409,8 +541,14 @@ void RemoteProxyOfflineTests::authenticate() params.insert("token", token); if (!nonce.isEmpty()) params.insert("nonce", nonce); - QVariant response = invokeWebSocketApiCall("Authentication.Authenticate", params); - qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); + // WebSocket + QVariantMap response; + response = invokeWebSocketApiCall("Authentication.Authenticate", params).toMap(); + //qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); + verifyAuthenticationError(response, expectedError); + + // TCP + response = invokeTcpSocketApiCall("Authentication.Authenticate", params).toMap(); verifyAuthenticationError(response, expectedError); // Clean up @@ -518,6 +656,9 @@ void RemoteProxyOfflineTests::authenticateSendData() // Start the server startServer(); + m_mockAuthenticator->setTimeoutDuration(100); + m_mockAuthenticator->setExpectedAuthenticationError(); + QVariantMap params; params.insert("uuid", "uuid"); params.insert("name", "name"); @@ -533,18 +674,18 @@ void RemoteProxyOfflineTests::authenticateSendData() // Connect socket QWebSocket *socket = new QWebSocket("proxy-testclient", QWebSocketProtocol::Version13); connect(socket, &QWebSocket::sslErrors, this, &BaseTest::sslErrors); - QSignalSpy spyConnection(socket, SIGNAL(connected())); + QSignalSpy spyConnection(socket, &QWebSocket::connected); socket->open(Engine::instance()->webSocketServer()->serverUrl()); spyConnection.wait(); QVERIFY(spyConnection.count() == 1); // Authenticate - QSignalSpy dataSpy(socket, SIGNAL(textMessageReceived(QString))); + QSignalSpy dataSpy(socket, &QWebSocket::textMessageReceived); socket->sendTextMessage(QString(jsonDoc.toJson(QJsonDocument::Compact))); dataSpy.wait(); QVERIFY(dataSpy.count() == 1); - // Send data and make sure we get disconnected + // Send data again and make sure we get disconnected since sending data while waiting for the partner is forbidden QSignalSpy disconnectedSpy(socket, SIGNAL(disconnected())); socket->sendTextMessage(QString(jsonDoc.toJson(QJsonDocument::Compact))); disconnectedSpy.wait(); @@ -556,7 +697,7 @@ void RemoteProxyOfflineTests::authenticateSendData() stopServer(); } -void RemoteProxyOfflineTests::clientConnection() +void RemoteProxyOfflineTests::clientConnectionWebSocket() { // Start the server startServer(); @@ -565,7 +706,7 @@ void RemoteProxyOfflineTests::clientConnection() m_mockAuthenticator->setTimeoutDuration(100); m_mockAuthenticator->setExpectedAuthenticationError(); - // Connect to the server (insecure disabled) + // Connect to the server using WebSocket (insecure disabled) RemoteProxyConnection *connection = new RemoteProxyConnection(QUuid::createUuid(), "Test client one", this); connect(connection, &RemoteProxyConnection::sslErrors, this, &BaseTest::ignoreConnectionSslError); @@ -606,6 +747,56 @@ void RemoteProxyOfflineTests::clientConnection() stopServer(); } +void RemoteProxyOfflineTests::clientConnectionTcpSocket() +{ + // Start the server + startServer(); + + // Configure mock authenticator + m_mockAuthenticator->setTimeoutDuration(100); + m_mockAuthenticator->setExpectedAuthenticationError(); + + // Connect to the server using TCP (insecure disabled) + RemoteProxyConnection *connection = new RemoteProxyConnection(QUuid::createUuid(), "Test client one", RemoteProxyConnection::ConnectionTypeTcpSocket, this); + connect(connection, &RemoteProxyConnection::sslErrors, this, &BaseTest::ignoreConnectionSslError); + + // Connect to server (insecue enabled for testing) + QSignalSpy readySpy(connection, &RemoteProxyConnection::ready); + QVERIFY(connection->connectServer(m_serverUrlTcp)); + readySpy.wait(); + QVERIFY(readySpy.count() == 1); + QVERIFY(connection->isConnected()); + QVERIFY(!connection->isRemoteConnected()); + QVERIFY(connection->state() == RemoteProxyConnection::StateReady); + QVERIFY(connection->error() == QAbstractSocket::UnknownSocketError); + QVERIFY(connection->serverUrl() == m_serverUrlTcp); + QVERIFY(connection->connectionType() == RemoteProxyConnection::ConnectionTypeTcpSocket); + QVERIFY(connection->serverName() == SERVER_NAME_STRING); + QVERIFY(connection->proxyServerName() == Engine::instance()->serverName()); + QVERIFY(connection->proxyServerVersion() == SERVER_VERSION_STRING); + QVERIFY(connection->proxyServerApiVersion() == API_VERSION_STRING); + + QSignalSpy authenticatedSpy(connection, &RemoteProxyConnection::authenticated); + QVERIFY(connection->authenticate("foobar")); + authenticatedSpy.wait(); + QVERIFY(authenticatedSpy.count() == 1); + QVERIFY(connection->isConnected()); + QVERIFY(connection->isAuthenticated()); + QVERIFY(connection->state() == RemoteProxyConnection::StateAuthenticated); + + // Disconnect and clean up + QSignalSpy spyDisconnected(connection, &RemoteProxyConnection::disconnected); + connection->disconnectServer(); + // FIXME: check why it waits the full time here + spyDisconnected.wait(500); + + QVERIFY(spyDisconnected.count() >= 1); + QVERIFY(!connection->isConnected()); + + connection->deleteLater(); + stopServer(); +} + void RemoteProxyOfflineTests::remoteConnection() { // Start the server @@ -912,6 +1103,10 @@ void RemoteProxyOfflineTests::jsonRpcTimeout() // Start the server startServer(); + m_configuration->setAuthenticationTimeout(3000); + m_configuration->setJsonRpcTimeout(1000); + m_configuration->setInactiveTimeout(2000); + // Configure result (authentication takes longer than json rpc timeout m_mockAuthenticator->setExpectedAuthenticationError(); m_mockAuthenticator->setTimeoutDuration(4000); @@ -964,7 +1159,7 @@ void RemoteProxyOfflineTests::authenticationReplyTimeout() // Configure result (authentication takes longer than json rpc timeout m_mockAuthenticator->setExpectedAuthenticationError(); - m_mockAuthenticator->setTimeoutDuration(1000); + m_mockAuthenticator->setTimeoutDuration(2000); m_configuration->setAuthenticationTimeout(500); m_configuration->setJsonRpcTimeout(1000); @@ -1017,4 +1212,98 @@ void RemoteProxyOfflineTests::authenticationReplyConnection() stopServer(); } +//void RemoteProxyOfflineTests::tcpRemoteConnection() +//{ +// // Start the server +// startServer(); + +// // Configure mock authenticator +// m_mockAuthenticator->setTimeoutDuration(100); +// m_mockAuthenticator->setExpectedAuthenticationError(); + +// QString nameConnectionOne = "Test client one"; +// QUuid uuidConnectionOne = QUuid::createUuid(); + +// QString nameConnectionTwo = "Test client two"; +// QUuid uuidConnectionTwo = QUuid::createUuid(); + +// QByteArray dataOne = "Hello from client one :-)"; +// QByteArray dataTwo = "Hello from client two :-)"; + +// // Create two connection +// RemoteProxyConnection *connectionOne = new RemoteProxyConnection(uuidConnectionOne, nameConnectionOne, RemoteProxyConnection::ConnectionTypeTcpSocket, this); +// connect(connectionOne, &RemoteProxyConnection::sslErrors, this, &BaseTest::ignoreConnectionSslError); + +// RemoteProxyConnection *connectionTwo = new RemoteProxyConnection(uuidConnectionTwo, nameConnectionTwo, RemoteProxyConnection::ConnectionTypeTcpSocket, this); +// connect(connectionTwo, &RemoteProxyConnection::sslErrors, this, &BaseTest::ignoreConnectionSslError); + +// // Connect one +// QSignalSpy connectionOneReadySpy(connectionOne, &RemoteProxyConnection::ready); +// QVERIFY(connectionOne->connectServer(m_serverUrlTcp)); +// connectionOneReadySpy.wait(); +// QVERIFY(connectionOneReadySpy.count() == 1); +// QVERIFY(connectionOne->isConnected()); + +// // Connect two +// QSignalSpy connectionTwoReadySpy(connectionTwo, &RemoteProxyConnection::ready); +// QVERIFY(connectionTwo->connectServer(m_serverUrlTcp)); +// connectionTwoReadySpy.wait(); +// QVERIFY(connectionTwoReadySpy.count() == 1); +// QVERIFY(connectionTwo->isConnected()); + +// // Authenticate one +// QSignalSpy remoteConnectionEstablishedOne(connectionOne, &RemoteProxyConnection::remoteConnectionEstablished); +// QSignalSpy connectionOneAuthenticatedSpy(connectionOne, &RemoteProxyConnection::authenticated); +// QVERIFY(connectionOne->authenticate(m_testToken)); +// connectionOneAuthenticatedSpy.wait(); +// QVERIFY(connectionOneAuthenticatedSpy.count() == 1); +// QVERIFY(connectionOne->isConnected()); +// QVERIFY(connectionOne->isAuthenticated()); +// QVERIFY(connectionOne->state() == RemoteProxyConnection::StateAuthenticated); + +// // Authenticate two +// QSignalSpy remoteConnectionEstablishedTwo(connectionTwo, &RemoteProxyConnection::remoteConnectionEstablished); +// QSignalSpy connectionTwoAuthenticatedSpy(connectionTwo, &RemoteProxyConnection::authenticated); +// QVERIFY(connectionTwo->authenticate(m_testToken)); +// connectionTwoAuthenticatedSpy.wait(); +// qDebug() << connectionTwoAuthenticatedSpy.count(); +// QVERIFY(connectionTwoAuthenticatedSpy.count() == 1); +// QVERIFY(connectionTwo->isConnected()); +// QVERIFY(connectionTwo->isAuthenticated()); + +// // Wait for both to be connected +// remoteConnectionEstablishedOne.wait(500); +// remoteConnectionEstablishedTwo.wait(500); + +// QVERIFY(remoteConnectionEstablishedOne.count() == 1); +// QVERIFY(remoteConnectionEstablishedTwo.count() == 1); +// QVERIFY(connectionOne->state() == RemoteProxyConnection::StateRemoteConnected); +// QVERIFY(connectionTwo->state() == RemoteProxyConnection::StateRemoteConnected); + +// QCOMPARE(connectionOne->tunnelPartnerName(), nameConnectionTwo); +// QCOMPARE(connectionOne->tunnelPartnerUuid(), uuidConnectionTwo.toString()); +// QCOMPARE(connectionTwo->tunnelPartnerName(), nameConnectionOne); +// QCOMPARE(connectionTwo->tunnelPartnerUuid(), uuidConnectionOne.toString()); + +// // Pipe data trought the tunnel +// QSignalSpy remoteConnectionDataOne(connectionOne, &RemoteProxyConnection::dataReady); +// QSignalSpy remoteConnectionDataTwo(connectionTwo, &RemoteProxyConnection::dataReady); + +// connectionOne->sendData(dataOne); +// remoteConnectionDataTwo.wait(500); +// QVERIFY(remoteConnectionDataTwo.count() == 1); +// QCOMPARE(remoteConnectionDataTwo.at(0).at(0).toByteArray().trimmed(), dataOne); + +// connectionTwo->sendData(dataTwo); +// remoteConnectionDataOne.wait(500); +// QVERIFY(remoteConnectionDataOne.count() == 1); +// QCOMPARE(remoteConnectionDataOne.at(0).at(0).toByteArray().trimmed(), dataTwo); + +// connectionOne->deleteLater(); +// connectionTwo->deleteLater(); + +// // Clean up +// stopServer(); +//} + QTEST_MAIN(RemoteProxyOfflineTests) diff --git a/tests/test-offline/nymea-remoteproxy-tests-offline.h b/tests/test-offline/nymea-remoteproxy-tests-offline.h index af62b3c..e7fe61e 100644 --- a/tests/test-offline/nymea-remoteproxy-tests-offline.h +++ b/tests/test-offline/nymea-remoteproxy-tests-offline.h @@ -54,10 +54,7 @@ private slots: void websocketBinaryData(); void websocketPing(); - // TCP socket connection - - - // Api + // WebSocket connection API void getIntrospect(); void getHello(); @@ -71,20 +68,21 @@ private slots: void authenticateSendData(); // Client lib - void clientConnection(); + void clientConnectionTcpSocket(); + void clientConnectionWebSocket(); void remoteConnection(); void multipleRemoteConnection(); void trippleConnection(); void duplicateUuid(); void sslConfigurations(); - void jsonRpcTimeout(); void inactiveTimeout(); + void jsonRpcTimeout(); void authenticationReplyTimeout(); void authenticationReplyConnection(); // TCP Websocket combinations - + //void tcpRemoteConnection(); }; diff --git a/tests/testbase/basetest.cpp b/tests/testbase/basetest.cpp index 6b398cd..62d54eb 100644 --- a/tests/testbase/basetest.cpp +++ b/tests/testbase/basetest.cpp @@ -31,8 +31,10 @@ #include "loggingcategories.h" #include "remoteproxyconnection.h" +#include #include #include +#include #include #include #include @@ -47,16 +49,34 @@ void BaseTest::loadConfiguration(const QString &fileName) { qDebug() << "Load test configurations" << fileName; m_configuration->loadConfiguration(fileName); - restartEngine(); + //restartEngine(); } void BaseTest::cleanUpEngine() { + qDebug() << "Clean up engine"; if (Engine::exists()) { Engine::instance()->stop(); Engine::instance()->destroy(); QVERIFY(!Engine::exists()); } + + if (m_mockAuthenticator) { + delete m_mockAuthenticator; + m_mockAuthenticator = nullptr; + } + + if (m_dummyAuthenticator) { + delete m_dummyAuthenticator; + m_dummyAuthenticator = nullptr; + } + + m_authenticator = nullptr; + + if (m_configuration) { + delete m_configuration; + m_configuration = nullptr; + } } void BaseTest::restartEngine() @@ -67,22 +87,29 @@ void BaseTest::restartEngine() void BaseTest::startEngine() { + m_configuration = new ProxyConfiguration(this); + loadConfiguration(":/test-configuration.conf"); + + m_dummyAuthenticator = new DummyAuthenticator(this); + m_mockAuthenticator = new MockAuthenticator(this); + m_authenticator = qobject_cast(m_mockAuthenticator); if (!Engine::exists()) { Engine::instance()->setAuthenticator(m_authenticator); Engine::instance()->setDeveloperModeEnabled(true); QVERIFY(Engine::exists()); + QVERIFY(!Engine::instance()->running()); } } void BaseTest::startServer() { - startEngine(); + restartEngine(); if (!Engine::instance()->running()) { QSignalSpy runningSpy(Engine::instance(), &Engine::runningChanged); Engine::instance()->setDeveloperModeEnabled(true); Engine::instance()->start(m_configuration); - runningSpy.wait(100); + runningSpy.wait(200); QVERIFY(runningSpy.count() == 1); } @@ -100,6 +127,8 @@ void BaseTest::stopServer() Engine::instance()->stop(); QVERIFY(!Engine::instance()->running()); + + cleanUpEngine(); } QVariant BaseTest::invokeWebSocketApiCall(const QString &method, const QVariantMap params, bool remainsConnected) @@ -310,20 +339,117 @@ bool BaseTest::createRemoteConnection(const QString &token, const QString &nonce return true; } +void BaseTest::initTestCase() +{ + Q_UNUSED(remainsConnected) + + QVariantMap request; + request.insert("id", m_commandCounter); + request.insert("method", method); + request.insert("params", params); + QJsonDocument jsonDoc = QJsonDocument::fromVariant(request); + + QSslSocket *socket = new QSslSocket(this); + typedef void (QSslSocket:: *sslErrorsSignal)(const QList &); + QObject::connect(socket, static_cast(&QSslSocket::sslErrors), this, &BaseTest::sslSocketSslErrors); + + QSignalSpy spyConnection(socket, &QSslSocket::connected); + socket->connectToHostEncrypted(Engine::instance()->tcpSocketServer()->serverUrl().host(), + static_cast(Engine::instance()->tcpSocketServer()->serverUrl().port())); + spyConnection.wait(); + if (spyConnection.count() == 0) { + return QVariant(); + } + + QSignalSpy dataSpy(socket, &QSslSocket::readyRead); + socket->write(jsonDoc.toJson(QJsonDocument::Compact) + '\n'); + // FIXME: check why it waits the full time here + dataSpy.wait(500); + if (dataSpy.count() != 1) { + qWarning() << "No data received"; + return QVariant(); + } + + QByteArray data = socket->readAll(); + socket->close(); + socket->deleteLater(); + + // Make sure the response ends with '}\n' + if (!data.endsWith("}\n")) { + qWarning() << "JSON data does not end with \"}\n\""; + return QVariant(); + } + + // Make sure the response it a valid JSON string + QJsonParseError error; + jsonDoc = QJsonDocument::fromJson(data, &error); + if (error.error != QJsonParseError::NoError) { + qWarning() << "JSON parser error" << error.errorString(); + return QVariant(); + } + + QVariantMap response = jsonDoc.toVariant().toMap(); + + if (response.value("id").toInt() == m_commandCounter) { + m_commandCounter++; + return jsonDoc.toVariant(); + } + + m_commandCounter++; + return QVariant(); +} + +QVariant BaseTest::injectTcpSocketData(const QByteArray &data) +{ + QSslSocket *socket = new QSslSocket(this); + typedef void (QSslSocket:: *sslErrorsSignal)(const QList &); + QObject::connect(socket, static_cast(&QSslSocket::sslErrors), this, &BaseTest::sslSocketSslErrors); + + QSignalSpy spyConnection(socket, &QSslSocket::connected); + socket->connectToHostEncrypted(Engine::instance()->tcpSocketServer()->serverUrl().host(), + static_cast(Engine::instance()->tcpSocketServer()->serverUrl().port())); + spyConnection.wait(); + if (spyConnection.count() == 0) { + return QVariant(); + } + + QSignalSpy dataSpy(socket, &QSslSocket::readyRead); + socket->write(data + '\n'); + dataSpy.wait(); + // FIXME: check why it waits the full time here + dataSpy.wait(500); + if (dataSpy.count() != 1) { + qWarning() << "No data received"; + return QVariant(); + } + + QByteArray socketData = socket->readAll(); + socket->close(); + socket->deleteLater(); + + // Make sure the response ends with '}\n' + if (!socketData.endsWith("}\n")) { + qWarning() << "JSON data does not end with \"}\n\""; + return QVariant(); + } + + // Make sure the response it a valid JSON string + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(socketData, &error); + if (error.error != QJsonParseError::NoError) { + qWarning() << "JSON parser error" << error.errorString(); + return QVariant(); + } + + m_commandCounter++; + return jsonDoc.toVariant(); +} + void BaseTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); - m_configuration = new ProxyConfiguration(this); - m_configuration->loadConfiguration(":/test-configuration.conf"); - - m_mockAuthenticator = new MockAuthenticator(this); - m_dummyAuthenticator = new DummyAuthenticator(this); - //m_awsAuthenticator = new AwsAuthenticator(m_configuration->awsCredentialsUrl(), this); - - m_authenticator = qobject_cast(m_mockAuthenticator); - m_testToken = "eyJraWQiOiJXdnFFT3prVVh5VDlINzFyRUpoNWdxRnkxNFhnR2l3SFAzVEIzUFQ1V3ZrPSIsImFsZyI6IlJT" "MjU2In0.eyJzdWIiOiJmZTJmZDNlNC1hMGJhLTQ1OTUtOWRiZS00ZDkxYjRiMjFlMzUiLCJhdWQiOiI4cmpoZ" "mRsZjlqZjFzdW9rMmpjcmx0ZDZ2IiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV2ZW50X2lkIjoiN2Y5NTRiNm" @@ -337,20 +463,13 @@ void BaseTest::initTestCase() "pQbj58v1vktaAEATdmKmlzmcix-HJK9wWHRSuv3TsNa8DGxvcPOoeTu8Vql7krZ-y7Zu-s2WsgeP4VxyT80VE" "T_xh6pMkOhE6g"; - qCDebug(dcApplication()) << "Init test case."; - restartEngine(); + qCDebug(dcApplication()) << "Init test case done."; + //restartEngine(); } void BaseTest::cleanupTestCase() { qCDebug(dcApplication()) << "Clean up test case."; - delete m_configuration; - delete m_mockAuthenticator; - delete m_dummyAuthenticator; - //delete m_awsAuthenticator; - - m_authenticator = nullptr; - cleanUpEngine(); } diff --git a/tests/testbase/basetest.h b/tests/testbase/basetest.h index 60c029c..a5f04ef 100644 --- a/tests/testbase/basetest.h +++ b/tests/testbase/basetest.h @@ -57,6 +57,7 @@ protected: ProxyConfiguration *m_configuration = nullptr; QUrl m_serverUrl = QUrl("wss://127.0.0.1:1212"); + QUrl m_serverUrlTcp = QUrl("ssl://127.0.0.1:1213"); QSslConfiguration m_sslConfiguration; @@ -88,8 +89,13 @@ protected slots: void cleanupTestCase(); public slots: + inline void sslSocketSslErrors(const QList &) { + QSslSocket *socket = static_cast(sender()); + socket->ignoreSslErrors(); + } + inline void sslErrors(const QList &) { - QWebSocket *socket = static_cast(sender()); + QWebSocket *socket = static_cast(sender()); socket->ignoreSslErrors(); } diff --git a/tests/testbase/mockauthenticator.cpp b/tests/testbase/mockauthenticator.cpp index d699253..06731cf 100644 --- a/tests/testbase/mockauthenticator.cpp +++ b/tests/testbase/mockauthenticator.cpp @@ -55,8 +55,7 @@ void MockAuthenticator::replyFinished() { MockAuthenticationReply *reply = static_cast(sender()); - qCDebug(dcAuthentication()) << name() << "Authentication finished."; - + qCDebug(dcAuthentication()) << name() << "Authentication finished" << reply << reply->authenticationReply(); setReplyError(reply->authenticationReply(), reply->error()); setReplyFinished(reply->authenticationReply()); reply->deleteLater(); @@ -64,7 +63,7 @@ void MockAuthenticator::replyFinished() AuthenticationReply *MockAuthenticator::authenticate(ProxyClient *proxyClient) { - qCDebug(dcAuthentication()) << name() << "Start authentication for" << proxyClient << "using token" << proxyClient->token(); + qCDebug(dcAuthentication()) << name() << "Start authentication for" << proxyClient << "using token" << proxyClient->token() << "Auth duration" << m_timeoutDuration << "[ms]"; AuthenticationReply *authenticationReply = createAuthenticationReply(proxyClient, proxyClient); @@ -84,3 +83,8 @@ MockAuthenticationReply::MockAuthenticationReply(int timeout, Authenticator::Aut QTimer::singleShot(timeout, this, &MockAuthenticationReply::finished); } +MockAuthenticationReply::~MockAuthenticationReply() +{ + qCCritical(dcAuthentication()) << "Destroy mock authentication reply"; +} + diff --git a/tests/testbase/mockauthenticator.h b/tests/testbase/mockauthenticator.h index fe80b83..77bae35 100644 --- a/tests/testbase/mockauthenticator.h +++ b/tests/testbase/mockauthenticator.h @@ -40,6 +40,7 @@ class MockAuthenticationReply : public QObject Q_OBJECT public: explicit MockAuthenticationReply(int timeout, Authenticator::AuthenticationError error, AuthenticationReply *authenticationReply, QObject *parent = nullptr); + ~MockAuthenticationReply(); AuthenticationReply *authenticationReply() const { return m_authenticationReply; } Authenticator::AuthenticationError error() const { return m_error; } @@ -66,7 +67,7 @@ public: void setExpectedAuthenticationError(Authenticator::AuthenticationError error = AuthenticationErrorNoError); private: - int m_timeoutDuration = 1000; + int m_timeoutDuration = 500; Authenticator::AuthenticationError m_expectedError; private slots: From b49a9767c7055bc360ba92a7c0b603c114b01947 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 12 Jun 2019 18:57:14 +0200 Subject: [PATCH 12/16] Fix package fragmentation in TCP server and client --- libnymea-remoteproxy/jsonrpcserver.cpp | 159 ++++++++++-------- libnymea-remoteproxy/jsonrpcserver.h | 2 + libnymea-remoteproxy/proxyclient.cpp | 37 ++++ libnymea-remoteproxy/proxyclient.h | 10 ++ .../proxyjsonrpcclient.cpp | 22 ++- .../proxyjsonrpcclient.h | 2 + .../nymea-remoteproxy-tests-offline.cpp | 152 ++++++++--------- .../nymea-remoteproxy-tests-offline.h | 2 +- 8 files changed, 235 insertions(+), 151 deletions(-) diff --git a/libnymea-remoteproxy/jsonrpcserver.cpp b/libnymea-remoteproxy/jsonrpcserver.cpp index b74d3a8..cbb29ce 100644 --- a/libnymea-remoteproxy/jsonrpcserver.cpp +++ b/libnymea-remoteproxy/jsonrpcserver.cpp @@ -173,6 +173,85 @@ void JsonRpcServer::unregisterHandler(JsonHandler *handler) m_handlers.remove(handler->name()); } +void JsonRpcServer::processDataPackage(ProxyClient *proxyClient, const QByteArray &data) +{ + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + if(error.error != QJsonParseError::NoError) { + qCWarning(dcJsonRpc) << "Failed to parse JSON data" << data << ":" << error.errorString(); + sendErrorResponse(proxyClient, -1, QString("Failed to parse JSON data: %1").arg(error.errorString())); + proxyClient->killConnection("Invalid JSON data received."); + return; + } + + QVariantMap message = jsonDoc.toVariant().toMap(); + + bool success = false; + int commandId = message.value("id").toInt(&success); + if (!success) { + qCWarning(dcJsonRpc()) << "Error parsing command. Missing \"id\":" << message; + sendErrorResponse(proxyClient, -1, "Error parsing command. Missing 'id'"); + proxyClient->killConnection("The id property is missing in the request."); + return; + } + + QStringList commandList = message.value("method").toString().split('.'); + if (commandList.count() != 2) { + qCWarning(dcJsonRpc) << "Error parsing method.\nGot:" << message.value("method").toString() << "\nExpected: \"Namespace.method\""; + sendErrorResponse(proxyClient, commandId, QString("Error parsing method. Got: '%1'', Expected: 'Namespace.method'").arg(message.value("method").toString())); + proxyClient->killConnection("Invalid method passed."); + return; + } + + QString targetNamespace = commandList.first(); + QString method = commandList.last(); + + JsonHandler *handler = m_handlers.value(targetNamespace); + if (!handler) { + sendErrorResponse(proxyClient, commandId, "No such namespace"); + proxyClient->killConnection("No such namespace."); + return; + } + + if (!handler->hasMethod(method)) { + sendErrorResponse(proxyClient, commandId, "No such method"); + proxyClient->killConnection("No such method."); + return; + } + + QVariantMap params = message.value("params").toMap(); + QPair validationResult = handler->validateParams(method, params); + if (!validationResult.first) { + sendErrorResponse(proxyClient, commandId, "Invalid params: " + validationResult.second); + proxyClient->killConnection("Invalid params passed."); + return; + } + + JsonReply *reply; + QMetaObject::invokeMethod(handler, method.toLatin1().data(), Q_RETURN_ARG(JsonReply*, reply), Q_ARG(QVariantMap, params), Q_ARG(ProxyClient *, proxyClient)); + if (reply->type() == JsonReply::TypeAsync) { + m_asyncReplies.insert(reply, proxyClient); + reply->setClientId(proxyClient->clientId()); + reply->setCommandId(commandId); + + connect(reply, &JsonReply::finished, this, &JsonRpcServer::asyncReplyFinished); + reply->startWait(); + } else { + Q_ASSERT_X((targetNamespace == "RemoteProxy" && method == "Introspect") || handler->validateReturns(method, reply->data()).first + ,"validating return value", formatAssertion(targetNamespace, method, handler, reply->data()).toLatin1().data()); + +// QPair returnValidation = reply->handler()->validateReturns(reply->method(), reply->data()); +// if (!returnValidation.first) { +// qCWarning(dcJsonRpc()) << "Return value validation failed of sync reply. This should never happen. Please check the source code."; +// } + + reply->setClientId(proxyClient->clientId()); + reply->setCommandId(commandId); + sendResponse(proxyClient, commandId, reply->data()); + reply->deleteLater(); + } +} + void JsonRpcServer::setup() { registerHandler(this); @@ -251,80 +330,18 @@ void JsonRpcServer::processData(ProxyClient *proxyClient, const QByteArray &data qCDebug(dcJsonRpcTraffic()) << "Incoming data from" << proxyClient << ": " << data; - QJsonParseError error; - QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); - if(error.error != QJsonParseError::NoError) { - qCWarning(dcJsonRpc) << "Failed to parse JSON data" << data << ":" << error.errorString(); - sendErrorResponse(proxyClient, -1, QString("Failed to parse JSON data: %1").arg(error.errorString())); - proxyClient->killConnection("Invalid JSON data received."); + // Handle package fragmentation + QList packages = proxyClient->processData(data); + + // Make sure the buffer size is in range + if (proxyClient->bufferSizeViolation()) { + qCWarning(dcJsonRpc()) << "Data buffer size violation from" << proxyClient; + proxyClient->killConnection("Data buffer size violation. Data >= 10 kB"); return; } - QVariantMap message = jsonDoc.toVariant().toMap(); - - bool success = false; - int commandId = message.value("id").toInt(&success); - if (!success) { - qCWarning(dcJsonRpc()) << "Error parsing command. Missing \"id\":" << message; - sendErrorResponse(proxyClient, -1, "Error parsing command. Missing 'id'"); - proxyClient->killConnection("The id property is missing in the request."); - return; - } - - QStringList commandList = message.value("method").toString().split('.'); - if (commandList.count() != 2) { - qCWarning(dcJsonRpc) << "Error parsing method.\nGot:" << message.value("method").toString() << "\nExpected: \"Namespace.method\""; - sendErrorResponse(proxyClient, commandId, QString("Error parsing method. Got: '%1'', Expected: 'Namespace.method'").arg(message.value("method").toString())); - proxyClient->killConnection("Invalid method passed."); - return; - } - - QString targetNamespace = commandList.first(); - QString method = commandList.last(); - - JsonHandler *handler = m_handlers.value(targetNamespace); - if (!handler) { - sendErrorResponse(proxyClient, commandId, "No such namespace"); - proxyClient->killConnection("No such namespace."); - return; - } - - if (!handler->hasMethod(method)) { - sendErrorResponse(proxyClient, commandId, "No such method"); - proxyClient->killConnection("No such method."); - return; - } - - QVariantMap params = message.value("params").toMap(); - QPair validationResult = handler->validateParams(method, params); - if (!validationResult.first) { - sendErrorResponse(proxyClient, commandId, "Invalid params: " + validationResult.second); - proxyClient->killConnection("Invalid params passed."); - return; - } - - JsonReply *reply; - QMetaObject::invokeMethod(handler, method.toLatin1().data(), Q_RETURN_ARG(JsonReply*, reply), Q_ARG(QVariantMap, params), Q_ARG(ProxyClient *, proxyClient)); - if (reply->type() == JsonReply::TypeAsync) { - m_asyncReplies.insert(reply, proxyClient); - reply->setClientId(proxyClient->clientId()); - reply->setCommandId(commandId); - - connect(reply, &JsonReply::finished, this, &JsonRpcServer::asyncReplyFinished); - reply->startWait(); - } else { - Q_ASSERT_X((targetNamespace == "RemoteProxy" && method == "Introspect") || handler->validateReturns(method, reply->data()).first - ,"validating return value", formatAssertion(targetNamespace, method, handler, reply->data()).toLatin1().data()); - -// QPair returnValidation = reply->handler()->validateReturns(reply->method(), reply->data()); -// if (!returnValidation.first) { -// qCWarning(dcJsonRpc()) << "Return value validation failed of sync reply. This should never happen. Please check the source code."; -// } - - reply->setClientId(proxyClient->clientId()); - reply->setCommandId(commandId); - sendResponse(proxyClient, commandId, reply->data()); - reply->deleteLater(); + foreach (const QByteArray &package, packages) { + processDataPackage(proxyClient, package); } } diff --git a/libnymea-remoteproxy/jsonrpcserver.h b/libnymea-remoteproxy/jsonrpcserver.h index 45f996a..19249da 100644 --- a/libnymea-remoteproxy/jsonrpcserver.h +++ b/libnymea-remoteproxy/jsonrpcserver.h @@ -58,6 +58,7 @@ private: QHash m_handlers; QHash m_asyncReplies; QList m_clients; + int m_notificationId = 0; void sendResponse(ProxyClient *client, int commandId, const QVariantMap ¶ms = QVariantMap()); @@ -67,6 +68,7 @@ private: void registerHandler(JsonHandler *handler); void unregisterHandler(JsonHandler *handler); + void processDataPackage(ProxyClient *proxyClient, const QByteArray &data); private slots: void setup(); diff --git a/libnymea-remoteproxy/proxyclient.cpp b/libnymea-remoteproxy/proxyclient.cpp index 10784f3..984fbc5 100644 --- a/libnymea-remoteproxy/proxyclient.cpp +++ b/libnymea-remoteproxy/proxyclient.cpp @@ -27,6 +27,7 @@ #include "engine.h" #include "proxyclient.h" +#include "loggingcategories.h" #include @@ -210,6 +211,42 @@ void ProxyClient::killConnection(const QString &reason) m_interface->killClientConnection(m_clientId, reason); } +int ProxyClient::generateMessageId() +{ + m_messageId++; + return m_messageId; +} + +QList ProxyClient::processData(const QByteArray &data) +{ + QList packages; + + // Handle packet fragmentation + m_dataBuffers.append(data); + int splitIndex = m_dataBuffers.indexOf("}\n{"); + while (splitIndex > -1) { + packages.append(m_dataBuffers.left(splitIndex + 1)); + m_dataBuffers = m_dataBuffers.right(m_dataBuffers.length() - splitIndex - 2); + splitIndex = m_dataBuffers.indexOf("}\n{"); + } + if (m_dataBuffers.trimmed().endsWith("}")) { + packages.append(m_dataBuffers); + m_dataBuffers.clear(); + } + + if (m_dataBuffers.size() > 1024 * 10) { + qCWarning(dcJsonRpc()) << "Client buffer larger than 10KB and no valid data. This is a buffer size violation."; + m_bufferSizeViolation = true; + } + + return packages; +} + +bool ProxyClient::bufferSizeViolation() const +{ + return m_bufferSizeViolation; +} + QDebug operator<<(QDebug debug, ProxyClient *proxyClient) { debug.nospace() << "ProxyClient("; diff --git a/libnymea-remoteproxy/proxyclient.h b/libnymea-remoteproxy/proxyclient.h index 792714f..d79093d 100644 --- a/libnymea-remoteproxy/proxyclient.h +++ b/libnymea-remoteproxy/proxyclient.h @@ -95,6 +95,11 @@ public: void sendData(const QByteArray &data); void killConnection(const QString &reason); + // Json server methods + int generateMessageId(); + QList processData(const QByteArray &data); + bool bufferSizeViolation() const; + private: TransportInterface *m_interface = nullptr; QTimer *m_timer = nullptr; @@ -114,6 +119,11 @@ private: QString m_userName; + // Json data information + int m_messageId = 0; + QByteArray m_dataBuffers; + bool m_bufferSizeViolation = false; + quint64 m_rxDataCount = 0; quint64 m_txDataCount = 0; diff --git a/libnymea-remoteproxyclient/proxyjsonrpcclient.cpp b/libnymea-remoteproxyclient/proxyjsonrpcclient.cpp index 78b6550..962d7e7 100644 --- a/libnymea-remoteproxyclient/proxyjsonrpcclient.cpp +++ b/libnymea-remoteproxyclient/proxyjsonrpcclient.cpp @@ -73,10 +73,8 @@ void JsonRpcClient::sendRequest(const QVariantMap &request) m_connection->sendData(data); } -void JsonRpcClient::processData(const QByteArray &data) +void JsonRpcClient::processDataPackage(const QByteArray &data) { - qCDebug(dcRemoteProxyClientJsonRpcTraffic()) << "Received data:" << data; - QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); if (error.error != QJsonParseError::NoError) { @@ -119,4 +117,22 @@ void JsonRpcClient::processData(const QByteArray &data) } } +void JsonRpcClient::processData(const QByteArray &data) +{ + qCDebug(dcRemoteProxyClientJsonRpcTraffic()) << "Received data:" << data; + + // Handle packet fragmentation + m_dataBuffer.append(data); + int splitIndex = m_dataBuffer.indexOf("}\n{"); + while (splitIndex > -1) { + processDataPackage(m_dataBuffer.left(splitIndex + 1)); + m_dataBuffer = m_dataBuffer.right(m_dataBuffer.length() - splitIndex - 2); + splitIndex = m_dataBuffer.indexOf("}\n{"); + } + if (m_dataBuffer.trimmed().endsWith("}")) { + processDataPackage(m_dataBuffer); + m_dataBuffer.clear(); + } +} + } diff --git a/libnymea-remoteproxyclient/proxyjsonrpcclient.h b/libnymea-remoteproxyclient/proxyjsonrpcclient.h index 8fed732..b16488b 100644 --- a/libnymea-remoteproxyclient/proxyjsonrpcclient.h +++ b/libnymea-remoteproxyclient/proxyjsonrpcclient.h @@ -54,10 +54,12 @@ private: ProxyConnection *m_connection = nullptr; int m_commandId = 0; + QByteArray m_dataBuffer; QHash m_replies; void sendRequest(const QVariantMap &request); + void processDataPackage(const QByteArray &data); signals: void tunnelEstablished(const QString clientName, const QString &clientUuid); diff --git a/tests/test-offline/nymea-remoteproxy-tests-offline.cpp b/tests/test-offline/nymea-remoteproxy-tests-offline.cpp index b845662..5773a92 100644 --- a/tests/test-offline/nymea-remoteproxy-tests-offline.cpp +++ b/tests/test-offline/nymea-remoteproxy-tests-offline.cpp @@ -446,7 +446,7 @@ void RemoteProxyOfflineTests::apiBasicCalls_data() QTest::newRow("valid call") << QByteArray("{\"id\":42, \"method\":\"RemoteProxy.Hello\"}") << 42 << "success"; QTest::newRow("missing id") << QByteArray("{\"method\":\"RemoteProxy.Hello\"}") << -1 << "error"; QTest::newRow("missing method") << QByteArray("{\"id\":42}") << 42 << "error"; - QTest::newRow("invalid json") << QByteArray("{\"id\":42, \"method\":\"RemoteProx") << -1 << "error"; + QTest::newRow("invalid json") << QByteArray("{\"id\":42, \"method\":\"RemoteProx}") << -1 << "error"; QTest::newRow("invalid function") << QByteArray("{\"id\":42, \"method\":\"RemoteProxy.Explode\"}") << 42 << "error"; QTest::newRow("invalid namespace") << QByteArray("{\"id\":42, \"method\":\"ProxyRemote.Hello\"}") << 42 << "error"; QTest::newRow("missing dot") << QByteArray("{\"id\":42, \"method\":\"RemoteProxyHello\"}") << 42 << "error"; @@ -1212,98 +1212,98 @@ void RemoteProxyOfflineTests::authenticationReplyConnection() stopServer(); } -//void RemoteProxyOfflineTests::tcpRemoteConnection() -//{ -// // Start the server -// startServer(); +void RemoteProxyOfflineTests::tcpRemoteConnection() +{ + // Start the server + startServer(); -// // Configure mock authenticator -// m_mockAuthenticator->setTimeoutDuration(100); -// m_mockAuthenticator->setExpectedAuthenticationError(); + // Configure mock authenticator + m_mockAuthenticator->setTimeoutDuration(100); + m_mockAuthenticator->setExpectedAuthenticationError(); -// QString nameConnectionOne = "Test client one"; -// QUuid uuidConnectionOne = QUuid::createUuid(); + QString nameConnectionOne = "Test client one"; + QUuid uuidConnectionOne = QUuid::createUuid(); -// QString nameConnectionTwo = "Test client two"; -// QUuid uuidConnectionTwo = QUuid::createUuid(); + QString nameConnectionTwo = "Test client two"; + QUuid uuidConnectionTwo = QUuid::createUuid(); -// QByteArray dataOne = "Hello from client one :-)"; -// QByteArray dataTwo = "Hello from client two :-)"; + QByteArray dataOne = "Hello from client one :-)"; + QByteArray dataTwo = "Hello from client two :-)"; -// // Create two connection -// RemoteProxyConnection *connectionOne = new RemoteProxyConnection(uuidConnectionOne, nameConnectionOne, RemoteProxyConnection::ConnectionTypeTcpSocket, this); -// connect(connectionOne, &RemoteProxyConnection::sslErrors, this, &BaseTest::ignoreConnectionSslError); + // Create two connection + RemoteProxyConnection *connectionOne = new RemoteProxyConnection(uuidConnectionOne, nameConnectionOne, RemoteProxyConnection::ConnectionTypeTcpSocket, this); + connect(connectionOne, &RemoteProxyConnection::sslErrors, this, &BaseTest::ignoreConnectionSslError); -// RemoteProxyConnection *connectionTwo = new RemoteProxyConnection(uuidConnectionTwo, nameConnectionTwo, RemoteProxyConnection::ConnectionTypeTcpSocket, this); -// connect(connectionTwo, &RemoteProxyConnection::sslErrors, this, &BaseTest::ignoreConnectionSslError); + RemoteProxyConnection *connectionTwo = new RemoteProxyConnection(uuidConnectionTwo, nameConnectionTwo, RemoteProxyConnection::ConnectionTypeTcpSocket, this); + connect(connectionTwo, &RemoteProxyConnection::sslErrors, this, &BaseTest::ignoreConnectionSslError); -// // Connect one -// QSignalSpy connectionOneReadySpy(connectionOne, &RemoteProxyConnection::ready); -// QVERIFY(connectionOne->connectServer(m_serverUrlTcp)); -// connectionOneReadySpy.wait(); -// QVERIFY(connectionOneReadySpy.count() == 1); -// QVERIFY(connectionOne->isConnected()); + // Connect one + QSignalSpy connectionOneReadySpy(connectionOne, &RemoteProxyConnection::ready); + QVERIFY(connectionOne->connectServer(m_serverUrlTcp)); + connectionOneReadySpy.wait(); + QVERIFY(connectionOneReadySpy.count() == 1); + QVERIFY(connectionOne->isConnected()); -// // Connect two -// QSignalSpy connectionTwoReadySpy(connectionTwo, &RemoteProxyConnection::ready); -// QVERIFY(connectionTwo->connectServer(m_serverUrlTcp)); -// connectionTwoReadySpy.wait(); -// QVERIFY(connectionTwoReadySpy.count() == 1); -// QVERIFY(connectionTwo->isConnected()); + // Connect two + QSignalSpy connectionTwoReadySpy(connectionTwo, &RemoteProxyConnection::ready); + QVERIFY(connectionTwo->connectServer(m_serverUrlTcp)); + connectionTwoReadySpy.wait(); + QVERIFY(connectionTwoReadySpy.count() == 1); + QVERIFY(connectionTwo->isConnected()); -// // Authenticate one -// QSignalSpy remoteConnectionEstablishedOne(connectionOne, &RemoteProxyConnection::remoteConnectionEstablished); -// QSignalSpy connectionOneAuthenticatedSpy(connectionOne, &RemoteProxyConnection::authenticated); -// QVERIFY(connectionOne->authenticate(m_testToken)); -// connectionOneAuthenticatedSpy.wait(); -// QVERIFY(connectionOneAuthenticatedSpy.count() == 1); -// QVERIFY(connectionOne->isConnected()); -// QVERIFY(connectionOne->isAuthenticated()); -// QVERIFY(connectionOne->state() == RemoteProxyConnection::StateAuthenticated); + // Authenticate one + QSignalSpy remoteConnectionEstablishedOne(connectionOne, &RemoteProxyConnection::remoteConnectionEstablished); + QSignalSpy connectionOneAuthenticatedSpy(connectionOne, &RemoteProxyConnection::authenticated); + QVERIFY(connectionOne->authenticate(m_testToken)); + connectionOneAuthenticatedSpy.wait(); + QVERIFY(connectionOneAuthenticatedSpy.count() == 1); + QVERIFY(connectionOne->isConnected()); + QVERIFY(connectionOne->isAuthenticated()); + QVERIFY(connectionOne->state() == RemoteProxyConnection::StateAuthenticated); -// // Authenticate two -// QSignalSpy remoteConnectionEstablishedTwo(connectionTwo, &RemoteProxyConnection::remoteConnectionEstablished); -// QSignalSpy connectionTwoAuthenticatedSpy(connectionTwo, &RemoteProxyConnection::authenticated); -// QVERIFY(connectionTwo->authenticate(m_testToken)); -// connectionTwoAuthenticatedSpy.wait(); -// qDebug() << connectionTwoAuthenticatedSpy.count(); -// QVERIFY(connectionTwoAuthenticatedSpy.count() == 1); -// QVERIFY(connectionTwo->isConnected()); -// QVERIFY(connectionTwo->isAuthenticated()); + // Authenticate two + QSignalSpy remoteConnectionEstablishedTwo(connectionTwo, &RemoteProxyConnection::remoteConnectionEstablished); + QSignalSpy connectionTwoAuthenticatedSpy(connectionTwo, &RemoteProxyConnection::authenticated); + QVERIFY(connectionTwo->authenticate(m_testToken)); + connectionTwoAuthenticatedSpy.wait(); + qDebug() << connectionTwoAuthenticatedSpy.count(); + QVERIFY(connectionTwoAuthenticatedSpy.count() == 1); + QVERIFY(connectionTwo->isConnected()); + QVERIFY(connectionTwo->isAuthenticated()); -// // Wait for both to be connected -// remoteConnectionEstablishedOne.wait(500); -// remoteConnectionEstablishedTwo.wait(500); + // Wait for both to be connected + remoteConnectionEstablishedOne.wait(500); + remoteConnectionEstablishedTwo.wait(500); -// QVERIFY(remoteConnectionEstablishedOne.count() == 1); -// QVERIFY(remoteConnectionEstablishedTwo.count() == 1); -// QVERIFY(connectionOne->state() == RemoteProxyConnection::StateRemoteConnected); -// QVERIFY(connectionTwo->state() == RemoteProxyConnection::StateRemoteConnected); + QVERIFY(remoteConnectionEstablishedOne.count() == 1); + QVERIFY(remoteConnectionEstablishedTwo.count() == 1); + QVERIFY(connectionOne->state() == RemoteProxyConnection::StateRemoteConnected); + QVERIFY(connectionTwo->state() == RemoteProxyConnection::StateRemoteConnected); -// QCOMPARE(connectionOne->tunnelPartnerName(), nameConnectionTwo); -// QCOMPARE(connectionOne->tunnelPartnerUuid(), uuidConnectionTwo.toString()); -// QCOMPARE(connectionTwo->tunnelPartnerName(), nameConnectionOne); -// QCOMPARE(connectionTwo->tunnelPartnerUuid(), uuidConnectionOne.toString()); + QCOMPARE(connectionOne->tunnelPartnerName(), nameConnectionTwo); + QCOMPARE(connectionOne->tunnelPartnerUuid(), uuidConnectionTwo.toString()); + QCOMPARE(connectionTwo->tunnelPartnerName(), nameConnectionOne); + QCOMPARE(connectionTwo->tunnelPartnerUuid(), uuidConnectionOne.toString()); -// // Pipe data trought the tunnel -// QSignalSpy remoteConnectionDataOne(connectionOne, &RemoteProxyConnection::dataReady); -// QSignalSpy remoteConnectionDataTwo(connectionTwo, &RemoteProxyConnection::dataReady); + // Pipe data trought the tunnel + QSignalSpy remoteConnectionDataOne(connectionOne, &RemoteProxyConnection::dataReady); + QSignalSpy remoteConnectionDataTwo(connectionTwo, &RemoteProxyConnection::dataReady); -// connectionOne->sendData(dataOne); -// remoteConnectionDataTwo.wait(500); -// QVERIFY(remoteConnectionDataTwo.count() == 1); -// QCOMPARE(remoteConnectionDataTwo.at(0).at(0).toByteArray().trimmed(), dataOne); + connectionOne->sendData(dataOne); + remoteConnectionDataTwo.wait(500); + QVERIFY(remoteConnectionDataTwo.count() == 1); + QCOMPARE(remoteConnectionDataTwo.at(0).at(0).toByteArray().trimmed(), dataOne); -// connectionTwo->sendData(dataTwo); -// remoteConnectionDataOne.wait(500); -// QVERIFY(remoteConnectionDataOne.count() == 1); -// QCOMPARE(remoteConnectionDataOne.at(0).at(0).toByteArray().trimmed(), dataTwo); + connectionTwo->sendData(dataTwo); + remoteConnectionDataOne.wait(500); + QVERIFY(remoteConnectionDataOne.count() == 1); + QCOMPARE(remoteConnectionDataOne.at(0).at(0).toByteArray().trimmed(), dataTwo); -// connectionOne->deleteLater(); -// connectionTwo->deleteLater(); + connectionOne->deleteLater(); + connectionTwo->deleteLater(); -// // Clean up -// stopServer(); -//} + // Clean up + stopServer(); +} QTEST_MAIN(RemoteProxyOfflineTests) diff --git a/tests/test-offline/nymea-remoteproxy-tests-offline.h b/tests/test-offline/nymea-remoteproxy-tests-offline.h index e7fe61e..da858e0 100644 --- a/tests/test-offline/nymea-remoteproxy-tests-offline.h +++ b/tests/test-offline/nymea-remoteproxy-tests-offline.h @@ -82,7 +82,7 @@ private slots: void authenticationReplyConnection(); // TCP Websocket combinations - //void tcpRemoteConnection(); + void tcpRemoteConnection(); }; From b91f89fea254824e4915478a598f128a01ba6dc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 12 Jun 2019 20:00:54 +0200 Subject: [PATCH 13/16] Add TCP to Websocket test and new coverage report script --- create-coverage-html.sh | 36 ------- generate-coverage.sh | 12 +++ .../nymea-remoteproxy-tests-offline.cpp | 94 +++++++++++++++++++ .../nymea-remoteproxy-tests-offline.h | 1 + 4 files changed, 107 insertions(+), 36 deletions(-) delete mode 100755 create-coverage-html.sh create mode 100755 generate-coverage.sh diff --git a/create-coverage-html.sh b/create-coverage-html.sh deleted file mode 100755 index a6908b8..0000000 --- a/create-coverage-html.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash - -# Export the library path for now -export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(pwd)/libnymea-remoteproxy:$(pwd)/libnymea-remoteproxyclient - -# Build -qmake CONFIG+=coverage CONFIG+=ccache -make -j$(nproc) -#make test -make coverage-html - -# Clean build -make clean - -# Clean source directory -rm -v Makefile - -rm -v libnymea-remoteproxy/libnymea-remoteproxy.so* -rm -v libnymea-remoteproxy/Makefile - -rm -v libnymea-remoteproxyclient/libnymea-remoteproxyclient.so* -rm -v libnymea-remoteproxyclient/Makefile - -rm -v server/nymea-remoteproxy -rm -v server/Makefile - -rm -v tests/Makefile - -rm -v tests/test-offline/nymea-remoteproxy-tests-offline -rm -v tests/test-offline/Makefile - -#rm -v tests/test-online/nymea-remoteproxy-tests-online -#rm -v tests/test-online/Makefile - -rm -v client/nymea-remoteproxy-client -rm -v client/Makefile diff --git a/generate-coverage.sh b/generate-coverage.sh new file mode 100755 index 0000000..9fd4ac4 --- /dev/null +++ b/generate-coverage.sh @@ -0,0 +1,12 @@ +#!/bin/sh +SCRIPT_DIR=$(dirname $0) +SRC_DIR="$SCRIPT_DIR/../build-nymea-remoteproxy-Desktop-Debug/" +COV_DIR="$SRC_DIR/coverage" +HTML_RESULTS="${COV_DIR}/html" + +# Build code coverage html report +mkdir -p ${HTML_RESULTS} +lcov -d "${SRC_DIR}" -c -o "${COV_DIR}/coverage.info" +lcov -r "${COV_DIR}/coverage.info" "*.h" "*/tests/*" "*.moc" "*moc_*.cpp" "*/test/*" "/usr/include/*" "*/build*/*" "*libnymea-remoteproxy/authentication/aws*" -o "${COV_DIR}/coverage-filtered.info" +genhtml -o "${HTML_RESULTS}" "${COV_DIR}/coverage-filtered.info" +lcov -d "${COV_DIR}" -z diff --git a/tests/test-offline/nymea-remoteproxy-tests-offline.cpp b/tests/test-offline/nymea-remoteproxy-tests-offline.cpp index 5773a92..8d373ab 100644 --- a/tests/test-offline/nymea-remoteproxy-tests-offline.cpp +++ b/tests/test-offline/nymea-remoteproxy-tests-offline.cpp @@ -1306,4 +1306,98 @@ void RemoteProxyOfflineTests::tcpRemoteConnection() stopServer(); } +void RemoteProxyOfflineTests::tcpWebsocketRemoteConnection() +{ + // Start the server + startServer(); + + // Configure mock authenticator + m_mockAuthenticator->setTimeoutDuration(100); + m_mockAuthenticator->setExpectedAuthenticationError(); + + QString nameConnectionOne = "Test client one"; + QUuid uuidConnectionOne = QUuid::createUuid(); + + QString nameConnectionTwo = "Test client two"; + QUuid uuidConnectionTwo = QUuid::createUuid(); + + QByteArray dataOne = "Hello from client one :-)"; + QByteArray dataTwo = "Hello from client two :-)"; + + // Create two connection + RemoteProxyConnection *connectionOne = new RemoteProxyConnection(uuidConnectionOne, nameConnectionOne, RemoteProxyConnection::ConnectionTypeWebSocket, this); + connect(connectionOne, &RemoteProxyConnection::sslErrors, this, &BaseTest::ignoreConnectionSslError); + + RemoteProxyConnection *connectionTwo = new RemoteProxyConnection(uuidConnectionTwo, nameConnectionTwo, RemoteProxyConnection::ConnectionTypeTcpSocket, this); + connect(connectionTwo, &RemoteProxyConnection::sslErrors, this, &BaseTest::ignoreConnectionSslError); + + // Connect one + QSignalSpy connectionOneReadySpy(connectionOne, &RemoteProxyConnection::ready); + QVERIFY(connectionOne->connectServer(m_serverUrl)); + connectionOneReadySpy.wait(); + QVERIFY(connectionOneReadySpy.count() == 1); + QVERIFY(connectionOne->isConnected()); + + // Connect two + QSignalSpy connectionTwoReadySpy(connectionTwo, &RemoteProxyConnection::ready); + QVERIFY(connectionTwo->connectServer(m_serverUrlTcp)); + connectionTwoReadySpy.wait(); + QVERIFY(connectionTwoReadySpy.count() == 1); + QVERIFY(connectionTwo->isConnected()); + + // Authenticate one + QSignalSpy remoteConnectionEstablishedOne(connectionOne, &RemoteProxyConnection::remoteConnectionEstablished); + QSignalSpy connectionOneAuthenticatedSpy(connectionOne, &RemoteProxyConnection::authenticated); + QVERIFY(connectionOne->authenticate(m_testToken)); + connectionOneAuthenticatedSpy.wait(); + QVERIFY(connectionOneAuthenticatedSpy.count() == 1); + QVERIFY(connectionOne->isConnected()); + QVERIFY(connectionOne->isAuthenticated()); + QVERIFY(connectionOne->state() == RemoteProxyConnection::StateAuthenticated); + + // Authenticate two + QSignalSpy remoteConnectionEstablishedTwo(connectionTwo, &RemoteProxyConnection::remoteConnectionEstablished); + QSignalSpy connectionTwoAuthenticatedSpy(connectionTwo, &RemoteProxyConnection::authenticated); + QVERIFY(connectionTwo->authenticate(m_testToken)); + connectionTwoAuthenticatedSpy.wait(); + qDebug() << connectionTwoAuthenticatedSpy.count(); + QVERIFY(connectionTwoAuthenticatedSpy.count() == 1); + QVERIFY(connectionTwo->isConnected()); + QVERIFY(connectionTwo->isAuthenticated()); + + // Wait for both to be connected + remoteConnectionEstablishedOne.wait(500); + remoteConnectionEstablishedTwo.wait(500); + + QVERIFY(remoteConnectionEstablishedOne.count() == 1); + QVERIFY(remoteConnectionEstablishedTwo.count() == 1); + QVERIFY(connectionOne->state() == RemoteProxyConnection::StateRemoteConnected); + QVERIFY(connectionTwo->state() == RemoteProxyConnection::StateRemoteConnected); + + QCOMPARE(connectionOne->tunnelPartnerName(), nameConnectionTwo); + QCOMPARE(connectionOne->tunnelPartnerUuid(), uuidConnectionTwo.toString()); + QCOMPARE(connectionTwo->tunnelPartnerName(), nameConnectionOne); + QCOMPARE(connectionTwo->tunnelPartnerUuid(), uuidConnectionOne.toString()); + + // Pipe data trought the tunnel + QSignalSpy remoteConnectionDataOne(connectionOne, &RemoteProxyConnection::dataReady); + QSignalSpy remoteConnectionDataTwo(connectionTwo, &RemoteProxyConnection::dataReady); + + connectionOne->sendData(dataOne); + remoteConnectionDataTwo.wait(500); + QVERIFY(remoteConnectionDataTwo.count() == 1); + QCOMPARE(remoteConnectionDataTwo.at(0).at(0).toByteArray().trimmed(), dataOne); + + connectionTwo->sendData(dataTwo); + remoteConnectionDataOne.wait(500); + QVERIFY(remoteConnectionDataOne.count() == 1); + QCOMPARE(remoteConnectionDataOne.at(0).at(0).toByteArray().trimmed(), dataTwo); + + connectionOne->deleteLater(); + connectionTwo->deleteLater(); + + // Clean up + stopServer(); +} + QTEST_MAIN(RemoteProxyOfflineTests) diff --git a/tests/test-offline/nymea-remoteproxy-tests-offline.h b/tests/test-offline/nymea-remoteproxy-tests-offline.h index da858e0..dc0a233 100644 --- a/tests/test-offline/nymea-remoteproxy-tests-offline.h +++ b/tests/test-offline/nymea-remoteproxy-tests-offline.h @@ -83,6 +83,7 @@ private slots: // TCP Websocket combinations void tcpRemoteConnection(); + void tcpWebsocketRemoteConnection(); }; From e902f46fd473b6ff22a8ed287148d62736a3bc76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Mon, 24 Jun 2019 08:18:52 +0200 Subject: [PATCH 14/16] Fix inline comments for data buffer size violation and remove uneccesary critical debugs --- .../authentication/authenticationreply.cpp | 2 +- libnymea-remoteproxy/jsonrpcserver.cpp | 4 ++-- libnymea-remoteproxy/proxyclient.cpp | 9 ++------- libnymea-remoteproxy/proxyclient.h | 2 +- tests/testbase/mockauthenticator.cpp | 2 +- 5 files changed, 7 insertions(+), 12 deletions(-) diff --git a/libnymea-remoteproxy/authentication/authenticationreply.cpp b/libnymea-remoteproxy/authentication/authenticationreply.cpp index 79bf9f4..bbbb7af 100644 --- a/libnymea-remoteproxy/authentication/authenticationreply.cpp +++ b/libnymea-remoteproxy/authentication/authenticationreply.cpp @@ -46,7 +46,7 @@ AuthenticationReply::AuthenticationReply(ProxyClient *proxyClient, QObject *pare AuthenticationReply::~AuthenticationReply() { - qCCritical(dcAuthentication()) << "Destroy authentication reply"; + } QPointer AuthenticationReply::proxyClient() const diff --git a/libnymea-remoteproxy/jsonrpcserver.cpp b/libnymea-remoteproxy/jsonrpcserver.cpp index cbb29ce..6753967 100644 --- a/libnymea-remoteproxy/jsonrpcserver.cpp +++ b/libnymea-remoteproxy/jsonrpcserver.cpp @@ -334,9 +334,9 @@ void JsonRpcServer::processData(ProxyClient *proxyClient, const QByteArray &data QList packages = proxyClient->processData(data); // Make sure the buffer size is in range - if (proxyClient->bufferSizeViolation()) { + if (proxyClient->bufferSize() > 1024 * 10) { qCWarning(dcJsonRpc()) << "Data buffer size violation from" << proxyClient; - proxyClient->killConnection("Data buffer size violation. Data >= 10 kB"); + proxyClient->killConnection("Data buffer size violation."); return; } diff --git a/libnymea-remoteproxy/proxyclient.cpp b/libnymea-remoteproxy/proxyclient.cpp index 984fbc5..0fc0bfc 100644 --- a/libnymea-remoteproxy/proxyclient.cpp +++ b/libnymea-remoteproxy/proxyclient.cpp @@ -234,17 +234,12 @@ QList ProxyClient::processData(const QByteArray &data) m_dataBuffers.clear(); } - if (m_dataBuffers.size() > 1024 * 10) { - qCWarning(dcJsonRpc()) << "Client buffer larger than 10KB and no valid data. This is a buffer size violation."; - m_bufferSizeViolation = true; - } - return packages; } -bool ProxyClient::bufferSizeViolation() const +int ProxyClient::bufferSize() const { - return m_bufferSizeViolation; + return m_dataBuffers.size(); } QDebug operator<<(QDebug debug, ProxyClient *proxyClient) diff --git a/libnymea-remoteproxy/proxyclient.h b/libnymea-remoteproxy/proxyclient.h index d79093d..492a429 100644 --- a/libnymea-remoteproxy/proxyclient.h +++ b/libnymea-remoteproxy/proxyclient.h @@ -98,7 +98,7 @@ public: // Json server methods int generateMessageId(); QList processData(const QByteArray &data); - bool bufferSizeViolation() const; + int bufferSize() const; private: TransportInterface *m_interface = nullptr; diff --git a/tests/testbase/mockauthenticator.cpp b/tests/testbase/mockauthenticator.cpp index 06731cf..d66ba56 100644 --- a/tests/testbase/mockauthenticator.cpp +++ b/tests/testbase/mockauthenticator.cpp @@ -85,6 +85,6 @@ MockAuthenticationReply::MockAuthenticationReply(int timeout, Authenticator::Aut MockAuthenticationReply::~MockAuthenticationReply() { - qCCritical(dcAuthentication()) << "Destroy mock authentication reply"; + } From 4c0f6b64018bd68d5d8891efbcc51025853e3929 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Tue, 27 Jul 2021 11:31:50 +0200 Subject: [PATCH 15/16] Fix tcp tests --- tests/testbase/basetest.cpp | 119 ++++++++++++++++++------------------ tests/testbase/basetest.h | 3 + 2 files changed, 63 insertions(+), 59 deletions(-) diff --git a/tests/testbase/basetest.cpp b/tests/testbase/basetest.cpp index 62d54eb..42ecbcc 100644 --- a/tests/testbase/basetest.cpp +++ b/tests/testbase/basetest.cpp @@ -226,6 +226,66 @@ QVariant BaseTest::injectWebSocketData(const QByteArray &data) return QVariant(); } +QVariant BaseTest::invokeTcpSocketApiCall(const QString &method, const QVariantMap params, bool remainsConnected) +{ + Q_UNUSED(remainsConnected) + + QVariantMap request; + request.insert("id", m_commandCounter); + request.insert("method", method); + request.insert("params", params); + QJsonDocument jsonDoc = QJsonDocument::fromVariant(request); + + QSslSocket *socket = new QSslSocket(this); + typedef void (QSslSocket:: *sslErrorsSignal)(const QList &); + QObject::connect(socket, static_cast(&QSslSocket::sslErrors), this, &BaseTest::sslSocketSslErrors); + + QSignalSpy spyConnection(socket, &QSslSocket::connected); + socket->connectToHostEncrypted(Engine::instance()->tcpSocketServer()->serverUrl().host(), + static_cast(Engine::instance()->tcpSocketServer()->serverUrl().port())); + spyConnection.wait(); + if (spyConnection.count() == 0) { + return QVariant(); + } + + QSignalSpy dataSpy(socket, &QSslSocket::readyRead); + socket->write(jsonDoc.toJson(QJsonDocument::Compact) + '\n'); + // FIXME: check why it waits the full time here + dataSpy.wait(500); + if (dataSpy.count() != 1) { + qWarning() << "No data received"; + return QVariant(); + } + + QByteArray data = socket->readAll(); + socket->close(); + socket->deleteLater(); + + // Make sure the response ends with '}\n' + if (!data.endsWith("}\n")) { + qWarning() << "JSON data does not end with \"}\n\""; + return QVariant(); + } + + // Make sure the response it a valid JSON string + QJsonParseError error; + jsonDoc = QJsonDocument::fromJson(data, &error); + if (error.error != QJsonParseError::NoError) { + qWarning() << "JSON parser error" << error.errorString(); + return QVariant(); + } + + QVariantMap response = jsonDoc.toVariant().toMap(); + + if (response.value("id").toInt() == m_commandCounter) { + m_commandCounter++; + return jsonDoc.toVariant(); + } + + m_commandCounter++; + return QVariant(); +} + bool BaseTest::createRemoteConnection(const QString &token, const QString &nonce, QObject *parent) { // Configure mock authenticator @@ -339,65 +399,6 @@ bool BaseTest::createRemoteConnection(const QString &token, const QString &nonce return true; } -void BaseTest::initTestCase() -{ - Q_UNUSED(remainsConnected) - - QVariantMap request; - request.insert("id", m_commandCounter); - request.insert("method", method); - request.insert("params", params); - QJsonDocument jsonDoc = QJsonDocument::fromVariant(request); - - QSslSocket *socket = new QSslSocket(this); - typedef void (QSslSocket:: *sslErrorsSignal)(const QList &); - QObject::connect(socket, static_cast(&QSslSocket::sslErrors), this, &BaseTest::sslSocketSslErrors); - - QSignalSpy spyConnection(socket, &QSslSocket::connected); - socket->connectToHostEncrypted(Engine::instance()->tcpSocketServer()->serverUrl().host(), - static_cast(Engine::instance()->tcpSocketServer()->serverUrl().port())); - spyConnection.wait(); - if (spyConnection.count() == 0) { - return QVariant(); - } - - QSignalSpy dataSpy(socket, &QSslSocket::readyRead); - socket->write(jsonDoc.toJson(QJsonDocument::Compact) + '\n'); - // FIXME: check why it waits the full time here - dataSpy.wait(500); - if (dataSpy.count() != 1) { - qWarning() << "No data received"; - return QVariant(); - } - - QByteArray data = socket->readAll(); - socket->close(); - socket->deleteLater(); - - // Make sure the response ends with '}\n' - if (!data.endsWith("}\n")) { - qWarning() << "JSON data does not end with \"}\n\""; - return QVariant(); - } - - // Make sure the response it a valid JSON string - QJsonParseError error; - jsonDoc = QJsonDocument::fromJson(data, &error); - if (error.error != QJsonParseError::NoError) { - qWarning() << "JSON parser error" << error.errorString(); - return QVariant(); - } - - QVariantMap response = jsonDoc.toVariant().toMap(); - - if (response.value("id").toInt() == m_commandCounter) { - m_commandCounter++; - return jsonDoc.toVariant(); - } - - m_commandCounter++; - return QVariant(); -} QVariant BaseTest::injectTcpSocketData(const QByteArray &data) { diff --git a/tests/testbase/basetest.h b/tests/testbase/basetest.h index a5f04ef..4643272 100644 --- a/tests/testbase/basetest.h +++ b/tests/testbase/basetest.h @@ -82,6 +82,9 @@ protected: QVariant invokeWebSocketApiCall(const QString &method, const QVariantMap params = QVariantMap(), bool remainsConnected = true); QVariant injectWebSocketData(const QByteArray &data); + QVariant invokeTcpSocketApiCall(const QString &method, const QVariantMap params = QVariantMap(), bool remainsConnected = true); + QVariant injectTcpSocketData(const QByteArray &data); + bool createRemoteConnection(const QString &token, const QString &nonce, QObject *parent); protected slots: From a1da55b92f16797fc79e2a12fa803d5c1a90eada Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Tue, 27 Jul 2021 11:49:46 +0200 Subject: [PATCH 16/16] Update install paths and copyright license headers --- client/client.pro | 2 +- client/main.cpp | 2 +- libnymea-remoteproxy/tcpsocketserver.h | 46 ++++++++++-------- .../tcpsocketconnection.cpp | 47 +++++++++++-------- .../tcpsocketconnection.h | 46 ++++++++++-------- monitor/monitor.pro | 2 +- server/main.cpp | 2 +- server/server.pro | 2 +- tests/test-offline/test-offline.pro | 2 +- tests/test-online/test-online.pro | 2 +- 10 files changed, 86 insertions(+), 67 deletions(-) diff --git a/client/client.pro b/client/client.pro index c50791f..4af05f0 100644 --- a/client/client.pro +++ b/client/client.pro @@ -11,7 +11,7 @@ LIBS += -L$$top_builddir/libnymea-remoteproxyclient/ -lnymea-remoteproxyclient SOURCES += main.cpp \ proxyclient.cpp -target.path = /usr/bin +target.path = $$[QT_INSTALL_PREFIX]/bin INSTALLS += target HEADERS += \ diff --git a/client/main.cpp b/client/main.cpp index adfc87f..c7586a6 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -98,7 +98,7 @@ int main(int argc, char *argv[]) "a server application as client perspective.\n\n" "Version: %1\n" "API version: %2\n\n" - "Copyright %3 2018 Simon Stürz \n") + "Copyright %3 2021 nymea GmbH \n") .arg(SERVER_VERSION_STRING) .arg(API_VERSION_STRING) .arg(QChar(0xA9))); diff --git a/libnymea-remoteproxy/tcpsocketserver.h b/libnymea-remoteproxy/tcpsocketserver.h index 5d6af91..82bea88 100644 --- a/libnymea-remoteproxy/tcpsocketserver.h +++ b/libnymea-remoteproxy/tcpsocketserver.h @@ -1,23 +1,29 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * * - * Copyright (C) 2019 Simon Stürz * - * * - * This file is part of nymea-remoteproxy. * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - * * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by copyright law, and +* remains the property of nymea GmbH. All rights, including reproduction, publication, +* editing and translation, are reserved. The use of this project is subject to the terms of a +* license agreement to be concluded with nymea GmbH in accordance with the terms +* of use of nymea GmbH, available under https://nymea.io/license +* +* GNU General Public License Usage +* Alternatively, this project may be redistributed and/or modified under +* the terms of the GNU General Public License as published by the Free Software Foundation, +* GNU version 3. this project is distributed in the hope that it will be useful, but WITHOUT ANY +* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +* PURPOSE. See the GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License along with this project. +* If not, see . +* +* For any further details and any questions please contact us under contact@nymea.io +* or see our FAQ/Licensing Information on https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef TCPSOCKETSERVER_H #define TCPSOCKETSERVER_H diff --git a/libnymea-remoteproxyclient/tcpsocketconnection.cpp b/libnymea-remoteproxyclient/tcpsocketconnection.cpp index 4ad69a3..213573b 100644 --- a/libnymea-remoteproxyclient/tcpsocketconnection.cpp +++ b/libnymea-remoteproxyclient/tcpsocketconnection.cpp @@ -1,25 +1,32 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * * - * Copyright (C) 2019 Simon Stürz * - * * - * This file is part of nymea-remoteproxy. * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - * * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by copyright law, and +* remains the property of nymea GmbH. All rights, including reproduction, publication, +* editing and translation, are reserved. The use of this project is subject to the terms of a +* license agreement to be concluded with nymea GmbH in accordance with the terms +* of use of nymea GmbH, available under https://nymea.io/license +* +* GNU General Public License Usage +* Alternatively, this project may be redistributed and/or modified under +* the terms of the GNU General Public License as published by the Free Software Foundation, +* GNU version 3. this project is distributed in the hope that it will be useful, but WITHOUT ANY +* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +* PURPOSE. See the GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License along with this project. +* If not, see . +* +* For any further details and any questions please contact us under contact@nymea.io +* or see our FAQ/Licensing Information on https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "tcpsocketconnection.h" + Q_LOGGING_CATEGORY(dcRemoteProxyClientTcpSocket, "RemoteProxyClientTcpSocket") namespace remoteproxyclient { diff --git a/libnymea-remoteproxyclient/tcpsocketconnection.h b/libnymea-remoteproxyclient/tcpsocketconnection.h index 934536e..384a8dc 100644 --- a/libnymea-remoteproxyclient/tcpsocketconnection.h +++ b/libnymea-remoteproxyclient/tcpsocketconnection.h @@ -1,23 +1,29 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * * - * Copyright (C) 2019 Simon Stürz * - * * - * This file is part of nymea-remoteproxy. * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - * * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by copyright law, and +* remains the property of nymea GmbH. All rights, including reproduction, publication, +* editing and translation, are reserved. The use of this project is subject to the terms of a +* license agreement to be concluded with nymea GmbH in accordance with the terms +* of use of nymea GmbH, available under https://nymea.io/license +* +* GNU General Public License Usage +* Alternatively, this project may be redistributed and/or modified under +* the terms of the GNU General Public License as published by the Free Software Foundation, +* GNU version 3. this project is distributed in the hope that it will be useful, but WITHOUT ANY +* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +* PURPOSE. See the GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License along with this project. +* If not, see . +* +* For any further details and any questions please contact us under contact@nymea.io +* or see our FAQ/Licensing Information on https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef TCPSOCKETCONNECTION_H #define TCPSOCKETCONNECTION_H diff --git a/monitor/monitor.pro b/monitor/monitor.pro index d455aa8..983cb61 100644 --- a/monitor/monitor.pro +++ b/monitor/monitor.pro @@ -15,5 +15,5 @@ SOURCES += main.cpp \ LIBS += -lncurses -target.path = /usr/bin +target.path = $$[QT_INSTALL_PREFIX]/bin INSTALLS += target diff --git a/server/main.cpp b/server/main.cpp index 0a8a67a..1e95df4 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -116,7 +116,7 @@ int main(int argc, char *argv[]) "registered nymea deamons to establish a tunnel connection.\n\n" "Version: %1\n" "API version: %2\n\n" - "Copyright %3 2018 Simon Stürz \n") + "Copyright %3 2021 nymea GmbH \n") .arg(SERVER_VERSION_STRING) .arg(API_VERSION_STRING) .arg(QChar(0xA9))); diff --git a/server/server.pro b/server/server.pro index eb044bb..2689bdf 100644 --- a/server/server.pro +++ b/server/server.pro @@ -10,7 +10,7 @@ LIBS += -L$$top_builddir/libnymea-remoteproxy/ -lnymea-remoteproxy SOURCES += main.cpp \ remoteproxyserverapplication.cpp -target.path = /usr/bin +target.path = $$[QT_INSTALL_PREFIX]/bin INSTALLS += target HEADERS += \ diff --git a/tests/test-offline/test-offline.pro b/tests/test-offline/test-offline.pro index 51e64f3..c6db854 100644 --- a/tests/test-offline/test-offline.pro +++ b/tests/test-offline/test-offline.pro @@ -10,5 +10,5 @@ HEADERS += nymea-remoteproxy-tests-offline.h SOURCES += nymea-remoteproxy-tests-offline.cpp -target.path = /usr/bin +target.path = $$[QT_INSTALL_PREFIX]/bin INSTALLS += target diff --git a/tests/test-online/test-online.pro b/tests/test-online/test-online.pro index 30c87ab..9f20b41 100644 --- a/tests/test-online/test-online.pro +++ b/tests/test-online/test-online.pro @@ -7,5 +7,5 @@ HEADERS += nymea-remoteproxy-tests-online.h SOURCES += nymea-remoteproxy-tests-online.cpp -target.path = /usr/bin +target.path = $$[QT_INSTALL_PREFIX]/bin INSTALLS += target