From 13572ad219190b4382c0487e9c785c660c9dee78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Mon, 20 Aug 2018 19:53:08 +0200 Subject: [PATCH] Add timing configurations and timeout tests --- .gitignore | 1 + client/proxyclient.cpp | 4 +- client/proxyclient.h | 2 +- create-coverage-html.sh | 7 +- .../authentication/authenticationreply.cpp | 14 +- .../authentication/authenticationreply.h | 2 +- .../jsonrpc/authenticationhandler.cpp | 11 +- libnymea-remoteproxy/jsonrpc/jsonreply.cpp | 3 +- libnymea-remoteproxy/jsonrpcserver.cpp | 23 +- libnymea-remoteproxy/monitorserver.cpp | 10 + libnymea-remoteproxy/proxyclient.cpp | 30 ++- libnymea-remoteproxy/proxyclient.h | 9 +- libnymea-remoteproxy/proxyconfiguration.cpp | 55 +++- libnymea-remoteproxy/proxyconfiguration.h | 17 ++ libnymea-remoteproxy/proxyserver.cpp | 26 +- libnymea-remoteproxy/proxyserver.h | 1 + libnymea-remoteproxy/transportinterface.h | 2 - libnymea-remoteproxy/websocketserver.cpp | 30 +-- libnymea-remoteproxy/websocketserver.h | 5 +- libnymea-remoteproxyclient/proxyconnection.h | 3 + .../remoteproxyconnection.cpp | 78 +++--- .../remoteproxyconnection.h | 29 +-- .../websocketconnection.cpp | 12 +- .../websocketconnection.h | 1 - nymea-remoteproxy.pri | 12 +- nymea-remoteproxy.pro | 1 + tests/resources/resources.qrc | 4 + tests/resources/test-configuration-chain.conf | 18 ++ ...test-configuration-faulty-certificate.conf | 18 ++ .../test-configuration-faulty-chain.conf | 18 ++ .../test-configuration-faulty-key.conf | 18 ++ tests/resources/test-configuration.conf | 4 + .../nymea-remoteproxy-tests-offline.cpp | 237 ++++++++++++++++-- .../nymea-remoteproxy-tests-offline.h | 13 +- .../nymea-remoteproxy-tests-online.cpp | 4 +- tests/test-online/test-online.pro | 2 - tests/testbase/basetest.cpp | 4 +- tests/testbase/basetest.h | 5 +- tests/tests.pro | 8 +- 39 files changed, 552 insertions(+), 189 deletions(-) create mode 100644 tests/resources/test-configuration-chain.conf create mode 100644 tests/resources/test-configuration-faulty-certificate.conf create mode 100644 tests/resources/test-configuration-faulty-chain.conf create mode 100644 tests/resources/test-configuration-faulty-key.conf diff --git a/.gitignore b/.gitignore index ea27b3b..49d79dc 100644 --- a/.gitignore +++ b/.gitignore @@ -78,3 +78,4 @@ tests/test-offline/nymea-remoteproxy-tests-offline tests/test-online/nymea-remoteproxy-tests-online .crossbuilder/ source_repository/ +*.gcno diff --git a/client/proxyclient.cpp b/client/proxyclient.cpp index 20f1d7b..6cb1430 100644 --- a/client/proxyclient.cpp +++ b/client/proxyclient.cpp @@ -51,9 +51,9 @@ void ProxyClient::setPingpong(bool enable) m_pingpong = enable; } -void ProxyClient::onErrorOccured(RemoteProxyConnection::Error error) +void ProxyClient::onErrorOccured(QAbstractSocket::SocketError error) { - qCWarning(dcProxyClient()) << "Error occured" << error << m_connection->errorString(); + qCWarning(dcProxyClient()) << "Error occured" << error; exit(-1); } diff --git a/client/proxyclient.h b/client/proxyclient.h index f2b5160..abf9c58 100644 --- a/client/proxyclient.h +++ b/client/proxyclient.h @@ -50,7 +50,7 @@ private: RemoteProxyConnection *m_connection = nullptr; private slots: - void onErrorOccured(RemoteProxyConnection::Error error); + void onErrorOccured(QAbstractSocket::SocketError error); void onClientReady(); void onAuthenticationFinished(); void onRemoteConnectionEstablished(); diff --git a/create-coverage-html.sh b/create-coverage-html.sh index 7fa639f..cbbc6d1 100755 --- a/create-coverage-html.sh +++ b/create-coverage-html.sh @@ -4,8 +4,9 @@ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(pwd)/libnymea-remoteproxy:$(pwd)/libnymea-remoteproxyclient # Build -qmake CONFIG+=coverage +qmake CONFIG+=coverage CONFIG+=ccache make -j$(nproc) +#make check make coverage-html # Clean build @@ -28,8 +29,8 @@ 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 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/libnymea-remoteproxy/authentication/authenticationreply.cpp b/libnymea-remoteproxy/authentication/authenticationreply.cpp index fd214d6..7d18e64 100644 --- a/libnymea-remoteproxy/authentication/authenticationreply.cpp +++ b/libnymea-remoteproxy/authentication/authenticationreply.cpp @@ -19,6 +19,7 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +#include "engine.h" #include "authenticationreply.h" #include "authentication/authenticator.h" @@ -28,11 +29,12 @@ AuthenticationReply::AuthenticationReply(ProxyClient *proxyClient, QObject *pare QObject(parent), m_proxyClient(proxyClient) { - m_timer = new QTimer(this); - m_timer->setInterval(10000); - m_timer->setSingleShot(true); + m_timer.setSingleShot(true); + connect(&m_timer, &QTimer::timeout, this, &AuthenticationReply::onTimeout); m_process = new QProcess(this); + + m_timer.start(Engine::instance()->configuration()->authenticationTimeout()); } ProxyClient *AuthenticationReply::proxyClient() const @@ -62,7 +64,7 @@ void AuthenticationReply::setError(Authenticator::AuthenticationError error) void AuthenticationReply::setFinished() { - m_timer->stop(); + m_timer.stop(); // emit in next event loop QTimer::singleShot(0, this, &AuthenticationReply::finished); } @@ -71,7 +73,7 @@ void AuthenticationReply::onTimeout() { m_timedOut = true; m_error = Authenticator::AuthenticationErrorTimeout; - m_timer->stop(); + m_timer.stop(); m_process->kill(); QTimer::singleShot(0, this, &AuthenticationReply::finished); } @@ -79,7 +81,7 @@ void AuthenticationReply::onTimeout() void AuthenticationReply::abort() { m_error = Authenticator::AuthenticationErrorAborted; - m_timer->stop(); + m_timer.stop(); m_process->kill(); QTimer::singleShot(0, this, &AuthenticationReply::finished); } diff --git a/libnymea-remoteproxy/authentication/authenticationreply.h b/libnymea-remoteproxy/authentication/authenticationreply.h index dea353a..82b724b 100644 --- a/libnymea-remoteproxy/authentication/authenticationreply.h +++ b/libnymea-remoteproxy/authentication/authenticationreply.h @@ -48,7 +48,7 @@ public: private: explicit AuthenticationReply(ProxyClient *proxyClient, QObject *parent = nullptr); ProxyClient *m_proxyClient = nullptr; - QTimer *m_timer = nullptr; + QTimer m_timer; QProcess *m_process = nullptr; bool m_timedOut = false; diff --git a/libnymea-remoteproxy/jsonrpc/authenticationhandler.cpp b/libnymea-remoteproxy/jsonrpc/authenticationhandler.cpp index de9d7e8..1d53af3 100644 --- a/libnymea-remoteproxy/jsonrpc/authenticationhandler.cpp +++ b/libnymea-remoteproxy/jsonrpc/authenticationhandler.cpp @@ -78,11 +78,9 @@ JsonReply *AuthenticationHandler::Authenticate(const QVariantMap ¶ms, ProxyC void AuthenticationHandler::onAuthenticationFinished() { AuthenticationReply *authenticationReply = static_cast(sender()); - JsonReply *jsonReply = m_runningAuthentications.take(authenticationReply); - - authenticationReply->deleteLater(); qCDebug(dcJsonRpc()) << "Authentication respons ready for" << authenticationReply->proxyClient() << authenticationReply->error(); + JsonReply *jsonReply = m_runningAuthentications.take(authenticationReply); if (authenticationReply->error() != Authenticator::AuthenticationErrorNoError) { qCWarning(dcJsonRpc()) << "Authentication error occured" << authenticationReply->error(); @@ -91,12 +89,13 @@ void AuthenticationHandler::onAuthenticationFinished() // Successfully authenticated jsonReply->setSuccess(true); } - - jsonReply->setData(errorToReply(authenticationReply->error())); - jsonReply->finished(); // Set client authenticated authenticationReply->proxyClient()->setAuthenticated(authenticationReply->error() == Authenticator::AuthenticationErrorNoError); + authenticationReply->deleteLater(); + + jsonReply->setData(errorToReply(authenticationReply->error())); + jsonReply->finished(); } } diff --git a/libnymea-remoteproxy/jsonrpc/jsonreply.cpp b/libnymea-remoteproxy/jsonrpc/jsonreply.cpp index 2de98fe..4b1222d 100644 --- a/libnymea-remoteproxy/jsonrpc/jsonreply.cpp +++ b/libnymea-remoteproxy/jsonrpc/jsonreply.cpp @@ -19,6 +19,7 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +#include "engine.h" #include "jsonreply.h" namespace remoteproxy { @@ -95,7 +96,7 @@ bool JsonReply::timedOut() const void JsonReply::startWait() { - m_timeout.start(5000); + m_timeout.start(Engine::instance()->configuration()->jsonRpcTimeout()); } void JsonReply::timeout() diff --git a/libnymea-remoteproxy/jsonrpcserver.cpp b/libnymea-remoteproxy/jsonrpcserver.cpp index ca4534c..2d8d33f 100644 --- a/libnymea-remoteproxy/jsonrpcserver.cpp +++ b/libnymea-remoteproxy/jsonrpcserver.cpp @@ -130,7 +130,7 @@ void JsonRpcServer::sendResponse(ProxyClient *client, int commandId, const QVari QByteArray data = QJsonDocument::fromVariant(response).toJson(QJsonDocument::Compact); qCDebug(dcJsonRpcTraffic()) << "Sending data:" << data; - client->interface()->sendData(client->clientId(), data); + client->sendData(data); } void JsonRpcServer::sendErrorResponse(ProxyClient *client, int commandId, const QString &error) @@ -176,10 +176,11 @@ void JsonRpcServer::setup() void JsonRpcServer::asyncReplyFinished() { JsonReply *reply = static_cast(sender()); - ProxyClient *proxyClient = m_asyncReplies.take(reply); - reply->deleteLater(); + ProxyClient *proxyClient = m_asyncReplies.take(reply); + 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"; return; @@ -197,7 +198,7 @@ void JsonRpcServer::asyncReplyFinished() } else { sendErrorResponse(proxyClient, reply->commandId(), "Command timed out"); // Disconnect this client since he requested something that created a timeout - proxyClient->interface()->killClientConnection(proxyClient->clientId(), "API call timeouted."); + proxyClient->killConnection("API call timeouted."); } } @@ -235,7 +236,7 @@ void JsonRpcServer::processData(ProxyClient *proxyClient, const QByteArray &data 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->interface()->killClientConnection(proxyClient->clientId(), "Invalid JSON data received."); + proxyClient->killConnection("Invalid JSON data received."); return; } @@ -246,7 +247,7 @@ void JsonRpcServer::processData(ProxyClient *proxyClient, const QByteArray &data if (!success) { qCWarning(dcJsonRpc()) << "Error parsing command. Missing \"id\":" << message; sendErrorResponse(proxyClient, -1, "Error parsing command. Missing 'id'"); - proxyClient->interface()->killClientConnection(proxyClient->clientId(), "The id property is missing in the request."); + proxyClient->killConnection("The id property is missing in the request."); return; } @@ -254,7 +255,7 @@ void JsonRpcServer::processData(ProxyClient *proxyClient, const QByteArray &data 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->interface()->killClientConnection(proxyClient->clientId(), "Invalid method passed."); + proxyClient->killConnection("Invalid method passed."); return; } @@ -264,22 +265,21 @@ void JsonRpcServer::processData(ProxyClient *proxyClient, const QByteArray &data JsonHandler *handler = m_handlers.value(targetNamespace); if (!handler) { sendErrorResponse(proxyClient, commandId, "No such namespace"); - proxyClient->interface()->killClientConnection(proxyClient->clientId(), "No such namespace."); + proxyClient->killConnection("No such namespace."); return; } if (!handler->hasMethod(method)) { sendErrorResponse(proxyClient, commandId, "No such method"); - proxyClient->interface()->killClientConnection(proxyClient->clientId(), "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->interface()->killClientConnection(proxyClient->clientId(), "Invalid params passed."); + proxyClient->killConnection("Invalid params passed."); return; } @@ -297,6 +297,7 @@ void JsonRpcServer::processData(ProxyClient *proxyClient, const QByteArray &data reply->setClientId(proxyClient->clientId()); reply->setCommandId(commandId); sendResponse(proxyClient, commandId, reply->data()); + reply->deleteLater(); } } diff --git a/libnymea-remoteproxy/monitorserver.cpp b/libnymea-remoteproxy/monitorserver.cpp index d6428b9..ffbeed8 100644 --- a/libnymea-remoteproxy/monitorserver.cpp +++ b/libnymea-remoteproxy/monitorserver.cpp @@ -23,6 +23,7 @@ #include "monitorserver.h" #include "loggingcategories.h" +#include #include namespace remoteproxy { @@ -87,6 +88,8 @@ void MonitorServer::onMonitorConnected() connect(clientConnection, &QLocalSocket::readyRead, this, &MonitorServer::onMonitorDisconnected); m_clients.append(clientConnection); + qCDebug(dcMonitorServer()) << "New monitor connected."; + if (!m_timer->isActive()) { // Send the data right the way onTimeout(); @@ -105,6 +108,13 @@ void MonitorServer::onMonitorDisconnected() void MonitorServer::startServer() { qCDebug(dcMonitorServer()) << "Starting server on" << m_serverName; + + // Make sure the monitor can be created + if (QFile::exists(m_serverName)) { + qCDebug(dcMonitorServer()) << "Clean up old monitor socket"; + QFile::remove(m_serverName); + } + m_server = new QLocalServer(this); if (!m_server->listen(m_serverName)) { qCWarning(dcMonitorServer()) << "Could not start local server for monitor on" << m_serverName << m_server->errorString(); diff --git a/libnymea-remoteproxy/proxyclient.cpp b/libnymea-remoteproxy/proxyclient.cpp index bf660e7..33673c0 100644 --- a/libnymea-remoteproxy/proxyclient.cpp +++ b/libnymea-remoteproxy/proxyclient.cpp @@ -19,6 +19,7 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +#include "engine.h" #include "proxyclient.h" #include @@ -32,6 +33,10 @@ ProxyClient::ProxyClient(TransportInterface *interface, const QUuid &clientId, c m_peerAddress(address) { 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()); } QUuid ProxyClient::clientId() const @@ -61,9 +66,10 @@ bool ProxyClient::isAuthenticated() const void ProxyClient::setAuthenticated(bool isAuthenticated) { - // TODO: start the timeout counter and disconnect if no tunnel established m_authenticated = isAuthenticated; - if (m_authenticated){ + if (m_authenticated) { + m_timer.stop(); + m_timer.start(Engine::instance()->configuration()->aloneTimeout()); emit authenticated(); } } @@ -75,9 +81,9 @@ bool ProxyClient::isTunnelConnected() const void ProxyClient::setTunnelConnected(bool isTunnelConnected) { - // TODO: reset the timeout counter and disconnect if no tunnel established m_tunnelConnected = isTunnelConnected; - if (m_tunnelConnected){ + if (m_tunnelConnected) { + m_timer.stop(); emit tunnelConnected(); } } @@ -117,6 +123,22 @@ void ProxyClient::setToken(const QString &token) m_token = token; } +void ProxyClient::sendData(const QByteArray &data) +{ + if (!m_interface) + return; + + m_interface->sendData(m_clientId, data); +} + +void ProxyClient::killConnection(const QString &reason) +{ + if (!m_interface) + return; + + m_interface->killClientConnection(m_clientId, reason); +} + QDebug operator<<(QDebug debug, ProxyClient *proxyClient) { debug.nospace() << "ProxyClient(" << proxyClient->interface()->serverName(); diff --git a/libnymea-remoteproxy/proxyclient.h b/libnymea-remoteproxy/proxyclient.h index 3fd7d6f..9042127 100644 --- a/libnymea-remoteproxy/proxyclient.h +++ b/libnymea-remoteproxy/proxyclient.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include "transportinterface.h" @@ -62,11 +63,16 @@ public: QString token() const; void setToken(const QString &token); + // Actions for this client + void sendData(const QByteArray &data); + void killConnection(const QString &reason); + private: TransportInterface *m_interface = nullptr; + QTimer m_timer; + QUuid m_clientId; QHostAddress m_peerAddress; - uint m_creationTimeStamp = 0; bool m_authenticated = false; @@ -79,6 +85,7 @@ private: signals: void authenticated(); void tunnelConnected(); + void timeoutOccured(); }; diff --git a/libnymea-remoteproxy/proxyconfiguration.cpp b/libnymea-remoteproxy/proxyconfiguration.cpp index 93e2a6a..fb10c5a 100644 --- a/libnymea-remoteproxy/proxyconfiguration.cpp +++ b/libnymea-remoteproxy/proxyconfiguration.cpp @@ -44,11 +44,6 @@ bool ProxyConfiguration::loadConfiguration(const QString &fileName) return false; } - if (!fileInfo.isReadable()) { - qCWarning(dcApplication()) << "Configuration: Cannot read configuration file" << m_fileName; - return false; - } - QSettings settings(m_fileName, QSettings::IniFormat); settings.beginGroup("ProxyServer"); @@ -56,6 +51,12 @@ bool ProxyConfiguration::loadConfiguration(const QString &fileName) setWriteLogFile(settings.value("writeLogs", false).toBool()); setLogFileName(settings.value("logFile", "/var/log/nymea-remoteproxy.log").toString()); setMonitorSocketFileName(settings.value("monitorSocket", "/tmp/nymea-remoteproxy.monitor").toString()); + + setJsonRpcTimeout(settings.value("jsonRpcTimeout", 10000).toInt()); + setAuthenticationTimeout(settings.value("authenticationTimeout", 5000).toInt()); + setInactiveTimeout(settings.value("inactiveTimeout", 5000).toInt()); + setAloneTimeout(settings.value("aloneTimeout", 5000).toInt()); + settings.endGroup(); settings.beginGroup("SSL"); @@ -164,6 +165,46 @@ void ProxyConfiguration::setMonitorSocketFileName(const QString &fileName) m_monitorSocketFileName = fileName; } +int ProxyConfiguration::jsonRpcTimeout() const +{ + return m_jsonRpcTimeout; +} + +void ProxyConfiguration::setJsonRpcTimeout(int timeout) +{ + m_jsonRpcTimeout = timeout; +} + +int ProxyConfiguration::authenticationTimeout() const +{ + return m_authenticationTimeout; +} + +void ProxyConfiguration::setAuthenticationTimeout(int timeout) +{ + m_authenticationTimeout = timeout; +} + +int ProxyConfiguration::inactiveTimeout() const +{ + return m_inactiveTimeout; +} + +void ProxyConfiguration::setInactiveTimeout(int timeout) +{ + m_inactiveTimeout = timeout; +} + +int ProxyConfiguration::aloneTimeout() const +{ + return m_aloneTimeout; +} + +void ProxyConfiguration::setAloneTimeout(int timeout) +{ + m_aloneTimeout = timeout; +} + QString ProxyConfiguration::sslCertificateFileName() const { return m_sslCertificateFileName; @@ -247,6 +288,10 @@ QDebug operator<<(QDebug debug, ProxyConfiguration *configuration) debug.nospace() << " - Server name:" << configuration->serverName() << endl; debug.nospace() << " - Write logfile:" << configuration->writeLogFile() << endl; debug.nospace() << " - Logfile:" << configuration->logFileName() << endl; + debug.nospace() << " - JSON RPC timeout:" << configuration->jsonRpcTimeout() << " [ms]" << endl; + debug.nospace() << " - Authentication timeout:" << configuration->authenticationTimeout() << " [ms]" << endl; + debug.nospace() << " - Inactive timeout:" << configuration->inactiveTimeout() << " [ms]" << endl; + debug.nospace() << " - Alone timeout:" << configuration->aloneTimeout() << " [ms]" << endl; debug.nospace() << "SSL configuration" << endl; debug.nospace() << " - Certificate:" << configuration->sslCertificateFileName() << endl; debug.nospace() << " - Certificate key:" << configuration->sslCertificateKeyFileName() << endl; diff --git a/libnymea-remoteproxy/proxyconfiguration.h b/libnymea-remoteproxy/proxyconfiguration.h index c90d3d7..4b7b9a0 100644 --- a/libnymea-remoteproxy/proxyconfiguration.h +++ b/libnymea-remoteproxy/proxyconfiguration.h @@ -52,6 +52,18 @@ public: QString monitorSocketFileName() const; void setMonitorSocketFileName(const QString &fileName); + int jsonRpcTimeout() const; + void setJsonRpcTimeout(int timeout); + + int authenticationTimeout() const; + void setAuthenticationTimeout(int timeout); + + int inactiveTimeout() const; + void setInactiveTimeout(int timeout); + + int aloneTimeout() const; + void setAloneTimeout(int timeout); + // Ssl QString sslCertificateFileName() const; void setSslCertificateFileName(const QString &fileName); @@ -86,6 +98,11 @@ private: QString m_logFileName = "/var/log/nymea-remoteproxy.log"; QString m_monitorSocketFileName; + int m_jsonRpcTimeout = 10000; + int m_authenticationTimeout = 5000; + int m_inactiveTimeout = 5000; + int m_aloneTimeout = 5000; + // Ssl QString m_sslCertificateFileName = "/etc/ssl/certs/ssl-cert-snakeoil.pem"; QString m_sslCertificateKeyFileName = "/etc/ssl/private/ssl-cert-snakeoil.key"; diff --git a/libnymea-remoteproxy/proxyserver.cpp b/libnymea-remoteproxy/proxyserver.cpp index 485e050..8b39ec6 100644 --- a/libnymea-remoteproxy/proxyserver.cpp +++ b/libnymea-remoteproxy/proxyserver.cpp @@ -149,6 +149,7 @@ void ProxyServer::onClientConnected(const QUuid &clientId, const QHostAddress &a ProxyClient *proxyClient = new ProxyClient(interface, clientId, address, this); connect(proxyClient, &ProxyClient::authenticated, this, &ProxyServer::onProxyClientAuthenticated); connect(proxyClient, &ProxyClient::tunnelConnected, this, &ProxyServer::onProxyClientTunnelConnected); + connect(proxyClient, &ProxyClient::timeoutOccured, this, &ProxyServer::onProxyClientTimeoutOccured); m_proxyClients.insert(clientId, proxyClient); m_jsonRpcServer->registerClient(proxyClient); @@ -176,7 +177,7 @@ void ProxyServer::onClientDisconnected(const QUuid &clientId) ProxyClient *remoteClient = getRemoteClient(proxyClient); m_tunnels.remove(proxyClient->token()); if (remoteClient) { - remoteClient->interface()->killClientConnection(remoteClient->clientId(), "Tunnel client disconnected"); + remoteClient->killConnection("Tunnel client disconnected"); } } @@ -207,27 +208,21 @@ void ProxyServer::onClientDataAvailable(const QUuid &clientId, const QByteArray qCWarning(dcProxyServer()) << "An authenticated client sent data without tunnel connection. This is not allowed."; m_jsonRpcServer->unregisterClient(proxyClient); // The client is authenticated and tries to send data, this is not allowed. - proxyClient->interface()->killClientConnection(proxyClient->clientId(), "Data received while authenticated but not remote connected."); + proxyClient->killConnection("Data received while authenticated but not remote connected."); return; } if (proxyClient->isAuthenticated() && proxyClient->isTunnelConnected()) { - // Check if there is realy a tunnel for this client - if (!m_tunnels.contains(proxyClient->token())) { - // FIXME: kill all clients, something went wrong - } - ProxyClient *remoteClient = getRemoteClient(proxyClient); - if (!remoteClient) { - // FIXME: kill all clients, something went wrong - } + Q_ASSERT_X(remoteClient, "ProxyServer", "Tunnel existing but not tunnel client available"); + Q_ASSERT_X(m_tunnels.contains(proxyClient->token()), "ProxyServer", "Tunnel connect but not existing"); qCDebug(dcProxyServerTraffic()) << "Pipe data:"; qCDebug(dcProxyServerTraffic()) << " --> from" << proxyClient; qCDebug(dcProxyServerTraffic()) << " --> to" << remoteClient; qCDebug(dcProxyServerTraffic()) << " --> data:" << qUtf8Printable(data); - remoteClient->interface()->sendData(remoteClient->clientId(), data); + remoteClient->sendData(data); } } @@ -244,7 +239,7 @@ void ProxyServer::onProxyClientAuthenticated() // Note: remove the authenticated token, so the current tunnel will not interrupted. proxyClient->setToken(QString()); proxyClient->setAuthenticated(false); - proxyClient->interface()->killClientConnection(proxyClient->clientId(), "There is already an established tunnel with this token."); + proxyClient->killConnection("There is already an established tunnel with this token."); return; } @@ -264,6 +259,13 @@ void ProxyServer::onProxyClientTunnelConnected() } +void ProxyServer::onProxyClientTimeoutOccured() +{ + ProxyClient *proxyClient = static_cast(sender()); + qCDebug(dcProxyServer()) << "Timeout occured for" << proxyClient; + proxyClient->killConnection("Proxy timeout occuret"); +} + void ProxyServer::startServer() { qCDebug(dcProxyServer()) << "Start proxy server."; diff --git a/libnymea-remoteproxy/proxyserver.h b/libnymea-remoteproxy/proxyserver.h index 166baa0..d90674c 100644 --- a/libnymea-remoteproxy/proxyserver.h +++ b/libnymea-remoteproxy/proxyserver.h @@ -77,6 +77,7 @@ private slots: void onProxyClientAuthenticated(); void onProxyClientTunnelConnected(); + void onProxyClientTimeoutOccured(); public slots: void startServer(); diff --git a/libnymea-remoteproxy/transportinterface.h b/libnymea-remoteproxy/transportinterface.h index b17dfac..6bbce12 100644 --- a/libnymea-remoteproxy/transportinterface.h +++ b/libnymea-remoteproxy/transportinterface.h @@ -37,8 +37,6 @@ public: QString serverName() const; virtual void sendData(const QUuid &clientId, const QByteArray &data) = 0; - virtual void sendData(const QList &clients, const QByteArray &data) = 0; - virtual void killClientConnection(const QUuid &clientId, const QString &killReason) = 0; signals: diff --git a/libnymea-remoteproxy/websocketserver.cpp b/libnymea-remoteproxy/websocketserver.cpp index c73f1ff..a8ded67 100644 --- a/libnymea-remoteproxy/websocketserver.cpp +++ b/libnymea-remoteproxy/websocketserver.cpp @@ -73,20 +73,13 @@ void WebSocketServer::sendData(const QUuid &clientId, const QByteArray &data) } } -void WebSocketServer::sendData(const QList &clients, const QByteArray &data) -{ - foreach (const QUuid &client, clients) { - sendData(client, data); - } -} - void WebSocketServer::killClientConnection(const QUuid &clientId, const QString &killReason) { QWebSocket *client = m_clientList.value(clientId); if (!client) return; - qCWarning(dcWebSocketServer()) << "Kill client connection" << clientId.toString() << "Reason:" << killReason; + qCWarning(dcWebSocketServer()) << "Killing client connection" << clientId.toString() << "Reason:" << killReason; client->close(QWebSocketProtocol::CloseCodeBadOperation, killReason); } @@ -110,7 +103,6 @@ void WebSocketServer::onClientConnected() // Append the new client to the client list m_clientList.insert(clientId, client); - connect(client, SIGNAL(pong(quint64,QByteArray)), this, SLOT(onPing(quint64,QByteArray))); connect(client, SIGNAL(binaryMessageReceived(QByteArray)), this, SLOT(onBinaryMessageReceived(QByteArray))); connect(client, SIGNAL(textMessageReceived(QString)), this, SLOT(onTextMessageReceived(QString))); connect(client, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onClientError(QAbstractSocket::SocketError))); @@ -130,12 +122,6 @@ void WebSocketServer::onClientDisconnected() emit clientDisconnected(clientId); } -void WebSocketServer::onBinaryMessageReceived(const QByteArray &data) -{ - QWebSocket *client = static_cast(sender()); - qCDebug(dcWebSocketServerTraffic()) << "<-- Binary message from" << client->peerAddress().toString() << ":" << data; -} - void WebSocketServer::onTextMessageReceived(const QString &message) { QWebSocket *client = static_cast(sender()); @@ -143,6 +129,14 @@ void WebSocketServer::onTextMessageReceived(const QString &message) emit dataAvailable(m_clientList.key(client), message.toUtf8()); } +void WebSocketServer::onBinaryMessageReceived(const QByteArray &data) +{ + QWebSocket *client = static_cast(sender()); + 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."); +} + void WebSocketServer::onClientError(QAbstractSocket::SocketError error) { QWebSocket *client = static_cast(sender()); @@ -154,12 +148,6 @@ void WebSocketServer::onServerError(QAbstractSocket::SocketError error) qCWarning(dcWebSocketServer()) << "Server error occured:" << error << m_server->errorString(); } -void WebSocketServer::onPing(quint64 elapsedTime, const QByteArray &payload) -{ - QWebSocket *client = static_cast(sender()); - qCDebug(dcWebSocketServer) << "ping response" << client->peerAddress() << elapsedTime << payload; -} - bool WebSocketServer::startServer() { m_server = new QWebSocketServer(QCoreApplication::applicationName(), QWebSocketServer::SecureMode, this); diff --git a/libnymea-remoteproxy/websocketserver.h b/libnymea-remoteproxy/websocketserver.h index 7b01359..03013f6 100644 --- a/libnymea-remoteproxy/websocketserver.h +++ b/libnymea-remoteproxy/websocketserver.h @@ -48,8 +48,6 @@ public: QSslConfiguration sslConfiguration() const; void sendData(const QUuid &clientId, const QByteArray &data) override; - void sendData(const QList &clients, const QByteArray &data) override; - void killClientConnection(const QUuid &clientId, const QString &killReason) override; private: @@ -63,11 +61,10 @@ private: private slots: void onClientConnected(); void onClientDisconnected(); - void onBinaryMessageReceived(const QByteArray &data); void onTextMessageReceived(const QString &message); + void onBinaryMessageReceived(const QByteArray &data); void onClientError(QAbstractSocket::SocketError error); void onServerError(QAbstractSocket::SocketError error); - void onPing(quint64 elapsedTime, const QByteArray & payload); public slots: bool startServer() override; diff --git a/libnymea-remoteproxyclient/proxyconnection.h b/libnymea-remoteproxyclient/proxyconnection.h index f71de17..60252c9 100644 --- a/libnymea-remoteproxyclient/proxyconnection.h +++ b/libnymea-remoteproxyclient/proxyconnection.h @@ -53,7 +53,10 @@ protected: signals: void connectedChanged(bool connected); void dataReceived(const QByteArray &data); + + void stateChanged(QAbstractSocket::SocketState state); void errorOccured(QAbstractSocket::SocketError error); + void sslErrors(const QList &errors); public slots: diff --git a/libnymea-remoteproxyclient/remoteproxyconnection.cpp b/libnymea-remoteproxyclient/remoteproxyconnection.cpp index d70d773..3dbe25e 100644 --- a/libnymea-remoteproxyclient/remoteproxyconnection.cpp +++ b/libnymea-remoteproxyclient/remoteproxyconnection.cpp @@ -47,38 +47,11 @@ RemoteProxyConnection::State RemoteProxyConnection::state() const return m_state; } -RemoteProxyConnection::Error RemoteProxyConnection::error() const +QAbstractSocket::SocketError RemoteProxyConnection::error() const { return m_error; } -QString RemoteProxyConnection::errorString() const -{ - QString errorString; - switch (m_error) { - case ErrorNoError: - errorString = ""; - break; - case ErrorHostNotFound: - errorString = "The proxy host url could not be resolved."; - break; - case ErrorSocketError: - errorString = "Socket connection error occured."; - break; - case ErrorSslError: - errorString = "SSL error occured."; - break; - case ErrorProxyNotResponding: - errorString = "The proxy server does not respond."; - break; - case ErrorProxyAuthenticationFailed: - errorString = "The authentication on the proxy server failed."; - break; - } - - return errorString; -} - void RemoteProxyConnection::ignoreSslErrors() { m_connection->ignoreSslErrors(); @@ -95,13 +68,13 @@ bool RemoteProxyConnection::isConnected() const || m_state == StateInitializing || m_state == StateReady || m_state == StateAuthenticating - || m_state == StateWaitTunnel + || m_state == StateAuthenticated || m_state == StateRemoteConnected; } bool RemoteProxyConnection::isAuthenticated() const { - return m_state == StateWaitTunnel || m_state == StateRemoteConnected; + return m_state == StateAuthenticated || m_state == StateRemoteConnected; } bool RemoteProxyConnection::isRemoteConnected() const @@ -197,13 +170,13 @@ void RemoteProxyConnection::setState(RemoteProxyConnection::State state) } -void RemoteProxyConnection::setError(RemoteProxyConnection::Error error) +void RemoteProxyConnection::setError(QAbstractSocket::SocketError error) { if (m_error == error) return; m_error = error; - qCDebug(dcRemoteProxyClientConnection()) << "Error occured" << m_error << errorString(); + qCDebug(dcRemoteProxyClientConnection()) << "Error occured" << m_error; emit errorOccured(m_error); } @@ -226,12 +199,13 @@ void RemoteProxyConnection::onConnectionChanged(bool isConnected) void RemoteProxyConnection::onConnectionDataAvailable(const QByteArray &data) { switch (m_state) { + case StateHostLookup: case StateConnecting: case StateConnected: case StateInitializing: case StateReady: case StateAuthenticating: - case StateWaitTunnel: + case StateAuthenticated: m_jsonClient->processData(data); break; case StateRemoteConnected: @@ -239,15 +213,38 @@ void RemoteProxyConnection::onConnectionDataAvailable(const QByteArray &data) emit dataReady(data); break; default: - qCWarning(dcRemoteProxyClientConnection()) << "Unhandled: Data reviced" << data; + qCWarning(dcRemoteProxyClientConnection()) << "Unhandled: Data received" << data; break; } } void RemoteProxyConnection::onConnectionSocketError(QAbstractSocket::SocketError error) { - emit socketErrorOccured(error); - setError(ErrorSocketError); + setError(error); +} + +void RemoteProxyConnection::onConnectionStateChanged(QAbstractSocket::SocketState state) +{ + switch (state) { + case QAbstractSocket::UnconnectedState: + setState(StateDisconnected); + break; + case QAbstractSocket::HostLookupState: + setState(StateHostLookup); + break; + case QAbstractSocket::ConnectingState: + setState(StateConnecting); + break; + case QAbstractSocket::ConnectedState: + setState(StateConnected); + break; + case QAbstractSocket::ClosingState: + setState(StateDiconnecting); + break; + case QAbstractSocket::BoundState: + case QAbstractSocket::ListeningState: + break; + } } void RemoteProxyConnection::onHelloFinished() @@ -287,11 +284,11 @@ void RemoteProxyConnection::onAuthenticateFinished() QVariantMap responseParams = response.value("params").toMap(); if (responseParams.value("authenticationError").toString() != "AuthenticationErrorNoError") { qCWarning(dcRemoteProxyClientConnection()) << "Authentication request finished with error" << responseParams.value("authenticationError").toString(); - setError(RemoteProxyConnection::ErrorProxyAuthenticationFailed); + setError(QAbstractSocket::ProxyConnectionRefusedError); m_connection->disconnectServer(); } else { qCDebug(dcRemoteProxyClientConnection()) << "Successfully authenticated."; - setState(StateWaitTunnel); + setState(StateAuthenticated); emit authenticated(); } } @@ -314,7 +311,7 @@ bool RemoteProxyConnection::connectServer(const QUrl &url) m_serverUrl = url; m_connectionType = ConnectionTypeWebSocket; - m_error = ErrorNoError; + m_error = QAbstractSocket::UnknownSocketError; cleanUp(); @@ -324,13 +321,14 @@ bool RemoteProxyConnection::connectServer(const QUrl &url) break; case ConnectionTypeTcpSocket: // FIXME: - m_connection = qobject_cast(new WebSocketConnection(this)); + //m_connection = qobject_cast(new WebSocketConnection(this)); break; } connect(m_connection, &ProxyConnection::connectedChanged, this, &RemoteProxyConnection::onConnectionChanged); connect(m_connection, &ProxyConnection::dataReceived, this, &RemoteProxyConnection::onConnectionDataAvailable); connect(m_connection, &ProxyConnection::errorOccured, this, &RemoteProxyConnection::onConnectionSocketError); + connect(m_connection, &ProxyConnection::stateChanged, this, &RemoteProxyConnection::onConnectionStateChanged); connect(m_connection, &ProxyConnection::sslErrors, this, &RemoteProxyConnection::sslErrors); m_jsonClient = new JsonRpcClient(m_connection, this); diff --git a/libnymea-remoteproxyclient/remoteproxyconnection.h b/libnymea-remoteproxyclient/remoteproxyconnection.h index 972db89..f47e966 100644 --- a/libnymea-remoteproxyclient/remoteproxyconnection.h +++ b/libnymea-remoteproxyclient/remoteproxyconnection.h @@ -49,33 +49,24 @@ public: Q_ENUM(ConnectionType) enum State { + StateHostLookup, StateConnecting, StateConnected, StateInitializing, StateReady, StateAuthenticating, - StateWaitTunnel, + StateAuthenticated, StateRemoteConnected, - StateDisconnected + StateDiconnecting, + StateDisconnected, }; Q_ENUM(State) - enum Error { - ErrorNoError, - ErrorHostNotFound, - ErrorSocketError, - ErrorSslError, - ErrorProxyNotResponding, - ErrorProxyAuthenticationFailed - }; - Q_ENUM(Error) - explicit RemoteProxyConnection(const QUuid &clientUuid, const QString &clientName, QObject *parent = nullptr); ~RemoteProxyConnection(); RemoteProxyConnection::State state() const; - RemoteProxyConnection::Error error() const; - QString errorString() const; + QAbstractSocket::SocketError error() const; void ignoreSslErrors(); void ignoreSslErrors(const QList &errors); @@ -104,7 +95,7 @@ private: QUrl m_serverUrl; State m_state = StateDisconnected; - Error m_error = ErrorNoError; + QAbstractSocket::SocketError m_error = QAbstractSocket::UnknownSocketError; bool m_insecureConnection = false; bool m_remoteConnected = false; @@ -125,7 +116,7 @@ private: void cleanUp(); void setState(State state); - void setError(Error error); + void setError(QAbstractSocket::SocketError error); signals: void connected(); @@ -135,8 +126,7 @@ signals: void disconnected(); void stateChanged(RemoteProxyConnection::State state); - void errorOccured(RemoteProxyConnection::Error error); - void socketErrorOccured(QAbstractSocket::SocketError error); + void errorOccured(QAbstractSocket::SocketError error); void sslErrors(const QList &errors); void dataReady(const QByteArray &data); @@ -145,6 +135,7 @@ private slots: void onConnectionChanged(bool isConnected); void onConnectionDataAvailable(const QByteArray &data); void onConnectionSocketError(QAbstractSocket::SocketError error); + void onConnectionStateChanged(QAbstractSocket::SocketState state); void onHelloFinished(); void onAuthenticateFinished(); @@ -155,12 +146,10 @@ public slots: bool authenticate(const QString &token); void disconnectServer(); bool sendData(const QByteArray &data); - }; } -Q_DECLARE_METATYPE(remoteproxyclient::RemoteProxyConnection::Error); Q_DECLARE_METATYPE(remoteproxyclient::RemoteProxyConnection::State); Q_DECLARE_METATYPE(remoteproxyclient::RemoteProxyConnection::ConnectionType); diff --git a/libnymea-remoteproxyclient/websocketconnection.cpp b/libnymea-remoteproxyclient/websocketconnection.cpp index 157c85d..f984a9c 100644 --- a/libnymea-remoteproxyclient/websocketconnection.cpp +++ b/libnymea-remoteproxyclient/websocketconnection.cpp @@ -32,7 +32,6 @@ WebSocketConnection::WebSocketConnection(QObject *parent) : connect(m_webSocket, &QWebSocket::disconnected, this, &WebSocketConnection::onDisconnected); connect(m_webSocket, &QWebSocket::textMessageReceived, this, &WebSocketConnection::onTextMessageReceived); - connect(m_webSocket, &QWebSocket::binaryMessageReceived, this, &WebSocketConnection::onBinaryMessageReceived); connect(m_webSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError))); connect(m_webSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onStateChanged(QAbstractSocket::SocketState))); @@ -41,7 +40,7 @@ WebSocketConnection::WebSocketConnection(QObject *parent) : WebSocketConnection::~WebSocketConnection() { - + m_webSocket->close(); } QUrl WebSocketConnection::serverUrl() const @@ -67,7 +66,7 @@ void WebSocketConnection::ignoreSslErrors(const QList &errors) void WebSocketConnection::onDisconnected() { qCDebug(dcRemoteProxyClientWebSocket()) << "Disconnected from" << m_webSocket->requestUrl().toString() << m_webSocket->closeReason(); - emit connectedChanged(false); + setConnected(false); } void WebSocketConnection::onError(QAbstractSocket::SocketError error) @@ -89,6 +88,8 @@ void WebSocketConnection::onStateChanged(QAbstractSocket::SocketState state) setConnected(false); break; } + + emit stateChanged(state); } void WebSocketConnection::onTextMessageReceived(const QString &message) @@ -96,11 +97,6 @@ void WebSocketConnection::onTextMessageReceived(const QString &message) emit dataReceived(message.toUtf8()); } -void WebSocketConnection::onBinaryMessageReceived(const QByteArray &message) -{ - Q_UNUSED(message) -} - void WebSocketConnection::connectServer(const QUrl &serverUrl) { if (connected()) { diff --git a/libnymea-remoteproxyclient/websocketconnection.h b/libnymea-remoteproxyclient/websocketconnection.h index 31b9186..50b8b08 100644 --- a/libnymea-remoteproxyclient/websocketconnection.h +++ b/libnymea-remoteproxyclient/websocketconnection.h @@ -56,7 +56,6 @@ private slots: void onError(QAbstractSocket::SocketError error); void onStateChanged(QAbstractSocket::SocketState state); void onTextMessageReceived(const QString &message); - void onBinaryMessageReceived(const QByteArray &message); public slots: void connectServer(const QUrl &serverUrl) override; diff --git a/nymea-remoteproxy.pri b/nymea-remoteproxy.pri index 28dcf02..11a8db2 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=1 -SERVER_VERSION=0.1.0 +SERVER_VERSION=0.1.1 DEFINES += SERVER_NAME_STRING=\\\"$${SERVER_NAME}\\\" \ SERVER_VERSION_STRING=\\\"$${SERVER_VERSION}\\\" \ @@ -19,6 +19,10 @@ QMAKE_LFLAGS *= -std=c++11 top_srcdir=$$PWD top_builddir=$$shadowed($$PWD) +ccache { + QMAKE_CXX = ccache g++ +} + coverage { # Note: this works only if you build in the source dir OBJECTS_DIR = @@ -44,7 +48,7 @@ coverage { coverage-html.depends = clean-gcda check generate-coverage-html generate-coverage-html.commands = \ - "@echo Collecting coverage data"; \ + "echo 'Collecting coverage data'"; \ "lcov --directory $${top_srcdir} --capture --output-file coverage.info --no-checksum --compat-libtool"; \ "lcov --extract coverage.info \"*/server/*.cpp\" --extract coverage.info \"*/libnymea-remoteproxy/*.cpp\" --extract coverage.info \"*/libnymea-remoteproxyclient/*.cpp\" -o coverage.info"; \ "lcov --remove coverage.info \"moc_*.cpp\" --remove coverage.info \"*/test/*\" -o coverage.info"; \ @@ -52,17 +56,19 @@ coverage { clean-coverage-html.depends = clean-gcda clean-coverage-html.commands = \ + "echo 'Clean html coverage report'"; \ "lcov --directory $${top_srcdir} -z"; \ "rm -rf coverage.info coverage-html" coverage-gcovr.depends = clean-gcda check generate-coverage-gcovr generate-coverage-gcovr.commands = \ - "@echo Generating coverage GCOVR report"; \ + "echo 'Generating coverage GCOVR report'"; \ "gcovr -x -r $${top_srcdir} -o $${top_srcdir}/coverage.xml -e \".*/moc_.*\" -e \"tests/.*\" -e \".*\\.h\"" clean-coverage-gcovr.depends = clean-gcda clean-coverage-gcovr.commands = \ + "echo 'Clean coverage report'"; \ "rm -rf $${top_srcdir}/coverage.xml" QMAKE_CLEAN += *.gcda *.gcno coverage.info coverage.xml diff --git a/nymea-remoteproxy.pro b/nymea-remoteproxy.pro index 2857702..4a98a89 100644 --- a/nymea-remoteproxy.pro +++ b/nymea-remoteproxy.pro @@ -13,3 +13,4 @@ message("----------------------------------------------------------") message("JSON-RPC API version $${API_VERSION_MAJOR}.$${API_VERSION_MINOR}") message("Qt version:" $$[QT_VERSION]) coverage { message("Building with coverage report") } +ccache { message("Building with ccache support") } diff --git a/tests/resources/resources.qrc b/tests/resources/resources.qrc index 3e2f3fd..8dde421 100644 --- a/tests/resources/resources.qrc +++ b/tests/resources/resources.qrc @@ -3,5 +3,9 @@ test-certificate.crt test-certificate.key test-configuration.conf + test-configuration-chain.conf + test-configuration-faulty-certificate.conf + test-configuration-faulty-key.conf + test-configuration-faulty-chain.conf diff --git a/tests/resources/test-configuration-chain.conf b/tests/resources/test-configuration-chain.conf new file mode 100644 index 0000000..74dd11e --- /dev/null +++ b/tests/resources/test-configuration-chain.conf @@ -0,0 +1,18 @@ +[ProxyServer] +name=test-nymea-remoteproxy +writeLogs=false +logFile=/var/log/nymea-remoteproxy-faulty.log +monitorSocket=/tmp/nymea-remoteproxy-test.sock + +[SSL] +certificate=:/test-certificate.crt +certificateKey=:/test-certificate.key +certificateChain=:/test-certificate.crt + +[WebSocketServer] +host=127.0.0.1 +port=1212 + +[TcpServer] +host=127.0.0.1 +port=1213 diff --git a/tests/resources/test-configuration-faulty-certificate.conf b/tests/resources/test-configuration-faulty-certificate.conf new file mode 100644 index 0000000..526fe03 --- /dev/null +++ b/tests/resources/test-configuration-faulty-certificate.conf @@ -0,0 +1,18 @@ +[ProxyServer] +name=test-nymea-remoteproxy-faulty-certificate +writeLogs=false +logFile=/var/log/nymea-remoteproxy-faulty.log +monitorSocket=/tmp/nymea-remoteproxy-test.sock + +[SSL] +certificate=:/not-existing-test-certificate.crt +certificateKey=:/test-certificate.key +certificateChain= + +[WebSocketServer] +host=127.0.0.1 +port=1212 + +[TcpServer] +host=127.0.0.1 +port=1213 diff --git a/tests/resources/test-configuration-faulty-chain.conf b/tests/resources/test-configuration-faulty-chain.conf new file mode 100644 index 0000000..5c949e7 --- /dev/null +++ b/tests/resources/test-configuration-faulty-chain.conf @@ -0,0 +1,18 @@ +[ProxyServer] +name=test-nymea-remoteproxy +writeLogs=false +logFile=/var/log/nymea-remoteproxy.log +monitorSocket=/tmp/nymea-remoteproxy-test.sock + +[SSL] +certificate=:/test-certificate.crt +certificateKey=:/test-certificate.key +certificateChain=:/not-existing-test-certificate.crt + +[WebSocketServer] +host=127.0.0.1 +port=1212 + +[TcpServer] +host=127.0.0.1 +port=1213 diff --git a/tests/resources/test-configuration-faulty-key.conf b/tests/resources/test-configuration-faulty-key.conf new file mode 100644 index 0000000..ae4307f --- /dev/null +++ b/tests/resources/test-configuration-faulty-key.conf @@ -0,0 +1,18 @@ +[ProxyServer] +name=test-nymea-remoteproxy-faulty-certificate +writeLogs=false +logFile=/var/log/nymea-remoteproxy-faulty.log +monitorSocket=/tmp/nymea-remoteproxy-test.sock + +[SSL] +certificate=:/test-certificate.crt +certificateKey=:/not-existing-test-certificate.key +certificateChain= + +[WebSocketServer] +host=127.0.0.1 +port=1212 + +[TcpServer] +host=127.0.0.1 +port=1213 diff --git a/tests/resources/test-configuration.conf b/tests/resources/test-configuration.conf index 9351534..e0fb21e 100644 --- a/tests/resources/test-configuration.conf +++ b/tests/resources/test-configuration.conf @@ -3,6 +3,10 @@ name=test-nymea-remoteproxy writeLogs=false logFile=/var/log/nymea-remoteproxy.log monitorSocket=/tmp/nymea-remoteproxy-test.sock +jsonRpcTimeout=1000 +authenticationTimeout=1500 +inactiveTimeout=1500 +aloneTimeout=1000 [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 f9e8515..bb83a06 100644 --- a/tests/test-offline/nymea-remoteproxy-tests-offline.cpp +++ b/tests/test-offline/nymea-remoteproxy-tests-offline.cpp @@ -73,16 +73,61 @@ void RemoteProxyOfflineTests::dummyAuthenticator() cleanUpEngine(); } + + void RemoteProxyOfflineTests::monitorServer() { + // Start the server startServer(); + QVERIFY(Engine::instance()->monitorServer()->running()); + QLocalSocket *monitor = new QLocalSocket(this); + QSignalSpy connectedSpy(monitor, &QLocalSocket::connected); + monitor->connectToServer(m_configuration->monitorSocketFileName()); + connectedSpy.wait(200); + QVERIFY(connectedSpy.count() == 1); + QSignalSpy dataSpy(monitor, &QLocalSocket::readyRead); + dataSpy.wait(); + QVERIFY(dataSpy.count() == 1); + QByteArray data = monitor->readAll(); + qDebug() << data; + + // TODO: verify monitor data + + QSignalSpy disconnectedSpy(monitor, &QLocalSocket::connected); + monitor->disconnectFromServer(); + disconnectedSpy.wait(200); + + // Clean up + monitor->deleteLater(); stopServer(); } -void RemoteProxyOfflineTests::webserverConnectionBlocked() +void RemoteProxyOfflineTests::configuration_data() +{ + QTest::addColumn("fileName"); + QTest::addColumn("success"); + + QTest::newRow("valid configuration") << ":/test-configuration.conf" << true; + QTest::newRow("valid configuration chain") << ":/test-configuration-chain.conf" << true; + QTest::newRow("faulty filename") << ":/not-exisitng-test-configuration.conf" << false; + QTest::newRow("faulty certificate") << ":/test-configuration-faulty-certificate.conf" << false; + QTest::newRow("faulty key") << ":/test-configuration-faulty-key.conf" << false; + QTest::newRow("faulty chain") << ":/test-configuration-faulty-chain.conf" << false; +} + +void RemoteProxyOfflineTests::configuration() +{ + QFETCH(QString, fileName); + QFETCH(bool, success); + + ProxyConfiguration configuration; + QCOMPARE(configuration.loadConfiguration(fileName), success); +} + +void RemoteProxyOfflineTests::serverPortBlocked() { cleanUpEngine(); @@ -117,6 +162,52 @@ void RemoteProxyOfflineTests::webserverConnectionBlocked() stopServer(); } +void RemoteProxyOfflineTests::websocketBinaryData() +{ + // Start the server + startServer(); + + QWebSocket *client = new QWebSocket("bad-client", QWebSocketProtocol::Version13); + connect(client, &QWebSocket::sslErrors, this, &BaseTest::sslErrors); + QSignalSpy spyConnection(client, SIGNAL(connected())); + client->open(Engine::instance()->webSocketServer()->serverUrl()); + spyConnection.wait(); + + // Send binary data and make sure the server disconnects this socket + QSignalSpy spyDisconnected(client, SIGNAL(disconnected())); + client->sendBinaryMessage("trying to upload stuff...stuff...more stuff... other stuff"); + spyConnection.wait(200); + QVERIFY(spyConnection.count() == 1); + + // Clean up + stopServer(); +} + +void RemoteProxyOfflineTests::websocketPing() +{ + // Start the server + startServer(); + + QWebSocket *client = new QWebSocket("bad-client", QWebSocketProtocol::Version13); + connect(client, &QWebSocket::sslErrors, this, &BaseTest::sslErrors); + QSignalSpy spyConnection(client, SIGNAL(connected())); + client->open(Engine::instance()->webSocketServer()->serverUrl()); + spyConnection.wait(); + QVERIFY(spyConnection.count() == 1); + + QByteArray pingMessage = QByteArray("ping!"); + QSignalSpy pongSpy(client, &QWebSocket::pong); + client->ping(pingMessage); + pongSpy.wait(); + QVERIFY(pongSpy.count() == 1); + qDebug() << pongSpy.at(0).at(0) << pongSpy.at(0).at(1); + + QVERIFY(pongSpy.at(0).at(1).toByteArray() == pingMessage); + + // Clean up + stopServer(); +} + void RemoteProxyOfflineTests::getIntrospect() { // Start the server @@ -134,9 +225,15 @@ void RemoteProxyOfflineTests::getHello() // Start the server startServer(); - QVariant response = invokeApiCall("RemoteProxy.Hello"); + QVariantMap response = invokeApiCall("RemoteProxy.Hello").toMap(); qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); + // Verify data + 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(); } @@ -144,30 +241,34 @@ void RemoteProxyOfflineTests::getHello() void RemoteProxyOfflineTests::apiBasicCalls_data() { QTest::addColumn("data"); - QTest::addColumn("valid"); + QTest::addColumn("responseId"); + QTest::addColumn("responseStatus"); - QTest::newRow("valid call") << QByteArray("{\"id\":42, \"method\":\"RemoteProxy.Hello\"}") << true; - QTest::newRow("missing id") << QByteArray("{\"method\":\"RemoteProxy.Hello\"}")<< false; - QTest::newRow("missing method") << QByteArray("{\"id\":42}") << false; - QTest::newRow("borked") << QByteArray("{\"id\":42, \"method\":\"RemoteProx")<< false; - QTest::newRow("invalid function") << QByteArray("{\"id\":42, \"method\":\"RemoteProxy.Explode\"}") << false; - QTest::newRow("invalid namespace") << QByteArray("{\"id\":42, \"method\":\"ProxyRemote.Hello\"}") << false; - QTest::newRow("missing dot") << QByteArray("{\"id\":42, \"method\":\"RemoteProxyHello\"}") << false; - QTest::newRow("invalid params") << QByteArray("{\"id\":42, \"method\":\"RemoteProxy.Hello\", \"params\":{\"törööö\":\"chooo-chooo\"}}") << false; + 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::apiBasicCalls() { QFETCH(QByteArray, data); - QFETCH(bool, valid); + QFETCH(int, responseId); + QFETCH(QString, responseStatus); // Start the server startServer(); QVariant response = injectSocketData(data); - if (valid) { - QVERIFY2(response.toMap().value("status").toString() == "success", "Call wasn't parsed correctly by nymea."); - } + 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(); @@ -249,7 +350,7 @@ void RemoteProxyOfflineTests::clientConnection() QVERIFY(connection->isConnected()); QVERIFY(!connection->isRemoteConnected()); QVERIFY(connection->state() == RemoteProxyConnection::StateReady); - QVERIFY(connection->error() == RemoteProxyConnection::ErrorNoError); + QVERIFY(connection->error() == QAbstractSocket::UnknownSocketError); QVERIFY(connection->serverUrl() == m_serverUrl); QVERIFY(connection->connectionType() == RemoteProxyConnection::ConnectionTypeWebSocket); QVERIFY(connection->serverName() == SERVER_NAME_STRING); @@ -263,15 +364,15 @@ void RemoteProxyOfflineTests::clientConnection() QVERIFY(authenticatedSpy.count() == 1); QVERIFY(connection->isConnected()); QVERIFY(connection->isAuthenticated()); - QVERIFY(connection->state() == RemoteProxyConnection::StateWaitTunnel); + 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(100); + spyDisconnected.wait(500); - QVERIFY(spyDisconnected.count() == 1); + QVERIFY(spyDisconnected.count() >= 1); QVERIFY(!connection->isConnected()); connection->deleteLater(); @@ -326,7 +427,7 @@ void RemoteProxyOfflineTests::remoteConnection() QVERIFY(connectionOneAuthenticatedSpy.count() == 1); QVERIFY(connectionOne->isConnected()); QVERIFY(connectionOne->isAuthenticated()); - QVERIFY(connectionOne->state() == RemoteProxyConnection::StateWaitTunnel); + QVERIFY(connectionOne->state() == RemoteProxyConnection::StateAuthenticated); // Authenticate two QSignalSpy remoteConnectionEstablishedTwo(connectionTwo, &RemoteProxyConnection::remoteConnectionEstablished); @@ -420,7 +521,7 @@ void RemoteProxyOfflineTests::trippleConnection() QVERIFY(connectionOneAuthenticatedSpy.count() == 1); QVERIFY(connectionOne->isConnected()); QVERIFY(connectionOne->isAuthenticated()); - QVERIFY(connectionOne->state() == RemoteProxyConnection::StateWaitTunnel); + QVERIFY(connectionOne->state() == RemoteProxyConnection::StateAuthenticated); QSignalSpy remoteConnectionEstablishedOne(connectionOne, &RemoteProxyConnection::remoteConnectionEstablished); @@ -463,7 +564,8 @@ void RemoteProxyOfflineTests::sslConfigurations() QVERIFY(connector->connectServer(m_serverUrl)); spyError.wait(); QVERIFY(spyError.count() == 1); - QVERIFY(connector->error() == RemoteProxyConnection::ErrorSocketError); + qDebug() << connector->error(); + QCOMPARE(connector->error(), QAbstractSocket::SslHandshakeFailedError); QVERIFY(connector->state() == RemoteProxyConnection::StateDisconnected); // Connect to server (insecue enabled) @@ -482,14 +584,14 @@ void RemoteProxyOfflineTests::sslConfigurations() stopServer(); } -void RemoteProxyOfflineTests::timeout() +void RemoteProxyOfflineTests::jsonRpcTimeout() { // Start the server startServer(); - // Configure result + // Configure result (authentication takes longer than json rpc timeout m_mockAuthenticator->setExpectedAuthenticationError(); - m_mockAuthenticator->setTimeoutDuration(6000); + m_mockAuthenticator->setTimeoutDuration(4000); // Create request QVariantMap params; @@ -500,6 +602,91 @@ void RemoteProxyOfflineTests::timeout() QVariant response = invokeApiCall("Authentication.Authenticate", params); qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); + QVERIFY(response.toMap().value("status").toString() == "error"); + + // Clean up + stopServer(); +} + +void RemoteProxyOfflineTests::inactiveTimeout() +{ + // Start the server + startServer(); + + RemoteProxyConnection *connection = new RemoteProxyConnection(QUuid::createUuid(), "Sleepy test client", this); + connect(connection, &RemoteProxyConnection::sslErrors, this, &BaseTest::ignoreConnectionSslError); + + // Connect one + QSignalSpy connectionReadySpy(connection, &RemoteProxyConnection::ready); + QSignalSpy connectionDisconnectedSpy(connection, &RemoteProxyConnection::disconnected); + QVERIFY(connection->connectServer(m_serverUrl)); + connectionReadySpy.wait(); + QVERIFY(connectionReadySpy.count() == 1); + + // Now wait for disconnected + connectionDisconnectedSpy.wait(); + QVERIFY(connectionDisconnectedSpy.count() >= 1); + + // Clean up + stopServer(); +} + +void RemoteProxyOfflineTests::authenticationReplyTimeout() +{ + // Start the server + startServer(); + + // Configure result (authentication takes longer than json rpc timeout + m_mockAuthenticator->setExpectedAuthenticationError(); + m_mockAuthenticator->setTimeoutDuration(1000); + + m_configuration->setAuthenticationTimeout(500); + m_configuration->setJsonRpcTimeout(1000); + m_configuration->setInactiveTimeout(1000); + + // Create request + QVariantMap params; + params.insert("uuid", QUuid::createUuid().toString()); + params.insert("name", "Sleepy test client"); + params.insert("token", "sleepy token zzzZZZ"); + + QVariant response = invokeApiCall("Authentication.Authenticate", params); + qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); + verifyAuthenticationError(response, Authenticator::AuthenticationErrorTimeout); + + // Clean up + stopServer(); +} + +void RemoteProxyOfflineTests::authenticationReplyConnection() +{ + // Start the server + startServer(); + + RemoteProxyConnection *connection = new RemoteProxyConnection(QUuid::createUuid(), "Sleepy test client", this); + connect(connection, &RemoteProxyConnection::sslErrors, this, &BaseTest::ignoreConnectionSslError); + + // Connect one + QSignalSpy connectionReadySpy(connection, &RemoteProxyConnection::ready); + QVERIFY(connection->connectServer(m_serverUrl)); + connectionReadySpy.wait(); + QVERIFY(connectionReadySpy.count() == 1); + + // Configure result (authentication takes longer than json rpc timeout + m_mockAuthenticator->setExpectedAuthenticationError(); + m_mockAuthenticator->setTimeoutDuration(1000); + + m_configuration->setAuthenticationTimeout(500); + m_configuration->setJsonRpcTimeout(1000); + m_configuration->setInactiveTimeout(1000); + + QSignalSpy connectionErrorSpy(connection, &RemoteProxyConnection::errorOccured); + connection->authenticate("blub"); + connectionErrorSpy.wait(); + QVERIFY(connectionErrorSpy.count() == 1); + QVERIFY(connectionErrorSpy.at(0).at(0).toInt() == static_cast(QAbstractSocket::ProxyConnectionRefusedError)); + QVERIFY(connection->error() == QAbstractSocket::ProxyConnectionRefusedError); + // Clean up stopServer(); } diff --git a/tests/test-offline/nymea-remoteproxy-tests-offline.h b/tests/test-offline/nymea-remoteproxy-tests-offline.h index 96170e7..f996e70 100644 --- a/tests/test-offline/nymea-remoteproxy-tests-offline.h +++ b/tests/test-offline/nymea-remoteproxy-tests-offline.h @@ -40,8 +40,13 @@ private slots: void dummyAuthenticator(); void monitorServer(); + void configuration_data(); + void configuration(); + // WebSocket connection - void webserverConnectionBlocked(); + void serverPortBlocked(); + void websocketBinaryData(); + void websocketPing(); // Api void getIntrospect(); @@ -58,7 +63,11 @@ private slots: void remoteConnection(); void trippleConnection(); void sslConfigurations(); - void timeout(); + + void jsonRpcTimeout(); + void inactiveTimeout(); + void authenticationReplyTimeout(); + void authenticationReplyConnection(); }; diff --git a/tests/test-online/nymea-remoteproxy-tests-online.cpp b/tests/test-online/nymea-remoteproxy-tests-online.cpp index 87d23f1..d05cf5c 100644 --- a/tests/test-online/nymea-remoteproxy-tests-online.cpp +++ b/tests/test-online/nymea-remoteproxy-tests-online.cpp @@ -34,7 +34,6 @@ RemoteProxyOnlineTests::RemoteProxyOnlineTests(QObject *parent) : BaseTest(parent) { m_authenticator = qobject_cast(m_awsAuthenticator); - } void RemoteProxyOnlineTests::awsStaticCredentials() @@ -53,7 +52,6 @@ void RemoteProxyOnlineTests::awsStaticCredentials() QVERIFY(connection->isConnected()); QVERIFY(!connection->isRemoteConnected()); QVERIFY(connection->state() == RemoteProxyConnection::StateReady); - QVERIFY(connection->error() == RemoteProxyConnection::ErrorNoError); QVERIFY(connection->serverUrl() == m_serverUrl); QVERIFY(connection->connectionType() == RemoteProxyConnection::ConnectionTypeWebSocket); QVERIFY(connection->serverName() == SERVER_NAME_STRING); @@ -67,7 +65,7 @@ void RemoteProxyOnlineTests::awsStaticCredentials() QVERIFY(connection->authenticate("foobar")); errorSpy.wait(); QVERIFY(errorSpy.count() == 1); - QVERIFY(connection->error() == RemoteProxyConnection::ErrorProxyAuthenticationFailed); + QVERIFY(connection->error() == QAbstractSocket::ProxyAuthenticationRequiredError); // Disconnect and clean up connection->disconnectServer(); diff --git a/tests/test-online/test-online.pro b/tests/test-online/test-online.pro index 501f3c0..30c87ab 100644 --- a/tests/test-online/test-online.pro +++ b/tests/test-online/test-online.pro @@ -1,8 +1,6 @@ include(../../nymea-remoteproxy.pri) include(../testbase/testbase.pri) - - TARGET = nymea-remoteproxy-tests-online HEADERS += nymea-remoteproxy-tests-online.h diff --git a/tests/testbase/basetest.cpp b/tests/testbase/basetest.cpp index 55f4a02..d4d250a 100644 --- a/tests/testbase/basetest.cpp +++ b/tests/testbase/basetest.cpp @@ -97,12 +97,13 @@ void BaseTest::startServer() QSignalSpy runningSpy(Engine::instance(), &Engine::runningChanged); Engine::instance()->setDeveloperModeEnabled(true); Engine::instance()->start(m_configuration); - runningSpy.wait(); + runningSpy.wait(100); QVERIFY(runningSpy.count() == 1); } QVERIFY(Engine::instance()->running()); QVERIFY(Engine::instance()->webSocketServer()->running()); + QVERIFY(Engine::instance()->monitorServer()->running()); } void BaseTest::stopServer() @@ -198,7 +199,6 @@ QVariant BaseTest::injectSocketData(const QByteArray &data) void BaseTest::initTestCase() { - qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); diff --git a/tests/testbase/basetest.h b/tests/testbase/basetest.h index 8d104e5..e2ff7ea 100644 --- a/tests/testbase/basetest.h +++ b/tests/testbase/basetest.h @@ -80,13 +80,14 @@ protected slots: void cleanupTestCase(); public slots: - void sslErrors(const QList &) { + inline void sslErrors(const QList &) { QWebSocket *socket = static_cast(sender()); socket->ignoreSslErrors(); } - void ignoreConnectionSslError(const QList &) { + inline void ignoreConnectionSslError(const QList &errors) { RemoteProxyConnection *connection = static_cast(sender()); + connection->ignoreSslErrors(errors); connection->ignoreSslErrors(); } diff --git a/tests/tests.pro b/tests/tests.pro index 9b5b4fe..b1c1bc0 100644 --- a/tests/tests.pro +++ b/tests/tests.pro @@ -1,4 +1,10 @@ +include(../nymea-remoteproxy.pri) + TEMPLATE=subdirs -SUBDIRS += test-offline test-online +SUBDIRS += test-offline +online { + message("Online tests enabled") + SUBDIRS += test-online +}