Add timing configurations and timeout tests

This commit is contained in:
Simon Stürz 2018-08-20 19:53:08 +02:00
parent f28ac0f4e3
commit 13572ad219
39 changed files with 552 additions and 189 deletions

1
.gitignore vendored
View File

@ -78,3 +78,4 @@ tests/test-offline/nymea-remoteproxy-tests-offline
tests/test-online/nymea-remoteproxy-tests-online
.crossbuilder/
source_repository/
*.gcno

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -78,11 +78,9 @@ JsonReply *AuthenticationHandler::Authenticate(const QVariantMap &params, ProxyC
void AuthenticationHandler::onAuthenticationFinished()
{
AuthenticationReply *authenticationReply = static_cast<AuthenticationReply *>(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();
}
}

View File

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

View File

@ -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<JsonReply *>(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<bool, QString> 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();
}
}

View File

@ -23,6 +23,7 @@
#include "monitorserver.h"
#include "loggingcategories.h"
#include <QFile>
#include <QJsonDocument>
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();

View File

@ -19,6 +19,7 @@
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "engine.h"
#include "proxyclient.h"
#include <QDateTime>
@ -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();

View File

@ -25,6 +25,7 @@
#include <QUuid>
#include <QDebug>
#include <QObject>
#include <QTimer>
#include <QHostAddress>
#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();
};

View File

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

View File

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

View File

@ -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<ProxyClient *>(sender());
qCDebug(dcProxyServer()) << "Timeout occured for" << proxyClient;
proxyClient->killConnection("Proxy timeout occuret");
}
void ProxyServer::startServer()
{
qCDebug(dcProxyServer()) << "Start proxy server.";

View File

@ -77,6 +77,7 @@ private slots:
void onProxyClientAuthenticated();
void onProxyClientTunnelConnected();
void onProxyClientTimeoutOccured();
public slots:
void startServer();

View File

@ -37,8 +37,6 @@ public:
QString serverName() const;
virtual void sendData(const QUuid &clientId, const QByteArray &data) = 0;
virtual void sendData(const QList<QUuid> &clients, const QByteArray &data) = 0;
virtual void killClientConnection(const QUuid &clientId, const QString &killReason) = 0;
signals:

View File

@ -73,20 +73,13 @@ void WebSocketServer::sendData(const QUuid &clientId, const QByteArray &data)
}
}
void WebSocketServer::sendData(const QList<QUuid> &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<QWebSocket *>(sender());
qCDebug(dcWebSocketServerTraffic()) << "<-- Binary message from" << client->peerAddress().toString() << ":" << data;
}
void WebSocketServer::onTextMessageReceived(const QString &message)
{
QWebSocket *client = static_cast<QWebSocket *>(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<QWebSocket *>(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<QWebSocket *>(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<QWebSocket *>(sender());
qCDebug(dcWebSocketServer) << "ping response" << client->peerAddress() << elapsedTime << payload;
}
bool WebSocketServer::startServer()
{
m_server = new QWebSocketServer(QCoreApplication::applicationName(), QWebSocketServer::SecureMode, this);

View File

@ -48,8 +48,6 @@ public:
QSslConfiguration sslConfiguration() const;
void sendData(const QUuid &clientId, const QByteArray &data) override;
void sendData(const QList<QUuid> &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;

View File

@ -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<QSslError> &errors);
public slots:

View File

@ -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<ProxyConnection *>(new WebSocketConnection(this));
//m_connection = qobject_cast<ProxyConnection *>(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);

View File

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

View File

@ -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<QSslError> &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()) {

View File

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

View File

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

View File

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

View File

@ -3,5 +3,9 @@
<file>test-certificate.crt</file>
<file>test-certificate.key</file>
<file>test-configuration.conf</file>
<file>test-configuration-chain.conf</file>
<file>test-configuration-faulty-certificate.conf</file>
<file>test-configuration-faulty-key.conf</file>
<file>test-configuration-faulty-chain.conf</file>
</qresource>
</RCC>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<QString>("fileName");
QTest::addColumn<bool>("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<QByteArray>("data");
QTest::addColumn<bool>("valid");
QTest::addColumn<int>("responseId");
QTest::addColumn<QString>("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<int>(QAbstractSocket::ProxyConnectionRefusedError));
QVERIFY(connection->error() == QAbstractSocket::ProxyConnectionRefusedError);
// Clean up
stopServer();
}

View File

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

View File

@ -34,7 +34,6 @@ RemoteProxyOnlineTests::RemoteProxyOnlineTests(QObject *parent) :
BaseTest(parent)
{
m_authenticator = qobject_cast<Authenticator *>(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();

View File

@ -1,8 +1,6 @@
include(../../nymea-remoteproxy.pri)
include(../testbase/testbase.pri)
TARGET = nymea-remoteproxy-tests-online
HEADERS += nymea-remoteproxy-tests-online.h

View File

@ -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<RemoteProxyConnection::Error>();
qRegisterMetaType<RemoteProxyConnection::State>();
qRegisterMetaType<RemoteProxyConnection::ConnectionType>();

View File

@ -80,13 +80,14 @@ protected slots:
void cleanupTestCase();
public slots:
void sslErrors(const QList<QSslError> &) {
inline void sslErrors(const QList<QSslError> &) {
QWebSocket *socket = static_cast<QWebSocket*>(sender());
socket->ignoreSslErrors();
}
void ignoreConnectionSslError(const QList<QSslError> &) {
inline void ignoreConnectionSslError(const QList<QSslError> &errors) {
RemoteProxyConnection *connection = static_cast<RemoteProxyConnection *>(sender());
connection->ignoreSslErrors(errors);
connection->ignoreSslErrors();
}

View File

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