Merge PR #9: Tcp server
This commit is contained in:
commit
498f6dc715
@ -11,7 +11,7 @@ LIBS += -L$$top_builddir/libnymea-remoteproxyclient/ -lnymea-remoteproxyclient
|
||||
SOURCES += main.cpp \
|
||||
proxyclient.cpp
|
||||
|
||||
target.path = /usr/bin
|
||||
target.path = $$[QT_INSTALL_PREFIX]/bin
|
||||
INSTALLS += target
|
||||
|
||||
HEADERS += \
|
||||
|
||||
@ -98,7 +98,7 @@ int main(int argc, char *argv[])
|
||||
"a server application as client perspective.\n\n"
|
||||
"Version: %1\n"
|
||||
"API version: %2\n\n"
|
||||
"Copyright %3 2018 Simon Stürz <simon.stuerz@guh.io>\n")
|
||||
"Copyright %3 2021 nymea GmbH <developer@nymea.io>\n")
|
||||
.arg(SERVER_VERSION_STRING)
|
||||
.arg(API_VERSION_STRING)
|
||||
.arg(QChar(0xA9)));
|
||||
|
||||
@ -1,36 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Export the library path for now
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(pwd)/libnymea-remoteproxy:$(pwd)/libnymea-remoteproxyclient
|
||||
|
||||
# Build
|
||||
qmake CONFIG+=coverage CONFIG+=ccache
|
||||
make -j$(nproc)
|
||||
#make check
|
||||
make coverage-html
|
||||
|
||||
# Clean build
|
||||
make clean
|
||||
|
||||
# Clean source directory
|
||||
rm -v Makefile
|
||||
|
||||
rm -v libnymea-remoteproxy/libnymea-remoteproxy.so*
|
||||
rm -v libnymea-remoteproxy/Makefile
|
||||
|
||||
rm -v libnymea-remoteproxyclient/libnymea-remoteproxyclient.so*
|
||||
rm -v libnymea-remoteproxyclient/Makefile
|
||||
|
||||
rm -v server/nymea-remoteproxy
|
||||
rm -v server/Makefile
|
||||
|
||||
rm -v tests/Makefile
|
||||
|
||||
rm -v tests/test-offline/nymea-remoteproxy-tests-offline
|
||||
rm -v tests/test-offline/Makefile
|
||||
|
||||
#rm -v tests/test-online/nymea-remoteproxy-tests-online
|
||||
#rm -v tests/test-online/Makefile
|
||||
|
||||
rm -v client/nymea-remoteproxy-client
|
||||
rm -v client/Makefile
|
||||
1
debian/nymea-remoteproxy.install.in
vendored
1
debian/nymea-remoteproxy.install.in
vendored
@ -1,2 +1,3 @@
|
||||
usr/bin/nymea-remoteproxy
|
||||
nymea-remoteproxy.conf etc/nymea/
|
||||
nymea-remoteproxy-logging.conf etc/nymea/
|
||||
|
||||
1
debian/nymea-remoteproxy.service
vendored
1
debian/nymea-remoteproxy.service
vendored
@ -6,6 +6,7 @@ Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Environment=QT_LOGGING_CONF=/etc/nymea/nymea-remoteproxy-logging.conf
|
||||
ExecStart=/usr/bin/nymea-remoteproxy -c /etc/nymea/nymea-remoteproxy.conf
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
3
debian/rules
vendored
3
debian/rules
vendored
@ -15,6 +15,9 @@ override_dh_strip:
|
||||
dh_strip --dbg-package=libnymea-remoteproxy-dbg
|
||||
dh_strip --dbg-package=libnymea-remoteproxyclient-dbg
|
||||
|
||||
#override_dh_auto_test:
|
||||
# make test
|
||||
|
||||
override_dh_auto_clean:
|
||||
dh_auto_clean
|
||||
find . -name *.qm -exec rm {} \;
|
||||
|
||||
12
generate-coverage.sh
Executable file
12
generate-coverage.sh
Executable file
@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
SCRIPT_DIR=$(dirname $0)
|
||||
SRC_DIR="$SCRIPT_DIR/../build-nymea-remoteproxy-Desktop-Debug/"
|
||||
COV_DIR="$SRC_DIR/coverage"
|
||||
HTML_RESULTS="${COV_DIR}/html"
|
||||
|
||||
# Build code coverage html report
|
||||
mkdir -p ${HTML_RESULTS}
|
||||
lcov -d "${SRC_DIR}" -c -o "${COV_DIR}/coverage.info"
|
||||
lcov -r "${COV_DIR}/coverage.info" "*.h" "*/tests/*" "*.moc" "*moc_*.cpp" "*/test/*" "/usr/include/*" "*/build*/*" "*libnymea-remoteproxy/authentication/aws*" -o "${COV_DIR}/coverage-filtered.info"
|
||||
genhtml -o "${HTML_RESULTS}" "${COV_DIR}/coverage-filtered.info"
|
||||
lcov -d "${COV_DIR}" -z
|
||||
@ -26,23 +26,30 @@
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "engine.h"
|
||||
#include "loggingcategories.h"
|
||||
#include "authenticationreply.h"
|
||||
#include "authentication/authenticator.h"
|
||||
|
||||
namespace remoteproxy {
|
||||
|
||||
AuthenticationReply::AuthenticationReply(ProxyClient *proxyClient, QObject *parent) :
|
||||
QObject(parent),
|
||||
m_proxyClient(proxyClient)
|
||||
QObject(parent)
|
||||
{
|
||||
m_proxyClient = proxyClient;
|
||||
|
||||
m_timer = new QTimer(this);
|
||||
m_timer->setSingleShot(true);
|
||||
connect(m_timer, &QTimer::timeout, this, &AuthenticationReply::onTimeout);
|
||||
|
||||
qCDebug(dcAuthentication) << "Created authentication reply for" << proxyClient << "Timeout:" << Engine::instance()->configuration()->authenticationTimeout() << "[ms]";
|
||||
m_timer->start(Engine::instance()->configuration()->authenticationTimeout());
|
||||
}
|
||||
|
||||
ProxyClient *AuthenticationReply::proxyClient() const
|
||||
AuthenticationReply::~AuthenticationReply()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QPointer<ProxyClient> AuthenticationReply::proxyClient() const
|
||||
{
|
||||
return m_proxyClient;
|
||||
}
|
||||
|
||||
@ -31,6 +31,7 @@
|
||||
#include <QUuid>
|
||||
#include <QTimer>
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
#include <QProcess>
|
||||
#include <QElapsedTimer>
|
||||
|
||||
@ -44,7 +45,7 @@ class AuthenticationReply : public QObject
|
||||
public:
|
||||
friend class Authenticator;
|
||||
|
||||
ProxyClient *proxyClient() const;
|
||||
QPointer<ProxyClient> proxyClient() const;
|
||||
|
||||
bool isTimedOut() const;
|
||||
bool isFinished() const;
|
||||
@ -53,7 +54,9 @@ public:
|
||||
|
||||
private:
|
||||
explicit AuthenticationReply(ProxyClient *proxyClient, QObject *parent = nullptr);
|
||||
ProxyClient *m_proxyClient = nullptr;
|
||||
~AuthenticationReply();
|
||||
|
||||
QPointer<ProxyClient> m_proxyClient;
|
||||
QTimer *m_timer = nullptr;
|
||||
|
||||
bool m_timedOut = false;
|
||||
|
||||
@ -37,6 +37,11 @@ Authenticator::Authenticator(QObject *parent) :
|
||||
|
||||
}
|
||||
|
||||
Authenticator::~Authenticator()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Authenticator::setReplyError(AuthenticationReply *reply, Authenticator::AuthenticationError error)
|
||||
{
|
||||
reply->setError(error);
|
||||
@ -52,9 +57,4 @@ AuthenticationReply *Authenticator::createAuthenticationReply(ProxyClient *proxy
|
||||
return new AuthenticationReply(proxyClient, parent);
|
||||
}
|
||||
|
||||
Authenticator::~Authenticator()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -72,20 +72,32 @@ void Engine::start(ProxyConfiguration *configuration)
|
||||
Q_ASSERT_X(m_authenticator != nullptr, "Engine", "There is no authenticator registerd.");
|
||||
|
||||
m_proxyServer = new ProxyServer(this);
|
||||
m_webSocketServer = new WebSocketServer(m_configuration->sslConfiguration(), this);
|
||||
m_webSocketServer = new WebSocketServer(m_configuration->sslEnabled(), m_configuration->sslConfiguration(), this);
|
||||
m_tcpSocketServer = new TcpSocketServer(m_configuration->sslEnabled(), m_configuration->sslConfiguration(), this);
|
||||
|
||||
// Configure websocket server
|
||||
QUrl websocketServerUrl;
|
||||
websocketServerUrl.setScheme("wss");
|
||||
websocketServerUrl.setScheme(m_configuration->sslEnabled() ? "wss" : "ws");
|
||||
websocketServerUrl.setHost(m_configuration->webSocketServerHost().toString());
|
||||
websocketServerUrl.setPort(m_configuration->webSocketServerPort());
|
||||
|
||||
m_webSocketServer->setServerUrl(websocketServerUrl);
|
||||
|
||||
m_proxyServer->registerTransportInterface(m_webSocketServer);
|
||||
// Configure tcp socket server
|
||||
QUrl tcpSocketServerUrl;
|
||||
tcpSocketServerUrl.setScheme(m_configuration->sslEnabled() ? "ssl" : "tcp");
|
||||
tcpSocketServerUrl.setHost(m_configuration->tcpServerHost().toString());
|
||||
tcpSocketServerUrl.setPort(m_configuration->tcpServerPort());
|
||||
m_tcpSocketServer->setServerUrl(tcpSocketServerUrl);
|
||||
|
||||
// Register the transport interfaces in the proxy server
|
||||
m_proxyServer->registerTransportInterface(m_webSocketServer);
|
||||
m_proxyServer->registerTransportInterface(m_tcpSocketServer);
|
||||
|
||||
// Start the server
|
||||
qCDebug(dcEngine()) << "Starting proxy server";
|
||||
m_proxyServer->startServer();
|
||||
|
||||
// Start the monitor server
|
||||
m_monitorServer = new MonitorServer(configuration->monitorSocketFileName(), this);
|
||||
m_monitorServer->startServer();
|
||||
|
||||
@ -153,6 +165,11 @@ ProxyServer *Engine::proxyServer() const
|
||||
return m_proxyServer;
|
||||
}
|
||||
|
||||
TcpSocketServer *Engine::tcpSocketServer() const
|
||||
{
|
||||
return m_tcpSocketServer;
|
||||
}
|
||||
|
||||
WebSocketServer *Engine::webSocketServer() const
|
||||
{
|
||||
return m_webSocketServer;
|
||||
|
||||
@ -38,6 +38,7 @@
|
||||
#include "logengine.h"
|
||||
#include "proxyserver.h"
|
||||
#include "monitorserver.h"
|
||||
#include "tcpsocketserver.h"
|
||||
#include "websocketserver.h"
|
||||
#include "proxyconfiguration.h"
|
||||
#include "authentication/authenticator.h"
|
||||
@ -67,6 +68,7 @@ public:
|
||||
ProxyConfiguration *configuration() const;
|
||||
Authenticator *authenticator() const;
|
||||
ProxyServer *proxyServer() const;
|
||||
TcpSocketServer *tcpSocketServer() const;
|
||||
WebSocketServer *webSocketServer() const;
|
||||
MonitorServer *monitorServer() const;
|
||||
LogEngine *logEngine() const;
|
||||
@ -88,6 +90,7 @@ private:
|
||||
ProxyConfiguration *m_configuration = nullptr;
|
||||
Authenticator *m_authenticator = nullptr;
|
||||
ProxyServer *m_proxyServer = nullptr;
|
||||
TcpSocketServer *m_tcpSocketServer = nullptr;
|
||||
WebSocketServer *m_webSocketServer = nullptr;
|
||||
MonitorServer *m_monitorServer = nullptr;
|
||||
LogEngine *m_logEngine = nullptr;
|
||||
|
||||
@ -89,7 +89,7 @@ void AuthenticationHandler::onAuthenticationFinished()
|
||||
AuthenticationReply *authenticationReply = static_cast<AuthenticationReply *>(sender());
|
||||
authenticationReply->deleteLater();
|
||||
|
||||
qCDebug(dcJsonRpc()) << "Authentication response ready for" << authenticationReply->proxyClient() << authenticationReply->error();
|
||||
qCDebug(dcJsonRpc()) << "Authentication reply finished";
|
||||
JsonReply *jsonReply = m_runningAuthentications.take(authenticationReply);
|
||||
|
||||
if (authenticationReply->error() != Authenticator::AuthenticationErrorNoError) {
|
||||
@ -100,10 +100,12 @@ void AuthenticationHandler::onAuthenticationFinished()
|
||||
jsonReply->setSuccess(true);
|
||||
}
|
||||
|
||||
// Set client authenticated
|
||||
authenticationReply->proxyClient()->setAuthenticated(authenticationReply->error() == Authenticator::AuthenticationErrorNoError);
|
||||
// Set client authenticated if still there
|
||||
if (!authenticationReply->proxyClient().isNull()) {
|
||||
authenticationReply->proxyClient()->setAuthenticated(authenticationReply->error() == Authenticator::AuthenticationErrorNoError);
|
||||
jsonReply->setData(errorToReply(authenticationReply->error()));
|
||||
}
|
||||
|
||||
jsonReply->setData(errorToReply(authenticationReply->error()));
|
||||
jsonReply->finished();
|
||||
}
|
||||
|
||||
|
||||
@ -173,80 +173,10 @@ void JsonRpcServer::unregisterHandler(JsonHandler *handler)
|
||||
m_handlers.remove(handler->name());
|
||||
}
|
||||
|
||||
void JsonRpcServer::setup()
|
||||
void JsonRpcServer::processDataPackage(ProxyClient *proxyClient, const QByteArray &data)
|
||||
{
|
||||
registerHandler(this);
|
||||
registerHandler(new AuthenticationHandler(this));
|
||||
}
|
||||
|
||||
void JsonRpcServer::asyncReplyFinished()
|
||||
{
|
||||
JsonReply *reply = static_cast<JsonReply *>(sender());
|
||||
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;
|
||||
}
|
||||
|
||||
if (!reply->timedOut()) {
|
||||
Q_ASSERT_X(reply->handler()->validateReturns(reply->method(), reply->data()).first
|
||||
,"validating return value", formatAssertion(reply->handler()->name(),
|
||||
reply->method(), reply->handler(), reply->data()).toLatin1().data());
|
||||
|
||||
QPair<bool, QString> returnValidation = reply->handler()->validateReturns(reply->method(), reply->data());
|
||||
if (!returnValidation.first) {
|
||||
qCWarning(dcJsonRpc()) << "Return value validation failed. This should never happen. Please check the source code.";
|
||||
}
|
||||
|
||||
sendResponse(proxyClient, reply->commandId(), reply->data());
|
||||
|
||||
if (!reply->success()) {
|
||||
// Disconnect this client since the request was not successfully
|
||||
proxyClient->interface()->killClientConnection(proxyClient->clientId(), "API call was not successfully.");
|
||||
}
|
||||
|
||||
} else {
|
||||
sendErrorResponse(proxyClient, reply->commandId(), "Command timed out");
|
||||
// Disconnect this client since he requested something that created a timeout
|
||||
proxyClient->killConnection("API call timeouted.");
|
||||
}
|
||||
}
|
||||
|
||||
void JsonRpcServer::registerClient(ProxyClient *proxyClient)
|
||||
{
|
||||
qCDebug(dcJsonRpc()) << "Register client" << proxyClient;
|
||||
if (m_clients.contains(proxyClient)) {
|
||||
qCWarning(dcJsonRpc()) << "Client already registered" << proxyClient;
|
||||
return;
|
||||
}
|
||||
m_clients.append(proxyClient);
|
||||
}
|
||||
|
||||
void JsonRpcServer::unregisterClient(ProxyClient *proxyClient)
|
||||
{
|
||||
qCDebug(dcJsonRpc()) << "Unregister client" << proxyClient;
|
||||
if (!m_clients.contains(proxyClient)) {
|
||||
qCWarning(dcJsonRpc()) << "Client was not registered" << proxyClient;
|
||||
return;
|
||||
}
|
||||
|
||||
m_clients.removeAll(proxyClient);
|
||||
}
|
||||
|
||||
void JsonRpcServer::processData(ProxyClient *proxyClient, const QByteArray &data)
|
||||
{
|
||||
if (!m_clients.contains(proxyClient))
|
||||
return;
|
||||
|
||||
qCDebug(dcJsonRpcTraffic()) << "Incoming data from" << proxyClient << ": " << data;
|
||||
|
||||
QJsonParseError error;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
||||
|
||||
if(error.error != QJsonParseError::NoError) {
|
||||
qCWarning(dcJsonRpc) << "Failed to parse JSON data" << data << ":" << error.errorString();
|
||||
sendErrorResponse(proxyClient, -1, QString("Failed to parse JSON data: %1").arg(error.errorString()));
|
||||
@ -322,6 +252,99 @@ void JsonRpcServer::processData(ProxyClient *proxyClient, const QByteArray &data
|
||||
}
|
||||
}
|
||||
|
||||
void JsonRpcServer::setup()
|
||||
{
|
||||
registerHandler(this);
|
||||
registerHandler(new AuthenticationHandler(this));
|
||||
}
|
||||
|
||||
void JsonRpcServer::asyncReplyFinished()
|
||||
{
|
||||
JsonReply *reply = static_cast<JsonReply *>(sender());
|
||||
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;
|
||||
}
|
||||
|
||||
if (!reply->timedOut()) {
|
||||
Q_ASSERT_X(reply->handler()->validateReturns(reply->method(), reply->data()).first
|
||||
,"validating return value", formatAssertion(reply->handler()->name(),
|
||||
reply->method(), reply->handler(), reply->data()).toLatin1().data());
|
||||
|
||||
QPair<bool, QString> returnValidation = reply->handler()->validateReturns(reply->method(), reply->data());
|
||||
if (!returnValidation.first) {
|
||||
qCWarning(dcJsonRpc()) << "Return value validation failed. This should never happen. Please check the source code.";
|
||||
}
|
||||
|
||||
sendResponse(proxyClient, reply->commandId(), reply->data());
|
||||
|
||||
if (!reply->success()) {
|
||||
// Disconnect this client since the request was not successfully
|
||||
proxyClient->interface()->killClientConnection(proxyClient->clientId(), "API call was not successfully.");
|
||||
}
|
||||
|
||||
} else {
|
||||
qCWarning(dcJsonRpc()) << "The reply timeouted.";
|
||||
sendErrorResponse(proxyClient, reply->commandId(), "Command timed out");
|
||||
// Disconnect this client since he requested something that created a timeout
|
||||
proxyClient->killConnection("API call timeouted.");
|
||||
}
|
||||
}
|
||||
|
||||
void JsonRpcServer::registerClient(ProxyClient *proxyClient)
|
||||
{
|
||||
qCDebug(dcJsonRpc()) << "Register client" << proxyClient;
|
||||
if (m_clients.contains(proxyClient)) {
|
||||
qCWarning(dcJsonRpc()) << "Client already registered" << proxyClient;
|
||||
return;
|
||||
}
|
||||
m_clients.append(proxyClient);
|
||||
}
|
||||
|
||||
void JsonRpcServer::unregisterClient(ProxyClient *proxyClient)
|
||||
{
|
||||
qCDebug(dcJsonRpc()) << "Unregister client" << proxyClient;
|
||||
if (!m_clients.contains(proxyClient)) {
|
||||
qCWarning(dcJsonRpc()) << "Client was not registered" << proxyClient;
|
||||
return;
|
||||
}
|
||||
m_clients.removeAll(proxyClient);
|
||||
|
||||
if (m_asyncReplies.values().contains(proxyClient)) {
|
||||
qCWarning(dcJsonRpc()) << "Client was still waiting for a reply. Clean up reply";
|
||||
JsonReply *reply = m_asyncReplies.key(proxyClient);
|
||||
m_asyncReplies.remove(reply);
|
||||
// Note: the reply will be deleted in the finished slot
|
||||
}
|
||||
}
|
||||
|
||||
void JsonRpcServer::processData(ProxyClient *proxyClient, const QByteArray &data)
|
||||
{
|
||||
if (!m_clients.contains(proxyClient))
|
||||
return;
|
||||
|
||||
qCDebug(dcJsonRpcTraffic()) << "Incoming data from" << proxyClient << ": " << data;
|
||||
|
||||
// Handle package fragmentation
|
||||
QList<QByteArray> packages = proxyClient->processData(data);
|
||||
|
||||
// Make sure the buffer size is in range
|
||||
if (proxyClient->bufferSize() > 1024 * 10) {
|
||||
qCWarning(dcJsonRpc()) << "Data buffer size violation from" << proxyClient;
|
||||
proxyClient->killConnection("Data buffer size violation.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (const QByteArray &package, packages) {
|
||||
processDataPackage(proxyClient, package);
|
||||
}
|
||||
}
|
||||
|
||||
void JsonRpcServer::sendNotification(const QString &nameSpace, const QString &method, const QVariantMap ¶ms, ProxyClient *proxyClient)
|
||||
{
|
||||
QVariantMap notification;
|
||||
|
||||
@ -58,6 +58,7 @@ private:
|
||||
QHash<QString, JsonHandler *> m_handlers;
|
||||
QHash<JsonReply *, ProxyClient *> m_asyncReplies;
|
||||
QList<ProxyClient *> m_clients;
|
||||
|
||||
int m_notificationId = 0;
|
||||
|
||||
void sendResponse(ProxyClient *client, int commandId, const QVariantMap ¶ms = QVariantMap());
|
||||
@ -67,6 +68,7 @@ private:
|
||||
|
||||
void registerHandler(JsonHandler *handler);
|
||||
void unregisterHandler(JsonHandler *handler);
|
||||
void processDataPackage(ProxyClient *proxyClient, const QByteArray &data);
|
||||
|
||||
private slots:
|
||||
void setup();
|
||||
|
||||
@ -6,6 +6,7 @@ TARGET = nymea-remoteproxy
|
||||
HEADERS += \
|
||||
engine.h \
|
||||
loggingcategories.h \
|
||||
tcpsocketserver.h \
|
||||
transportinterface.h \
|
||||
websocketserver.h \
|
||||
proxyclient.h \
|
||||
@ -31,6 +32,7 @@ HEADERS += \
|
||||
SOURCES += \
|
||||
engine.cpp \
|
||||
loggingcategories.cpp \
|
||||
tcpsocketserver.cpp \
|
||||
transportinterface.cpp \
|
||||
websocketserver.cpp \
|
||||
proxyclient.cpp \
|
||||
|
||||
@ -30,7 +30,10 @@
|
||||
Q_LOGGING_CATEGORY(dcApplication, "Application")
|
||||
Q_LOGGING_CATEGORY(dcEngine, "Engine")
|
||||
Q_LOGGING_CATEGORY(dcJsonRpc, "JsonRpc")
|
||||
Q_LOGGING_CATEGORY(dcTunnel, "Tunnel")
|
||||
Q_LOGGING_CATEGORY(dcJsonRpcTraffic, "JsonRpcTraffic")
|
||||
Q_LOGGING_CATEGORY(dcTcpSocketServer, "TcpSocketServer")
|
||||
Q_LOGGING_CATEGORY(dcTcpSocketServerTraffic, "TcpSocketServerTraffic")
|
||||
Q_LOGGING_CATEGORY(dcWebSocketServer, "WebSocketServer")
|
||||
Q_LOGGING_CATEGORY(dcWebSocketServerTraffic, "WebSocketServerTraffic")
|
||||
Q_LOGGING_CATEGORY(dcAuthentication, "Authentication")
|
||||
|
||||
@ -34,9 +34,12 @@
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcApplication)
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcEngine)
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcJsonRpc)
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcTunnel)
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcJsonRpcTraffic)
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcWebSocketServer)
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcWebSocketServerTraffic)
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcTcpSocketServer)
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcTcpSocketServerTraffic)
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcAuthentication)
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcAuthenticationProcess)
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcProxyServer)
|
||||
|
||||
@ -27,6 +27,7 @@
|
||||
|
||||
#include "engine.h"
|
||||
#include "proxyclient.h"
|
||||
#include "loggingcategories.h"
|
||||
|
||||
#include <QDateTime>
|
||||
|
||||
@ -40,9 +41,10 @@ ProxyClient::ProxyClient(TransportInterface *interface, const QUuid &clientId, c
|
||||
{
|
||||
m_creationTimeStamp = QDateTime::currentDateTime().toTime_t();
|
||||
|
||||
connect(&m_timer, &QTimer::timeout, this, &ProxyClient::timeoutOccured);
|
||||
m_timer.setSingleShot(true);
|
||||
m_timer.start(Engine::instance()->configuration()->inactiveTimeout());
|
||||
m_timer = new QTimer(this);
|
||||
connect(m_timer, &QTimer::timeout, this, &ProxyClient::timeoutOccured);
|
||||
m_timer->setSingleShot(true);
|
||||
resetTimer();
|
||||
}
|
||||
|
||||
QUuid ProxyClient::clientId() const
|
||||
@ -74,8 +76,8 @@ void ProxyClient::setAuthenticated(bool isAuthenticated)
|
||||
{
|
||||
m_authenticated = isAuthenticated;
|
||||
if (m_authenticated) {
|
||||
m_timer.stop();
|
||||
m_timer.start(Engine::instance()->configuration()->aloneTimeout());
|
||||
m_timerWaitState = TimerWaitStateAlone;
|
||||
resetTimer();
|
||||
emit authenticated();
|
||||
}
|
||||
}
|
||||
@ -89,7 +91,7 @@ void ProxyClient::setTunnelConnected(bool isTunnelConnected)
|
||||
{
|
||||
m_tunnelConnected = isTunnelConnected;
|
||||
if (m_tunnelConnected) {
|
||||
m_timer.stop();
|
||||
m_timer->stop();
|
||||
emit tunnelConnected();
|
||||
}
|
||||
}
|
||||
@ -174,6 +176,25 @@ void ProxyClient::addTxDataCount(int dataCount)
|
||||
m_txDataCount += static_cast<quint64>(dataCount);
|
||||
}
|
||||
|
||||
ProxyClient::TimerWaitState ProxyClient::timerWaitState() const
|
||||
{
|
||||
return m_timerWaitState;
|
||||
}
|
||||
|
||||
void ProxyClient::resetTimer()
|
||||
{
|
||||
switch (m_timerWaitState) {
|
||||
case TimerWaitStateInactive:
|
||||
m_timer->stop();
|
||||
m_timer->start(Engine::instance()->configuration()->inactiveTimeout());
|
||||
break;
|
||||
case TimerWaitStateAlone:
|
||||
m_timer->stop();
|
||||
m_timer->start(Engine::instance()->configuration()->aloneTimeout());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ProxyClient::sendData(const QByteArray &data)
|
||||
{
|
||||
if (!m_interface)
|
||||
@ -190,6 +211,37 @@ void ProxyClient::killConnection(const QString &reason)
|
||||
m_interface->killClientConnection(m_clientId, reason);
|
||||
}
|
||||
|
||||
int ProxyClient::generateMessageId()
|
||||
{
|
||||
m_messageId++;
|
||||
return m_messageId;
|
||||
}
|
||||
|
||||
QList<QByteArray> ProxyClient::processData(const QByteArray &data)
|
||||
{
|
||||
QList<QByteArray> packages;
|
||||
|
||||
// Handle packet fragmentation
|
||||
m_dataBuffers.append(data);
|
||||
int splitIndex = m_dataBuffers.indexOf("}\n{");
|
||||
while (splitIndex > -1) {
|
||||
packages.append(m_dataBuffers.left(splitIndex + 1));
|
||||
m_dataBuffers = m_dataBuffers.right(m_dataBuffers.length() - splitIndex - 2);
|
||||
splitIndex = m_dataBuffers.indexOf("}\n{");
|
||||
}
|
||||
if (m_dataBuffers.trimmed().endsWith("}")) {
|
||||
packages.append(m_dataBuffers);
|
||||
m_dataBuffers.clear();
|
||||
}
|
||||
|
||||
return packages;
|
||||
}
|
||||
|
||||
int ProxyClient::bufferSize() const
|
||||
{
|
||||
return m_dataBuffers.size();
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug debug, ProxyClient *proxyClient)
|
||||
{
|
||||
debug.nospace() << "ProxyClient(";
|
||||
@ -200,8 +252,8 @@ QDebug operator<<(QDebug debug, ProxyClient *proxyClient)
|
||||
debug.nospace() << ", " << proxyClient->clientId().toString();
|
||||
debug.nospace() << ", " << proxyClient->userName();
|
||||
debug.nospace() << ", " << proxyClient->peerAddress().toString();
|
||||
debug.nospace() << ", " << proxyClient->creationTimeString() << ") ";
|
||||
return debug;
|
||||
debug.nospace() << ", " << proxyClient->creationTimeString() << ")";
|
||||
return debug.space();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -43,6 +43,12 @@ class ProxyClient : public QObject
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum TimerWaitState {
|
||||
TimerWaitStateInactive,
|
||||
TimerWaitStateAlone
|
||||
};
|
||||
Q_ENUM(TimerWaitState)
|
||||
|
||||
explicit ProxyClient(TransportInterface *interface, const QUuid &clientId, const QHostAddress &address, QObject *parent = nullptr);
|
||||
|
||||
QUuid clientId() const;
|
||||
@ -84,12 +90,20 @@ public:
|
||||
void addTxDataCount(int dataCount);
|
||||
|
||||
// Actions for this client
|
||||
TimerWaitState timerWaitState() const;
|
||||
void resetTimer();
|
||||
void sendData(const QByteArray &data);
|
||||
void killConnection(const QString &reason);
|
||||
|
||||
// Json server methods
|
||||
int generateMessageId();
|
||||
QList<QByteArray> processData(const QByteArray &data);
|
||||
int bufferSize() const;
|
||||
|
||||
private:
|
||||
TransportInterface *m_interface = nullptr;
|
||||
QTimer m_timer;
|
||||
QTimer *m_timer = nullptr;
|
||||
TimerWaitState m_timerWaitState = TimerWaitStateInactive;
|
||||
|
||||
QUuid m_clientId;
|
||||
QHostAddress m_peerAddress;
|
||||
@ -105,6 +119,11 @@ private:
|
||||
|
||||
QString m_userName;
|
||||
|
||||
// Json data information
|
||||
int m_messageId = 0;
|
||||
QByteArray m_dataBuffers;
|
||||
bool m_bufferSizeViolation = false;
|
||||
|
||||
quint64 m_rxDataCount = 0;
|
||||
quint64 m_txDataCount = 0;
|
||||
|
||||
|
||||
@ -71,6 +71,7 @@ bool ProxyConfiguration::loadConfiguration(const QString &fileName)
|
||||
settings.endGroup();
|
||||
|
||||
settings.beginGroup("SSL");
|
||||
setSslEnabled(settings.value("enabled", true).toBool());
|
||||
setSslCertificateFileName(settings.value("certificate", "/etc/ssl/certs/ssl-cert-snakeoil.pem").toString());
|
||||
setSslCertificateKeyFileName(settings.value("certificateKey", "/etc/ssl/private/ssl-cert-snakeoil.key").toString());
|
||||
setSslCertificateChainFileName(settings.value("certificateChain", "").toString());
|
||||
@ -256,6 +257,16 @@ void ProxyConfiguration::setAwsCredentialsUrl(const QUrl &url)
|
||||
m_awsCredentialsUrl = url;
|
||||
}
|
||||
|
||||
bool ProxyConfiguration::sslEnabled() const
|
||||
{
|
||||
return m_sslEnabled;
|
||||
}
|
||||
|
||||
void ProxyConfiguration::setSslEnabled(bool enabled)
|
||||
{
|
||||
m_sslEnabled = enabled;
|
||||
}
|
||||
|
||||
QString ProxyConfiguration::sslCertificateFileName() const
|
||||
{
|
||||
return m_sslCertificateFileName;
|
||||
@ -349,6 +360,7 @@ QDebug operator<<(QDebug debug, ProxyConfiguration *configuration)
|
||||
debug.nospace() << " - Authorizer lambda function:" << configuration->awsAuthorizerLambdaFunctionName() << endl;
|
||||
debug.nospace() << " - Credentials URL:" << configuration->awsCredentialsUrl().toString() << endl;
|
||||
debug.nospace() << "SSL configuration" << endl;
|
||||
debug.nospace() << " - Enabled:" << configuration->sslEnabled() << endl;
|
||||
debug.nospace() << " - Certificate:" << configuration->sslCertificateFileName() << endl;
|
||||
debug.nospace() << " - Certificate key:" << configuration->sslCertificateKeyFileName() << endl;
|
||||
debug.nospace() << " - Certificate chain:" << configuration->sslCertificateChainFileName() << endl;
|
||||
|
||||
@ -85,6 +85,9 @@ public:
|
||||
void setAwsCredentialsUrl(const QUrl &url);
|
||||
|
||||
// Ssl
|
||||
bool sslEnabled() const;
|
||||
void setSslEnabled(bool enabled);
|
||||
|
||||
QString sslCertificateFileName() const;
|
||||
void setSslCertificateFileName(const QString &fileName);
|
||||
|
||||
@ -130,6 +133,7 @@ private:
|
||||
QUrl m_awsCredentialsUrl;
|
||||
|
||||
// Ssl
|
||||
bool m_sslEnabled = true;
|
||||
QString m_sslCertificateFileName = "/etc/ssl/certs/ssl-cert-snakeoil.pem";
|
||||
QString m_sslCertificateKeyFileName = "/etc/ssl/private/ssl-cert-snakeoil.key";
|
||||
QString m_sslCertificateChainFileName;
|
||||
|
||||
@ -185,6 +185,8 @@ void ProxyServer::establishTunnel(ProxyClient *firstClient, ProxyClient *secondC
|
||||
secondClient->setTunnelConnected(true);
|
||||
|
||||
qCDebug(dcProxyServer()) << tunnel;
|
||||
qCDebug(dcTunnel()) << "Tunnel established between" << firstClient->peerAddress().toString() << firstClient->clientId()
|
||||
<< "<-->" << secondClient->peerAddress().toString() << secondClient->clientId();
|
||||
|
||||
m_totalTunnelCount += 1;
|
||||
saveStatistics();
|
||||
@ -242,18 +244,21 @@ void ProxyServer::onClientDisconnected(const QUuid &clientId)
|
||||
|
||||
// Check if
|
||||
if (m_tunnels.contains(proxyClient->tunnelIdentifier())) {
|
||||
|
||||
// There is a tunnel connection for this client, remove the tunnel and disconnect also the other client
|
||||
ProxyClient *remoteClient = getRemoteClient(proxyClient);
|
||||
TunnelConnection tunnelConnection = m_tunnels.take(proxyClient->tunnelIdentifier());
|
||||
Engine::instance()->logEngine()->logTunnel(tunnelConnection);
|
||||
if (remoteClient) {
|
||||
qCDebug(dcTunnel()) << "Remove tunnel between" << proxyClient->peerAddress().toString() << proxyClient->clientId()
|
||||
<< "<-->" << remoteClient->peerAddress().toString() << remoteClient->clientId();
|
||||
remoteClient->killConnection("Tunnel client disconnected");
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the proxy client
|
||||
proxyClient->deleteLater();
|
||||
} else {
|
||||
qCWarning(dcProxyServer()) << "Unknown client disconnected from proxy server." << clientId.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@ -269,6 +274,8 @@ void ProxyServer::onClientDataAvailable(const QUuid &clientId, const QByteArray
|
||||
if (!proxyClient->isAuthenticated() && !proxyClient->isTunnelConnected()) {
|
||||
qCDebug(dcProxyServerTraffic()) << "Client data available" << proxyClient << qUtf8Printable(data);
|
||||
m_jsonRpcServer->processData(proxyClient, data);
|
||||
// Reset the inactive timer
|
||||
proxyClient->resetTimer();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -368,8 +375,15 @@ void ProxyServer::onProxyClientAuthenticated()
|
||||
void ProxyServer::onProxyClientTimeoutOccured()
|
||||
{
|
||||
ProxyClient *proxyClient = static_cast<ProxyClient *>(sender());
|
||||
qCDebug(dcProxyServer()) << "Timeout occured for" << proxyClient;
|
||||
proxyClient->killConnection("Proxy timeout occuret");
|
||||
qCDebug(dcProxyServer()) << "Timeout occured for" << proxyClient;
|
||||
switch (proxyClient->timerWaitState()) {
|
||||
case ProxyClient::TimerWaitStateInactive:
|
||||
proxyClient->killConnection("Proxy timeout occuret. The socket was inactive.");
|
||||
break;
|
||||
case ProxyClient::TimerWaitStateAlone:
|
||||
proxyClient->killConnection("Proxy timeout occuret. The tunnel partner did not show up.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ProxyServer::startServer()
|
||||
|
||||
183
libnymea-remoteproxy/tcpsocketserver.cpp
Normal file
183
libnymea-remoteproxy/tcpsocketserver.cpp
Normal file
@ -0,0 +1,183 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* Copyright (C) 2019 Simon Stürz <simon.stuerz@guh.io> *
|
||||
* *
|
||||
* This file is part of nymea-remoteproxy. *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "tcpsocketserver.h"
|
||||
#include "loggingcategories.h"
|
||||
|
||||
namespace remoteproxy {
|
||||
|
||||
TcpSocketServer::TcpSocketServer(bool sslEnabled, const QSslConfiguration &sslConfiguration, QObject *parent) :
|
||||
TransportInterface(parent),
|
||||
m_sslEnabled(sslEnabled),
|
||||
m_sslConfiguration(sslConfiguration)
|
||||
{
|
||||
m_serverName = "TCP";
|
||||
}
|
||||
|
||||
TcpSocketServer::~TcpSocketServer()
|
||||
{
|
||||
stopServer();
|
||||
}
|
||||
|
||||
void TcpSocketServer::sendData(const QUuid &clientId, const QByteArray &data)
|
||||
{
|
||||
QTcpSocket *client = nullptr;
|
||||
client = m_clientList.value(clientId);
|
||||
if (!client) {
|
||||
qCWarning(dcTcpSocketServer()) << "Client" << clientId << "unknown to this transport";
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(dcTcpSocketServerTraffic()) << "Send data to" << clientId.toString() << data + '\n';
|
||||
if (client->write(data + '\n') < 0) {
|
||||
qCWarning(dcTcpSocketServer()) << "Could not write data to client socket" << clientId.toString();
|
||||
}
|
||||
}
|
||||
|
||||
void TcpSocketServer::killClientConnection(const QUuid &clientId, const QString &killReason)
|
||||
{
|
||||
QTcpSocket *client = m_clientList.value(clientId);
|
||||
if (!client)
|
||||
return;
|
||||
|
||||
qCWarning(dcTcpSocketServer()) << "Killing client connection" << clientId.toString() << "Reason:" << killReason;
|
||||
client->close();
|
||||
}
|
||||
|
||||
bool TcpSocketServer::running() const
|
||||
{
|
||||
if (!m_server)
|
||||
return false;
|
||||
|
||||
return m_server->isListening();
|
||||
}
|
||||
|
||||
void TcpSocketServer::onDataAvailable(QSslSocket *client, const QByteArray &data)
|
||||
{
|
||||
//qCDebug(dcTcpSocketServerTraffic()) << "Emitting data available internal.";
|
||||
QUuid clientId = m_clientList.key(qobject_cast<QTcpSocket *>(client));
|
||||
emit dataAvailable(clientId, data);
|
||||
}
|
||||
|
||||
void TcpSocketServer::onClientConnected(QSslSocket *client)
|
||||
{
|
||||
QUuid clientId = QUuid::createUuid();
|
||||
qCDebug(dcTcpSocketServer()) << "New client connected:" << client << client->peerAddress().toString() << clientId.toString();
|
||||
m_clientList.insert(clientId, client);
|
||||
emit clientConnected(clientId, client->peerAddress());
|
||||
}
|
||||
|
||||
void TcpSocketServer::onClientDisconnected(QSslSocket *client)
|
||||
{
|
||||
QUuid clientId = m_clientList.key(client);
|
||||
qCDebug(dcTcpSocketServer()) << "Client disconnected:" << client << client->peerAddress().toString() << clientId.toString();
|
||||
m_clientList.take(clientId);
|
||||
// Note: the SslServer is deleting the socket object
|
||||
emit clientDisconnected(clientId);
|
||||
}
|
||||
|
||||
bool TcpSocketServer::startServer()
|
||||
{
|
||||
qCDebug(dcTcpSocketServer()) << "Starting TCP server" << m_serverUrl.toString();
|
||||
m_server = new SslServer(m_sslEnabled, m_sslConfiguration);
|
||||
if(!m_server->listen(QHostAddress(m_serverUrl.host()), static_cast<quint16>(m_serverUrl.port()))) {
|
||||
qCWarning(dcTcpSocketServer()) << "Tcp server error: can not listen on" << m_serverUrl.toString();
|
||||
delete m_server;
|
||||
m_server = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
connect(m_server, &SslServer::clientConnected, this, &TcpSocketServer::onClientConnected);
|
||||
connect(m_server, SIGNAL(clientDisconnected(QSslSocket *)), SLOT(onClientDisconnected(QSslSocket *)));
|
||||
connect(m_server, &SslServer::dataAvailable, this, &TcpSocketServer::onDataAvailable);
|
||||
qCDebug(dcTcpSocketServer()) << "Server started successfully.";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TcpSocketServer::stopServer()
|
||||
{
|
||||
// Clean up client connections
|
||||
foreach (const QUuid &clientId, m_clientList.keys()) {
|
||||
killClientConnection(clientId, "Stop server");
|
||||
}
|
||||
|
||||
if (!m_server)
|
||||
return true;
|
||||
|
||||
qCDebug(dcTcpSocketServer()) << "Stop server" << m_serverUrl.toString();
|
||||
m_server->close();
|
||||
delete m_server;
|
||||
m_server = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
SslServer::SslServer(bool sslEnabled, const QSslConfiguration &config, QObject *parent) :
|
||||
QTcpServer(parent),
|
||||
m_sslEnabled(sslEnabled),
|
||||
m_config(config)
|
||||
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void SslServer::incomingConnection(qintptr socketDescriptor)
|
||||
{
|
||||
QSslSocket *sslSocket = new QSslSocket(this);
|
||||
qCDebug(dcTcpSocketServer()) << "Incomming connection" << sslSocket;
|
||||
connect(sslSocket, &QSslSocket::readyRead, this, &SslServer::onSocketReadyRead);
|
||||
connect(sslSocket, &QSslSocket::disconnected, this, &SslServer::onClientDisconnected);
|
||||
connect(sslSocket, &QSslSocket::encrypted, [this, sslSocket](){
|
||||
qCDebug(dcTcpSocketServer()) << "SSL encryption established for" << sslSocket;
|
||||
emit clientConnected(sslSocket);
|
||||
});
|
||||
|
||||
if (!sslSocket->setSocketDescriptor(socketDescriptor)) {
|
||||
qCWarning(dcTcpSocketServer()) << "Failed to set SSL socket descriptor.";
|
||||
delete sslSocket;
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_sslEnabled) {
|
||||
sslSocket->setSslConfiguration(m_config);
|
||||
qCDebug(dcTcpSocketServer()) << "Start SSL encryption for" << sslSocket;
|
||||
sslSocket->startServerEncryption();
|
||||
} else {
|
||||
emit clientConnected(sslSocket);
|
||||
}
|
||||
}
|
||||
|
||||
void SslServer::onClientDisconnected()
|
||||
{
|
||||
QSslSocket *sslSocket = qobject_cast<QSslSocket *>(sender());
|
||||
qCDebug(dcTcpSocketServer()) << "Client socket disconnected:" << sslSocket << sslSocket->peerAddress().toString();;
|
||||
emit clientDisconnected(sslSocket);
|
||||
sslSocket->deleteLater();
|
||||
}
|
||||
|
||||
void SslServer::onSocketReadyRead()
|
||||
{
|
||||
QSslSocket *sslSocket = qobject_cast<QSslSocket *>(sender());
|
||||
QByteArray data = sslSocket->readAll();
|
||||
qCDebug(dcTcpSocketServerTraffic()) << "Data from socket" << sslSocket->peerAddress().toString() << data;
|
||||
emit dataAvailable(sslSocket, data);
|
||||
}
|
||||
|
||||
}
|
||||
100
libnymea-remoteproxy/tcpsocketserver.h
Normal file
100
libnymea-remoteproxy/tcpsocketserver.h
Normal file
@ -0,0 +1,100 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by copyright law, and
|
||||
* remains the property of nymea GmbH. All rights, including reproduction, publication,
|
||||
* editing and translation, are reserved. The use of this project is subject to the terms of a
|
||||
* license agreement to be concluded with nymea GmbH in accordance with the terms
|
||||
* of use of nymea GmbH, available under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under
|
||||
* the terms of the GNU General Public License as published by the Free Software Foundation,
|
||||
* GNU version 3. this project is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this project.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under contact@nymea.io
|
||||
* or see our FAQ/Licensing Information on https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#ifndef TCPSOCKETSERVER_H
|
||||
#define TCPSOCKETSERVER_H
|
||||
|
||||
#include <QUuid>
|
||||
#include <QObject>
|
||||
#include <QTcpServer>
|
||||
#include <QSslConfiguration>
|
||||
|
||||
#include "transportinterface.h"
|
||||
|
||||
namespace remoteproxy {
|
||||
|
||||
|
||||
class SslServer: public QTcpServer
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SslServer(bool sslEnabled, const QSslConfiguration &config, QObject *parent = nullptr);
|
||||
~SslServer() override = default;
|
||||
|
||||
private:
|
||||
bool m_sslEnabled = false;
|
||||
QSslConfiguration m_config;
|
||||
|
||||
signals:
|
||||
void clientConnected(QSslSocket *socket);
|
||||
void clientDisconnected(QSslSocket *socket);
|
||||
void dataAvailable(QSslSocket *socket, const QByteArray &data);
|
||||
|
||||
protected:
|
||||
void incomingConnection(qintptr socketDescriptor) override;
|
||||
|
||||
private slots:
|
||||
void onClientDisconnected();
|
||||
void onSocketReadyRead();
|
||||
|
||||
};
|
||||
|
||||
|
||||
class TcpSocketServer : public TransportInterface
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit TcpSocketServer(bool sslEnabled, const QSslConfiguration &sslConfiguration, QObject *parent = nullptr);
|
||||
~TcpSocketServer() override;
|
||||
|
||||
void sendData(const QUuid &clientId, const QByteArray &data) override;
|
||||
void killClientConnection(const QUuid &clientId, const QString &killReason) override;
|
||||
|
||||
bool running() const override;
|
||||
|
||||
private:
|
||||
bool m_sslEnabled;
|
||||
QSslConfiguration m_sslConfiguration;
|
||||
|
||||
QHash<QUuid, QTcpSocket *> m_clientList;
|
||||
|
||||
SslServer *m_server = nullptr;
|
||||
|
||||
private slots:
|
||||
void onDataAvailable(QSslSocket *client, const QByteArray &data);
|
||||
void onClientConnected(QSslSocket *client);
|
||||
void onClientDisconnected(QSslSocket *client);
|
||||
|
||||
public slots:
|
||||
bool startServer() override;
|
||||
bool stopServer() override;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // TCPSOCKETSERVER_H
|
||||
@ -35,14 +35,24 @@ TransportInterface::TransportInterface(QObject *parent) :
|
||||
|
||||
}
|
||||
|
||||
QString TransportInterface::serverName() const
|
||||
{
|
||||
return m_serverName;
|
||||
}
|
||||
|
||||
TransportInterface::~TransportInterface()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QString TransportInterface::serverName() const
|
||||
{
|
||||
return m_serverName;
|
||||
}
|
||||
|
||||
QUrl TransportInterface::serverUrl() const
|
||||
{
|
||||
return m_serverUrl;
|
||||
}
|
||||
|
||||
void TransportInterface::setServerUrl(const QUrl &serverUrl)
|
||||
{
|
||||
m_serverUrl = serverUrl;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -28,6 +28,7 @@
|
||||
#ifndef TRANSPORTINTERFACE_H
|
||||
#define TRANSPORTINTERFACE_H
|
||||
|
||||
#include <QUrl>
|
||||
#include <QObject>
|
||||
#include <QHostAddress>
|
||||
|
||||
@ -45,12 +46,18 @@ public:
|
||||
virtual void sendData(const QUuid &clientId, const QByteArray &data) = 0;
|
||||
virtual void killClientConnection(const QUuid &clientId, const QString &killReason) = 0;
|
||||
|
||||
QUrl serverUrl() const;
|
||||
void setServerUrl(const QUrl &serverUrl);
|
||||
|
||||
virtual bool running() const = 0;
|
||||
|
||||
signals:
|
||||
void clientConnected(const QUuid &clientId, const QHostAddress &address);
|
||||
void clientDisconnected(const QUuid &clientId);
|
||||
void dataAvailable(const QUuid &clientId, const QByteArray &data);
|
||||
|
||||
protected:
|
||||
QUrl m_serverUrl;
|
||||
QString m_serverName;
|
||||
|
||||
public slots:
|
||||
|
||||
@ -32,11 +32,12 @@
|
||||
|
||||
namespace remoteproxy {
|
||||
|
||||
WebSocketServer::WebSocketServer(const QSslConfiguration &sslConfiguration, QObject *parent) :
|
||||
WebSocketServer::WebSocketServer(bool sslEnabled, const QSslConfiguration &sslConfiguration, QObject *parent) :
|
||||
TransportInterface(parent),
|
||||
m_sslEnabled(sslEnabled),
|
||||
m_sslConfiguration(sslConfiguration)
|
||||
{
|
||||
m_serverName = "Websocket server";
|
||||
m_serverName = "WebSocket";
|
||||
}
|
||||
|
||||
WebSocketServer::~WebSocketServer()
|
||||
@ -44,16 +45,6 @@ WebSocketServer::~WebSocketServer()
|
||||
stopServer();
|
||||
}
|
||||
|
||||
QUrl WebSocketServer::serverUrl() const
|
||||
{
|
||||
return m_serverUrl;
|
||||
}
|
||||
|
||||
void WebSocketServer::setServerUrl(const QUrl &serverUrl)
|
||||
{
|
||||
m_serverUrl = serverUrl;
|
||||
}
|
||||
|
||||
bool WebSocketServer::running() const
|
||||
{
|
||||
if (!m_server)
|
||||
@ -93,12 +84,16 @@ void WebSocketServer::onClientConnected()
|
||||
{
|
||||
// Got a new client connected
|
||||
QWebSocket *client = m_server->nextPendingConnection();
|
||||
if (!client) {
|
||||
qCWarning(dcWebSocketServer()) << "Next pending connection dissapeared. Doing nothing.";
|
||||
return;
|
||||
}
|
||||
|
||||
// Check websocket version
|
||||
if (client->version() != QWebSocketProtocol::Version13) {
|
||||
qCWarning(dcWebSocketServer()) << "Client with invalid protocol version" << client->version() << ". Rejecting.";
|
||||
client->close(QWebSocketProtocol::CloseCodeProtocolError, QString("invalid protocol version: %1 != Supported Version 13").arg(client->version()));
|
||||
delete client;
|
||||
client->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -149,7 +144,10 @@ void WebSocketServer::onBinaryMessageReceived(const QByteArray &data)
|
||||
void WebSocketServer::onClientError(QAbstractSocket::SocketError error)
|
||||
{
|
||||
QWebSocket *client = static_cast<QWebSocket *>(sender());
|
||||
qCWarning(dcWebSocketServer()) << "Client error occurred:" << error << client->errorString();
|
||||
qCWarning(dcWebSocketServer()) << "Client error occurred:" << client << client->peerAddress().toString() << error << client->errorString() << "Closing the socket.";
|
||||
|
||||
// Note: on any error which can occure, make sure the socket will be closed in any case
|
||||
client->close();
|
||||
}
|
||||
|
||||
void WebSocketServer::onAcceptError(QAbstractSocket::SocketError error)
|
||||
@ -164,8 +162,12 @@ void WebSocketServer::onServerError(QWebSocketProtocol::CloseCode closeCode)
|
||||
|
||||
bool WebSocketServer::startServer()
|
||||
{
|
||||
m_server = new QWebSocketServer(QCoreApplication::applicationName(), QWebSocketServer::SecureMode, this);
|
||||
m_server->setSslConfiguration(sslConfiguration());
|
||||
if (m_sslEnabled) {
|
||||
m_server = new QWebSocketServer(QCoreApplication::applicationName(), QWebSocketServer::SecureMode, this);
|
||||
m_server->setSslConfiguration(sslConfiguration());
|
||||
} else {
|
||||
m_server = new QWebSocketServer(QCoreApplication::applicationName(), QWebSocketServer::NonSecureMode, this);
|
||||
}
|
||||
|
||||
connect (m_server, &QWebSocketServer::newConnection, this, &WebSocketServer::onClientConnected);
|
||||
connect (m_server, &QWebSocketServer::acceptError, this, &WebSocketServer::onAcceptError);
|
||||
@ -188,16 +190,18 @@ bool WebSocketServer::stopServer()
|
||||
// Clean up client connections
|
||||
foreach (QWebSocket *client, m_clientList.values()) {
|
||||
client->close(QWebSocketProtocol::CloseCodeNormal, "Stop server");
|
||||
client->flush();
|
||||
client->abort();
|
||||
}
|
||||
|
||||
// Delete the server object
|
||||
if (m_server) {
|
||||
qCDebug(dcWebSocketServer()) << "Stop server" << m_server->serverName() << serverUrl().toString();
|
||||
m_server->close();
|
||||
delete m_server;
|
||||
m_server = nullptr;
|
||||
}
|
||||
if (!m_server)
|
||||
return true;
|
||||
|
||||
qCDebug(dcWebSocketServer()) << "Stop server" << m_server->serverName() << serverUrl().toString();
|
||||
m_server->close();
|
||||
delete m_server;
|
||||
m_server = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -43,13 +43,10 @@ class WebSocketServer : public TransportInterface
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WebSocketServer(const QSslConfiguration &sslConfiguration, QObject *parent = nullptr);
|
||||
explicit WebSocketServer(bool sslEnabled, const QSslConfiguration &sslConfiguration, QObject *parent = nullptr);
|
||||
~WebSocketServer() override;
|
||||
|
||||
QUrl serverUrl() const;
|
||||
void setServerUrl(const QUrl &serverUrl);
|
||||
|
||||
bool running() const;
|
||||
bool running() const override;
|
||||
|
||||
QSslConfiguration sslConfiguration() const;
|
||||
|
||||
@ -57,10 +54,9 @@ public:
|
||||
void killClientConnection(const QUuid &clientId, const QString &killReason) override;
|
||||
|
||||
private:
|
||||
QUrl m_serverUrl;
|
||||
QWebSocketServer *m_server = nullptr;
|
||||
bool m_sslEnabled;
|
||||
QSslConfiguration m_sslConfiguration;
|
||||
bool m_enabled = false;
|
||||
|
||||
QHash<QUuid, QWebSocket *> m_clientList;
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
INCLUDEPATH += $${PWD}
|
||||
|
||||
HEADERS += \
|
||||
$${PWD}/tcpsocketconnection.h \
|
||||
$${PWD}/proxyjsonrpcclient.h \
|
||||
$${PWD}/jsonreply.h \
|
||||
$${PWD}/remoteproxyconnection.h \
|
||||
@ -8,6 +9,7 @@ HEADERS += \
|
||||
$${PWD}/websocketconnection.h
|
||||
|
||||
SOURCES += \
|
||||
$${PWD}/tcpsocketconnection.cpp \
|
||||
$${PWD}/proxyjsonrpcclient.cpp \
|
||||
$${PWD}/jsonreply.cpp \
|
||||
$${PWD}/remoteproxyconnection.cpp \
|
||||
|
||||
@ -34,6 +34,11 @@ ProxyConnection::ProxyConnection(QObject *parent) : QObject(parent)
|
||||
|
||||
}
|
||||
|
||||
QUrl ProxyConnection::serverUrl() const
|
||||
{
|
||||
return m_serverUrl;
|
||||
}
|
||||
|
||||
bool ProxyConnection::connected()
|
||||
{
|
||||
return m_connected;
|
||||
@ -48,6 +53,11 @@ void ProxyConnection::setConnected(bool connected)
|
||||
emit connectedChanged(m_connected);
|
||||
}
|
||||
|
||||
void ProxyConnection::setServerUrl(const QUrl &serverUrl)
|
||||
{
|
||||
m_serverUrl = serverUrl;
|
||||
}
|
||||
|
||||
ProxyConnection::~ProxyConnection()
|
||||
{
|
||||
|
||||
|
||||
@ -28,6 +28,7 @@
|
||||
#ifndef SOCKETCONNECTOR_H
|
||||
#define SOCKETCONNECTOR_H
|
||||
|
||||
#include <QUrl>
|
||||
#include <QObject>
|
||||
#include <QSslError>
|
||||
#include <QHostAddress>
|
||||
@ -43,7 +44,7 @@ public:
|
||||
|
||||
virtual void sendData(const QByteArray &data) = 0;
|
||||
|
||||
virtual QUrl serverUrl() const = 0;
|
||||
QUrl serverUrl() const;
|
||||
|
||||
virtual void ignoreSslErrors() = 0;
|
||||
virtual void ignoreSslErrors(const QList<QSslError> &errors) = 0;
|
||||
@ -52,9 +53,11 @@ public:
|
||||
|
||||
private:
|
||||
bool m_connected = false;
|
||||
QUrl m_serverUrl;
|
||||
|
||||
protected:
|
||||
void setConnected(bool connected);
|
||||
void setServerUrl(const QUrl &serverUrl);
|
||||
|
||||
signals:
|
||||
void connectedChanged(bool connected);
|
||||
|
||||
@ -73,10 +73,8 @@ void JsonRpcClient::sendRequest(const QVariantMap &request)
|
||||
m_connection->sendData(data);
|
||||
}
|
||||
|
||||
void JsonRpcClient::processData(const QByteArray &data)
|
||||
void JsonRpcClient::processDataPackage(const QByteArray &data)
|
||||
{
|
||||
qCDebug(dcRemoteProxyClientJsonRpcTraffic()) << "Received data:" << data;
|
||||
|
||||
QJsonParseError error;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
@ -119,4 +117,22 @@ void JsonRpcClient::processData(const QByteArray &data)
|
||||
}
|
||||
}
|
||||
|
||||
void JsonRpcClient::processData(const QByteArray &data)
|
||||
{
|
||||
qCDebug(dcRemoteProxyClientJsonRpcTraffic()) << "Received data:" << data;
|
||||
|
||||
// Handle packet fragmentation
|
||||
m_dataBuffer.append(data);
|
||||
int splitIndex = m_dataBuffer.indexOf("}\n{");
|
||||
while (splitIndex > -1) {
|
||||
processDataPackage(m_dataBuffer.left(splitIndex + 1));
|
||||
m_dataBuffer = m_dataBuffer.right(m_dataBuffer.length() - splitIndex - 2);
|
||||
splitIndex = m_dataBuffer.indexOf("}\n{");
|
||||
}
|
||||
if (m_dataBuffer.trimmed().endsWith("}")) {
|
||||
processDataPackage(m_dataBuffer);
|
||||
m_dataBuffer.clear();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -54,10 +54,12 @@ private:
|
||||
ProxyConnection *m_connection = nullptr;
|
||||
|
||||
int m_commandId = 0;
|
||||
QByteArray m_dataBuffer;
|
||||
|
||||
QHash<int, JsonReply *> m_replies;
|
||||
|
||||
void sendRequest(const QVariantMap &request);
|
||||
void processDataPackage(const QByteArray &data);
|
||||
|
||||
signals:
|
||||
void tunnelEstablished(const QString clientName, const QString &clientUuid);
|
||||
|
||||
@ -27,6 +27,7 @@
|
||||
|
||||
#include "proxyconnection.h"
|
||||
#include "proxyjsonrpcclient.h"
|
||||
#include "tcpsocketconnection.h"
|
||||
#include "websocketconnection.h"
|
||||
#include "remoteproxyconnection.h"
|
||||
|
||||
@ -43,6 +44,15 @@ RemoteProxyConnection::RemoteProxyConnection(const QUuid &clientUuid, const QStr
|
||||
|
||||
}
|
||||
|
||||
RemoteProxyConnection::RemoteProxyConnection(const QUuid &clientUuid, const QString &clientName, RemoteProxyConnection::ConnectionType connectionType, QObject *parent) :
|
||||
QObject(parent),
|
||||
m_clientUuid(clientUuid),
|
||||
m_clientName(clientName),
|
||||
m_connectionType(connectionType)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
RemoteProxyConnection::~RemoteProxyConnection()
|
||||
{
|
||||
|
||||
@ -309,25 +319,19 @@ void RemoteProxyConnection::onTunnelEstablished(const QString &clientName, const
|
||||
|
||||
bool RemoteProxyConnection::connectServer(const QUrl &url)
|
||||
{
|
||||
if (url.scheme() != "wss") {
|
||||
// FIXME: support also tcp
|
||||
qCWarning(dcRemoteProxyClientConnection()) << "Unsupported connection type" << url.scheme() << "Default to wss";
|
||||
m_serverUrl.setScheme("wss");
|
||||
}
|
||||
|
||||
m_serverUrl = url;
|
||||
m_connectionType = ConnectionTypeWebSocket;
|
||||
m_error = QAbstractSocket::UnknownSocketError;
|
||||
|
||||
cleanUp();
|
||||
|
||||
switch (m_connectionType) {
|
||||
case ConnectionTypeWebSocket:
|
||||
qCDebug(dcRemoteProxyClientConnection()) << "Creating a web socket connection to" << url.toString();
|
||||
m_connection = qobject_cast<ProxyConnection *>(new WebSocketConnection(this));
|
||||
break;
|
||||
case ConnectionTypeTcpSocket:
|
||||
// FIXME:
|
||||
//m_connection = qobject_cast<ProxyConnection *>(new WebSocketConnection(this));
|
||||
qCDebug(dcRemoteProxyClientConnection()) << "Creating a TCP socket connection to" << url.toString();
|
||||
m_connection = qobject_cast<ProxyConnection *>(new TcpSocketConnection(this));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@ -69,6 +69,7 @@ public:
|
||||
Q_ENUM(State)
|
||||
|
||||
explicit RemoteProxyConnection(const QUuid &clientUuid, const QString &clientName, QObject *parent = nullptr);
|
||||
RemoteProxyConnection(const QUuid &clientUuid, const QString &clientName, ConnectionType connectionType, QObject *parent = nullptr);
|
||||
~RemoteProxyConnection();
|
||||
|
||||
RemoteProxyConnection::State state() const;
|
||||
@ -94,9 +95,9 @@ public:
|
||||
QString tunnelPartnerUuid() const;
|
||||
|
||||
private:
|
||||
ConnectionType m_connectionType = ConnectionTypeWebSocket;
|
||||
QUuid m_clientUuid;
|
||||
QString m_clientName;
|
||||
ConnectionType m_connectionType = ConnectionTypeWebSocket;
|
||||
|
||||
QUrl m_serverUrl;
|
||||
|
||||
@ -152,6 +153,7 @@ public slots:
|
||||
bool authenticate(const QString &token, const QString &nonce = QString());
|
||||
void disconnectServer();
|
||||
bool sendData(const QByteArray &data);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
128
libnymea-remoteproxyclient/tcpsocketconnection.cpp
Normal file
128
libnymea-remoteproxyclient/tcpsocketconnection.cpp
Normal file
@ -0,0 +1,128 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by copyright law, and
|
||||
* remains the property of nymea GmbH. All rights, including reproduction, publication,
|
||||
* editing and translation, are reserved. The use of this project is subject to the terms of a
|
||||
* license agreement to be concluded with nymea GmbH in accordance with the terms
|
||||
* of use of nymea GmbH, available under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under
|
||||
* the terms of the GNU General Public License as published by the Free Software Foundation,
|
||||
* GNU version 3. this project is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this project.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under contact@nymea.io
|
||||
* or see our FAQ/Licensing Information on https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "tcpsocketconnection.h"
|
||||
|
||||
Q_LOGGING_CATEGORY(dcRemoteProxyClientTcpSocket, "RemoteProxyClientTcpSocket")
|
||||
|
||||
namespace remoteproxyclient {
|
||||
|
||||
TcpSocketConnection::TcpSocketConnection(QObject *parent) :
|
||||
ProxyConnection(parent)
|
||||
{
|
||||
m_tcpSocket = new QSslSocket(this);
|
||||
|
||||
connect(m_tcpSocket, &QSslSocket::disconnected, this, &TcpSocketConnection::onDisconnected);
|
||||
connect(m_tcpSocket, &QSslSocket::encrypted, this, &TcpSocketConnection::onEncrypted);
|
||||
connect(m_tcpSocket, &QSslSocket::readyRead, this, &TcpSocketConnection::onReadyRead);
|
||||
connect(m_tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError)));
|
||||
connect(m_tcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onStateChanged(QAbstractSocket::SocketState)));
|
||||
connect(m_tcpSocket, SIGNAL(sslErrors(QList<QSslError>)), this, SIGNAL(sslErrors(QList<QSslError>)));
|
||||
}
|
||||
|
||||
TcpSocketConnection::~TcpSocketConnection()
|
||||
{
|
||||
m_tcpSocket->close();
|
||||
}
|
||||
|
||||
void TcpSocketConnection::sendData(const QByteArray &data)
|
||||
{
|
||||
m_tcpSocket->write(data);
|
||||
}
|
||||
|
||||
void TcpSocketConnection::ignoreSslErrors()
|
||||
{
|
||||
m_tcpSocket->ignoreSslErrors();
|
||||
}
|
||||
|
||||
void TcpSocketConnection::ignoreSslErrors(const QList<QSslError> &errors)
|
||||
{
|
||||
m_tcpSocket->ignoreSslErrors(errors);
|
||||
}
|
||||
|
||||
void TcpSocketConnection::onDisconnected()
|
||||
{
|
||||
qCDebug(dcRemoteProxyClientTcpSocket()) << "Disconnected from" << serverUrl().toString();
|
||||
setConnected(false);
|
||||
}
|
||||
|
||||
void TcpSocketConnection::onEncrypted()
|
||||
{
|
||||
qCDebug(dcRemoteProxyClientTcpSocket()) << "Connection encrypted";
|
||||
setConnected(true);
|
||||
}
|
||||
|
||||
void TcpSocketConnection::onError(QAbstractSocket::SocketError error)
|
||||
{
|
||||
qCDebug(dcRemoteProxyClientTcpSocket()) << "Socket error occured" << error << m_tcpSocket->errorString();
|
||||
emit errorOccured(error);
|
||||
}
|
||||
|
||||
void TcpSocketConnection::onStateChanged(QAbstractSocket::SocketState state)
|
||||
{
|
||||
qCDebug(dcRemoteProxyClientTcpSocket()) << "Socket state changed" << state;
|
||||
|
||||
switch (state) {
|
||||
case QAbstractSocket::ConnectedState:
|
||||
qCDebug(dcRemoteProxyClientTcpSocket()) << "Connected with" << serverUrl().toString();
|
||||
if (!m_ssl) {
|
||||
setConnected(true);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
setConnected(false);
|
||||
break;
|
||||
}
|
||||
|
||||
emit stateChanged(state);
|
||||
}
|
||||
|
||||
void TcpSocketConnection::onReadyRead()
|
||||
{
|
||||
emit dataReceived(m_tcpSocket->readAll());
|
||||
}
|
||||
|
||||
void TcpSocketConnection::connectServer(const QUrl &serverUrl)
|
||||
{
|
||||
setServerUrl(serverUrl);
|
||||
qCDebug(dcRemoteProxyClientTcpSocket()) << "Connecting to" << this->serverUrl().toString();
|
||||
|
||||
if (serverUrl.scheme() == "tcp") {
|
||||
m_tcpSocket->connectToHost(QHostAddress(this->serverUrl().host()), static_cast<quint16>(this->serverUrl().port()));
|
||||
} else {
|
||||
m_ssl = true;
|
||||
m_tcpSocket->connectToHostEncrypted(this->serverUrl().host(), static_cast<quint16>(this->serverUrl().port()));
|
||||
}
|
||||
}
|
||||
|
||||
void TcpSocketConnection::disconnectServer()
|
||||
{
|
||||
qCDebug(dcRemoteProxyClientTcpSocket()) << "Disconnecting from" << serverUrl().toString();
|
||||
m_tcpSocket->close();
|
||||
}
|
||||
|
||||
}
|
||||
74
libnymea-remoteproxyclient/tcpsocketconnection.h
Normal file
74
libnymea-remoteproxyclient/tcpsocketconnection.h
Normal file
@ -0,0 +1,74 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by copyright law, and
|
||||
* remains the property of nymea GmbH. All rights, including reproduction, publication,
|
||||
* editing and translation, are reserved. The use of this project is subject to the terms of a
|
||||
* license agreement to be concluded with nymea GmbH in accordance with the terms
|
||||
* of use of nymea GmbH, available under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under
|
||||
* the terms of the GNU General Public License as published by the Free Software Foundation,
|
||||
* GNU version 3. this project is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this project.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under contact@nymea.io
|
||||
* or see our FAQ/Licensing Information on https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#ifndef TCPSOCKETCONNECTION_H
|
||||
#define TCPSOCKETCONNECTION_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QTcpSocket>
|
||||
#include <QSslSocket>
|
||||
#include <QLoggingCategory>
|
||||
|
||||
#include "proxyconnection.h"
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcRemoteProxyClienTcpSocket)
|
||||
|
||||
namespace remoteproxyclient {
|
||||
|
||||
class TcpSocketConnection : public ProxyConnection
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TcpSocketConnection(QObject *parent = nullptr);
|
||||
~TcpSocketConnection() override;
|
||||
|
||||
void sendData(const QByteArray &data) override;
|
||||
|
||||
void ignoreSslErrors() override;
|
||||
void ignoreSslErrors(const QList<QSslError> &errors) override;
|
||||
|
||||
private:
|
||||
QSslSocket *m_tcpSocket = nullptr;
|
||||
bool m_ssl = false;
|
||||
|
||||
private slots:
|
||||
void onDisconnected();
|
||||
void onEncrypted();
|
||||
void onError(QAbstractSocket::SocketError error);
|
||||
void onStateChanged(QAbstractSocket::SocketState state);
|
||||
void onReadyRead();
|
||||
|
||||
public slots:
|
||||
void connectServer(const QUrl &serverUrl) override;
|
||||
void disconnectServer() override;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // TCPSOCKETCONNECTION_H
|
||||
@ -49,11 +49,6 @@ WebSocketConnection::~WebSocketConnection()
|
||||
m_webSocket->close();
|
||||
}
|
||||
|
||||
QUrl WebSocketConnection::serverUrl() const
|
||||
{
|
||||
return m_serverUrl;
|
||||
}
|
||||
|
||||
void WebSocketConnection::sendData(const QByteArray &data)
|
||||
{
|
||||
m_webSocket->sendTextMessage(QString::fromUtf8(data + '\n'));
|
||||
@ -108,10 +103,10 @@ void WebSocketConnection::connectServer(const QUrl &serverUrl)
|
||||
if (connected()) {
|
||||
m_webSocket->close();
|
||||
}
|
||||
setServerUrl(serverUrl);
|
||||
|
||||
m_serverUrl = serverUrl;
|
||||
qCDebug(dcRemoteProxyClientWebSocket()) << "Connecting to" << m_serverUrl.toString();
|
||||
m_webSocket->open(m_serverUrl);
|
||||
qCDebug(dcRemoteProxyClientWebSocket()) << "Connecting to" << this->serverUrl().toString();
|
||||
m_webSocket->open(this->serverUrl());
|
||||
}
|
||||
|
||||
void WebSocketConnection::disconnectServer()
|
||||
|
||||
@ -46,15 +46,12 @@ public:
|
||||
explicit WebSocketConnection(QObject *parent = nullptr);
|
||||
~WebSocketConnection() override;
|
||||
|
||||
QUrl serverUrl() const override;
|
||||
|
||||
void sendData(const QByteArray &data) override;
|
||||
|
||||
void ignoreSslErrors() override;
|
||||
void ignoreSslErrors(const QList<QSslError> &errors) override;
|
||||
|
||||
private:
|
||||
QUrl m_serverUrl;
|
||||
QWebSocket *m_webSocket = nullptr;
|
||||
|
||||
private slots:
|
||||
|
||||
@ -15,5 +15,5 @@ SOURCES += main.cpp \
|
||||
|
||||
LIBS += -lncurses
|
||||
|
||||
target.path = /usr/bin
|
||||
target.path = $$[QT_INSTALL_PREFIX]/bin
|
||||
INSTALLS += target
|
||||
|
||||
16
nymea-remoteproxy-logging.conf
Normal file
16
nymea-remoteproxy-logging.conf
Normal file
@ -0,0 +1,16 @@
|
||||
[Rules]
|
||||
*.debug=false
|
||||
Application.debug=true
|
||||
Engine.debug=true
|
||||
Tunnel.debug=true
|
||||
JsonRpc.debug=true
|
||||
JsonRpcTraffic.debug=false
|
||||
WebSocketServer.debug=true
|
||||
WebSocketServerTraffic.debug=false
|
||||
Authentication.debug=true
|
||||
AuthenticationProcess.debug=false
|
||||
ProxyServer.debug=true
|
||||
ProxyServerTraffic.debug=false
|
||||
MonitorServer.debug=true
|
||||
AwsCredentialsProvider.debug=false
|
||||
AwsCredentialsProviderTraffic.debug=false
|
||||
@ -15,6 +15,7 @@ authorizerLambdaFunction=system-services-authorizer-dev-checkToken
|
||||
awsCredentialsUrl=http://169.254.169.254/latest/meta-data/iam/security-credentials/EC2-Remote-Connection-Proxy-Role
|
||||
|
||||
[SSL]
|
||||
enabled=true
|
||||
certificate=/etc/ssl/certs/ssl-cert-snakeoil.pem
|
||||
certificateKey=/etc/ssl/private/ssl-cert-snakeoil.key
|
||||
certificateChain=
|
||||
|
||||
@ -5,7 +5,7 @@ QT -= gui
|
||||
SERVER_NAME=nymea-remoteproxy
|
||||
API_VERSION_MAJOR=0
|
||||
API_VERSION_MINOR=3
|
||||
SERVER_VERSION=0.1.7
|
||||
SERVER_VERSION=0.1.8
|
||||
|
||||
DEFINES += SERVER_NAME_STRING=\\\"$${SERVER_NAME}\\\" \
|
||||
SERVER_VERSION_STRING=\\\"$${SERVER_VERSION}\\\" \
|
||||
@ -23,7 +23,7 @@ ccache {
|
||||
QMAKE_CXX = ccache g++
|
||||
}
|
||||
|
||||
coverage {<
|
||||
coverage {
|
||||
# Note: this works only if you build in the source dir
|
||||
OBJECTS_DIR =
|
||||
MOC_DIR =
|
||||
|
||||
@ -15,6 +15,11 @@ server.depends = libnymea-remoteproxy
|
||||
client.depends = libnymea-remoteproxyclient
|
||||
tests.depends = libnymea-remoteproxy libnymea-remoteproxyclient
|
||||
|
||||
test.commands = LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:$$top_builddir/libnymea-remoteproxy:$$top_builddir/libnymea-remoteproxyclient \
|
||||
LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:$$top_srcdir/libnymea-remoteproxy:$$top_srcdir/libnymea-remoteproxyclient \
|
||||
make check
|
||||
QMAKE_EXTRA_TARGETS += test
|
||||
|
||||
message("----------------------------------------------------------")
|
||||
message("Building nymea-remoteproxy $${SERVER_VERSION}")
|
||||
message("----------------------------------------------------------")
|
||||
|
||||
@ -62,19 +62,6 @@ static const char *const normal = "\033[0m";
|
||||
static const char *const warning = "\e[33m";
|
||||
static const char *const error = "\e[31m";
|
||||
|
||||
static void loggingCategoryFilter(QLoggingCategory *category)
|
||||
{
|
||||
if (s_loggingFilters.contains(category->categoryName())) {
|
||||
bool debugEnabled = s_loggingFilters.value(category->categoryName());
|
||||
category->setEnabled(QtDebugMsg, debugEnabled);
|
||||
category->setEnabled(QtWarningMsg, debugEnabled || s_loggingFilters.value("Warnings"));
|
||||
} else {
|
||||
// Enable default debug output
|
||||
category->setEnabled(QtDebugMsg, true);
|
||||
category->setEnabled(QtWarningMsg, s_loggingFilters.value("Warnings"));
|
||||
}
|
||||
}
|
||||
|
||||
static void consoleLogHandler(QtMsgType type, const QMessageLogContext& context, const QString& message)
|
||||
{
|
||||
QString messageString;
|
||||
@ -119,22 +106,6 @@ int main(int argc, char *argv[])
|
||||
application.setOrganizationName("nymea");
|
||||
application.setApplicationVersion(SERVER_VERSION_STRING);
|
||||
|
||||
s_loggingFilters.insert("Application", true);
|
||||
s_loggingFilters.insert("Engine", true);
|
||||
s_loggingFilters.insert("JsonRpc", true);
|
||||
s_loggingFilters.insert("WebSocketServer", true);
|
||||
s_loggingFilters.insert("Authentication", true);
|
||||
s_loggingFilters.insert("ProxyServer", true);
|
||||
s_loggingFilters.insert("MonitorServer", true);
|
||||
s_loggingFilters.insert("AwsCredentialsProvider", true);
|
||||
|
||||
// Only with verbose enabled
|
||||
s_loggingFilters.insert("JsonRpcTraffic", false);
|
||||
s_loggingFilters.insert("ProxyServerTraffic", false);
|
||||
s_loggingFilters.insert("AuthenticationProcess", false);
|
||||
s_loggingFilters.insert("WebSocketServerTraffic", false);
|
||||
s_loggingFilters.insert("AwsCredentialsProviderTraffic", false);
|
||||
|
||||
QString configFile = "/etc/nymea/nymea-remoteproxy.conf";
|
||||
|
||||
// command line parser
|
||||
@ -145,7 +116,7 @@ int main(int argc, char *argv[])
|
||||
"registered nymea deamons to establish a tunnel connection.\n\n"
|
||||
"Version: %1\n"
|
||||
"API version: %2\n\n"
|
||||
"Copyright %3 2018 Simon Stürz <simon.stuerz@guh.io>\n")
|
||||
"Copyright %3 2021 nymea GmbH <developer@nymea.io>\n")
|
||||
.arg(SERVER_VERSION_STRING)
|
||||
.arg(API_VERSION_STRING)
|
||||
.arg(QChar(0xA9)));
|
||||
@ -186,15 +157,6 @@ int main(int argc, char *argv[])
|
||||
configuration->setLogFileName(parser.value(logfileOption));
|
||||
}
|
||||
|
||||
if (parser.isSet(verboseOption)) {
|
||||
s_loggingFilters["JsonRpcTraffic"] = true;
|
||||
s_loggingFilters["ProxyServerTraffic"] = true;
|
||||
s_loggingFilters["AuthenticationProcess"] = true;
|
||||
s_loggingFilters["WebSocketServerTraffic"] = true;
|
||||
s_loggingFilters["AwsCredentialsProviderTraffic"] = true;
|
||||
}
|
||||
QLoggingCategory::installFilter(loggingCategoryFilter);
|
||||
|
||||
// Open logfile if configured
|
||||
if (configuration->writeLogFile()) {
|
||||
s_loggingEnabled = true;
|
||||
@ -224,9 +186,11 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
|
||||
// Verify SSL configuration
|
||||
if (configuration->sslConfiguration().isNull()) {
|
||||
qCCritical(dcApplication()) << "No SSL configuration specified. The server does not suppoert insecure connections.";
|
||||
if (configuration->sslEnabled() && configuration->sslConfiguration().isNull()) {
|
||||
qCCritical(dcApplication()) << "SSL is enabled but no SSL configuration specified.";
|
||||
exit(-1);
|
||||
} else {
|
||||
qCDebug(dcApplication()) << "Using SSL version:" << QSslSocket::sslLibraryVersionString();
|
||||
}
|
||||
|
||||
qCDebug(dcApplication()) << "==========================================================";
|
||||
@ -241,7 +205,6 @@ int main(int argc, char *argv[])
|
||||
if (s_loggingEnabled)
|
||||
qCDebug(dcApplication()) << "Logging enabled. Writing logs to" << s_logFile.fileName();
|
||||
|
||||
qCDebug(dcApplication()) << "Using SSL version:" << QSslSocket::sslLibraryVersionString();
|
||||
|
||||
Authenticator *authenticator = nullptr;
|
||||
if (parser.isSet(mockAuthenticatorOption)) {
|
||||
|
||||
@ -10,7 +10,7 @@ LIBS += -L$$top_builddir/libnymea-remoteproxy/ -lnymea-remoteproxy
|
||||
SOURCES += main.cpp \
|
||||
remoteproxyserverapplication.cpp
|
||||
|
||||
target.path = /usr/bin
|
||||
target.path = $$[QT_INSTALL_PREFIX]/bin
|
||||
INSTALLS += target
|
||||
|
||||
HEADERS += \
|
||||
|
||||
@ -3,10 +3,10 @@ name=test-nymea-remoteproxy
|
||||
writeLogs=false
|
||||
logFile=/var/log/nymea-remoteproxy.log
|
||||
monitorSocket=/tmp/nymea-remoteproxy-test.sock
|
||||
jsonRpcTimeout=1000
|
||||
authenticationTimeout=1500
|
||||
jsonRpcTimeout=2000
|
||||
authenticationTimeout=1000
|
||||
inactiveTimeout=1500
|
||||
aloneTimeout=1000
|
||||
aloneTimeout=1500
|
||||
|
||||
[SSL]
|
||||
certificate=:/test-certificate.crt
|
||||
|
||||
@ -53,9 +53,14 @@ void RemoteProxyOfflineTests::dummyAuthenticator()
|
||||
{
|
||||
cleanUpEngine();
|
||||
|
||||
m_configuration = new ProxyConfiguration(this);
|
||||
loadConfiguration(":/test-configuration.conf");
|
||||
|
||||
m_dummyAuthenticator = new DummyAuthenticator(this);
|
||||
m_authenticator = qobject_cast<Authenticator *>(m_dummyAuthenticator);
|
||||
|
||||
// Start proxy webserver
|
||||
Engine::instance()->setAuthenticator(m_dummyAuthenticator);
|
||||
Engine::instance()->setAuthenticator(m_dummyAuthenticator);
|
||||
QSignalSpy runningSpy(Engine::instance(), &Engine::runningChanged);
|
||||
Engine::instance()->start(m_configuration);
|
||||
runningSpy.wait();
|
||||
@ -73,7 +78,7 @@ void RemoteProxyOfflineTests::dummyAuthenticator()
|
||||
params.insert("name", "test");
|
||||
params.insert("token", "foobar");
|
||||
|
||||
QVariant response = invokeApiCall("Authentication.Authenticate", params);
|
||||
QVariant response = invokeWebSocketApiCall("Authentication.Authenticate", params);
|
||||
qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented));
|
||||
verifyAuthenticationError(response);
|
||||
|
||||
@ -209,16 +214,21 @@ void RemoteProxyOfflineTests::serverPortBlocked()
|
||||
{
|
||||
cleanUpEngine();
|
||||
|
||||
m_configuration = new ProxyConfiguration(this);
|
||||
loadConfiguration(":/test-configuration.conf");
|
||||
|
||||
m_mockAuthenticator = new MockAuthenticator(this);
|
||||
m_authenticator = qobject_cast<Authenticator *>(m_mockAuthenticator);
|
||||
|
||||
// Create a dummy server which blocks the port
|
||||
QWebSocketServer dummyServer("dummy-server", QWebSocketServer::NonSecureMode);
|
||||
dummyServer.listen(QHostAddress::LocalHost, 1212);
|
||||
QVERIFY(dummyServer.listen(QHostAddress::LocalHost, m_configuration->webSocketServerPort()));
|
||||
|
||||
// Start proxy webserver
|
||||
QSignalSpy runningSpy(Engine::instance(), &Engine::runningChanged);
|
||||
Engine::instance()->setAuthenticator(m_authenticator);
|
||||
Engine::instance()->start(m_configuration);
|
||||
runningSpy.wait();
|
||||
qDebug() << runningSpy.count();
|
||||
QVERIFY(runningSpy.count() == 1);
|
||||
|
||||
// Make sure the server is not running
|
||||
@ -233,11 +243,49 @@ void RemoteProxyOfflineTests::serverPortBlocked()
|
||||
QVERIFY(closedSpy.count() == 1);
|
||||
|
||||
// Try again
|
||||
cleanUpEngine();
|
||||
startServer();
|
||||
|
||||
// Clean up
|
||||
stopServer();
|
||||
|
||||
// Do the same with the tcp server
|
||||
cleanUpEngine();
|
||||
|
||||
m_configuration = new ProxyConfiguration(this);
|
||||
loadConfiguration(":/test-configuration.conf");
|
||||
|
||||
m_mockAuthenticator = new MockAuthenticator(this);
|
||||
m_authenticator = qobject_cast<Authenticator *>(m_mockAuthenticator);
|
||||
|
||||
// Create a dummy server which blocks the port
|
||||
QTcpServer *tcpDummyServer = new QTcpServer(this);
|
||||
QVERIFY(tcpDummyServer->listen(QHostAddress::LocalHost, m_configuration->tcpServerPort()));
|
||||
|
||||
// Start proxy webserver
|
||||
QSignalSpy runningSpy2(Engine::instance(), &Engine::runningChanged);
|
||||
Engine::instance()->setAuthenticator(m_authenticator);
|
||||
Engine::instance()->start(m_configuration);
|
||||
runningSpy2.wait();
|
||||
QVERIFY(runningSpy2.count() == 1);
|
||||
|
||||
// Make sure the engine is running
|
||||
QVERIFY(Engine::instance()->running());
|
||||
|
||||
// Make sure the TCP server is not running
|
||||
QVERIFY(!Engine::instance()->tcpSocketServer()->running());
|
||||
|
||||
tcpDummyServer->close();
|
||||
delete tcpDummyServer;
|
||||
|
||||
// Try again
|
||||
startServer();
|
||||
|
||||
// Make sure the TCP server is not running
|
||||
QVERIFY(Engine::instance()->webSocketServer()->running());
|
||||
QVERIFY(Engine::instance()->tcpSocketServer()->running());
|
||||
|
||||
// Clean up
|
||||
stopServer();
|
||||
}
|
||||
|
||||
void RemoteProxyOfflineTests::websocketBinaryData()
|
||||
@ -286,13 +334,70 @@ void RemoteProxyOfflineTests::websocketPing()
|
||||
stopServer();
|
||||
}
|
||||
|
||||
//void RemoteProxyOfflineTests::apiBasicCallsTcp_data()
|
||||
//{
|
||||
// QTest::addColumn<QByteArray>("data");
|
||||
// QTest::addColumn<int>("responseId");
|
||||
// QTest::addColumn<QString>("responseStatus");
|
||||
|
||||
// QTest::newRow("valid call") << QByteArray("{\"id\":42, \"method\":\"RemoteProxy.Hello\"}") << 42 << "success";
|
||||
// QTest::newRow("missing id") << QByteArray("{\"method\":\"RemoteProxy.Hello\"}") << -1 << "error";
|
||||
// QTest::newRow("missing method") << QByteArray("{\"id\":42}") << 42 << "error";
|
||||
// QTest::newRow("invalid json") << QByteArray("{\"id\":42, \"method\":\"RemoteProx") << -1 << "error";
|
||||
// QTest::newRow("invalid function") << QByteArray("{\"id\":42, \"method\":\"RemoteProxy.Explode\"}") << 42 << "error";
|
||||
// QTest::newRow("invalid namespace") << QByteArray("{\"id\":42, \"method\":\"ProxyRemote.Hello\"}") << 42 << "error";
|
||||
// QTest::newRow("missing dot") << QByteArray("{\"id\":42, \"method\":\"RemoteProxyHello\"}") << 42 << "error";
|
||||
// QTest::newRow("invalid params") << QByteArray("{\"id\":42, \"method\":\"RemoteProxy.Hello\", \"params\":{\"törööö\":\"chooo-chooo\"}}") << 42 << "error";
|
||||
// QTest::newRow("invalid authentication params") << QByteArray("{\"id\":42, \"method\":\"Authentication.Authenticate\", \"params\":{\"your\":\"mamma\"}}") << 42 << "error";
|
||||
//}
|
||||
|
||||
//void RemoteProxyOfflineTests::apiBasicCallsTcp()
|
||||
//{
|
||||
// QFETCH(QByteArray, data);
|
||||
// QFETCH(int, responseId);
|
||||
// QFETCH(QString, responseStatus);
|
||||
|
||||
// // Start the server
|
||||
// startServer();
|
||||
|
||||
// QVariant response = injectTcpSocketData(data);
|
||||
// QVERIFY(!response.isNull());
|
||||
|
||||
// qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented));
|
||||
|
||||
// QCOMPARE(response.toMap().value("id").toInt(), responseId);
|
||||
// QCOMPARE(response.toMap().value("status").toString(), responseStatus);
|
||||
|
||||
// // Clean up
|
||||
// stopServer();
|
||||
//}
|
||||
|
||||
void RemoteProxyOfflineTests::getIntrospect()
|
||||
{
|
||||
// Start the server
|
||||
startServer();
|
||||
|
||||
QVariant response = invokeApiCall("RemoteProxy.Introspect");
|
||||
qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented));
|
||||
QVariantMap response;
|
||||
|
||||
// WebSocket
|
||||
response = invokeWebSocketApiCall("RemoteProxy.Introspect").toMap();
|
||||
//qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented));
|
||||
QVERIFY(!response.isEmpty());
|
||||
QVERIFY(response.value("status").toString() == "success");
|
||||
QVERIFY(response.value("params").toMap().contains("methods"));
|
||||
QVERIFY(response.value("params").toMap().contains("notifications"));
|
||||
QVERIFY(response.value("params").toMap().contains("types"));
|
||||
|
||||
// Tcp
|
||||
response.clear();
|
||||
response = invokeTcpSocketApiCall("RemoteProxy.Introspect").toMap();
|
||||
//qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented));
|
||||
|
||||
QVERIFY(!response.isEmpty());
|
||||
QVERIFY(response.value("status").toString() == "success");
|
||||
QVERIFY(response.value("params").toMap().contains("methods"));
|
||||
QVERIFY(response.value("params").toMap().contains("notifications"));
|
||||
QVERIFY(response.value("params").toMap().contains("types"));
|
||||
|
||||
// Clean up
|
||||
stopServer();
|
||||
@ -302,16 +407,32 @@ void RemoteProxyOfflineTests::getHello()
|
||||
{
|
||||
// Start the server
|
||||
startServer();
|
||||
QVariantMap response;
|
||||
|
||||
QVariantMap response = invokeApiCall("RemoteProxy.Hello").toMap();
|
||||
qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented));
|
||||
// WebSocket
|
||||
response = invokeWebSocketApiCall("RemoteProxy.Hello").toMap();
|
||||
//qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented));
|
||||
|
||||
// Verify data
|
||||
QVERIFY(!response.isEmpty());
|
||||
QCOMPARE(response.value("params").toMap().value("name").toString(), Engine::instance()->configuration()->serverName());
|
||||
QCOMPARE(response.value("params").toMap().value("server").toString(), QString(SERVER_NAME_STRING));
|
||||
QCOMPARE(response.value("params").toMap().value("version").toString(), QString(SERVER_VERSION_STRING));
|
||||
QCOMPARE(response.value("params").toMap().value("apiVersion").toString(), QString(API_VERSION_STRING));
|
||||
|
||||
// TCP
|
||||
response.clear();
|
||||
response = invokeTcpSocketApiCall("RemoteProxy.Hello").toMap();
|
||||
//qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented));
|
||||
|
||||
// Verify data
|
||||
QVERIFY(!response.isEmpty());
|
||||
QCOMPARE(response.value("params").toMap().value("name").toString(), Engine::instance()->configuration()->serverName());
|
||||
QCOMPARE(response.value("params").toMap().value("server").toString(), QString(SERVER_NAME_STRING));
|
||||
QCOMPARE(response.value("params").toMap().value("version").toString(), QString(SERVER_VERSION_STRING));
|
||||
QCOMPARE(response.value("params").toMap().value("apiVersion").toString(), QString(API_VERSION_STRING));
|
||||
|
||||
|
||||
// Clean up
|
||||
stopServer();
|
||||
}
|
||||
@ -325,7 +446,7 @@ void RemoteProxyOfflineTests::apiBasicCalls_data()
|
||||
QTest::newRow("valid call") << QByteArray("{\"id\":42, \"method\":\"RemoteProxy.Hello\"}") << 42 << "success";
|
||||
QTest::newRow("missing id") << QByteArray("{\"method\":\"RemoteProxy.Hello\"}") << -1 << "error";
|
||||
QTest::newRow("missing method") << QByteArray("{\"id\":42}") << 42 << "error";
|
||||
QTest::newRow("invalid json") << QByteArray("{\"id\":42, \"method\":\"RemoteProx") << -1 << "error";
|
||||
QTest::newRow("invalid json") << QByteArray("{\"id\":42, \"method\":\"RemoteProx}") << -1 << "error";
|
||||
QTest::newRow("invalid function") << QByteArray("{\"id\":42, \"method\":\"RemoteProxy.Explode\"}") << 42 << "error";
|
||||
QTest::newRow("invalid namespace") << QByteArray("{\"id\":42, \"method\":\"ProxyRemote.Hello\"}") << 42 << "error";
|
||||
QTest::newRow("missing dot") << QByteArray("{\"id\":42, \"method\":\"RemoteProxyHello\"}") << 42 << "error";
|
||||
@ -342,11 +463,22 @@ void RemoteProxyOfflineTests::apiBasicCalls()
|
||||
// Start the server
|
||||
startServer();
|
||||
|
||||
QVariant response = injectSocketData(data);
|
||||
qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented));
|
||||
QVariantMap response;
|
||||
|
||||
QCOMPARE(response.toMap().value("id").toInt(), responseId);
|
||||
QCOMPARE(response.toMap().value("status").toString(), responseStatus);
|
||||
// Websocket
|
||||
response = injectWebSocketData(data).toMap();
|
||||
//qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented));
|
||||
QVERIFY(!response.isEmpty());
|
||||
QCOMPARE(response.value("id").toInt(), responseId);
|
||||
QCOMPARE(response.value("status").toString(), responseStatus);
|
||||
|
||||
// TCP
|
||||
response.clear();
|
||||
response = injectTcpSocketData(data).toMap();
|
||||
//qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented));
|
||||
QVERIFY(!response.isEmpty());
|
||||
QCOMPARE(response.value("id").toInt(), responseId);
|
||||
QCOMPARE(response.value("status").toString(), responseStatus);
|
||||
|
||||
// Clean up
|
||||
stopServer();
|
||||
@ -409,8 +541,14 @@ void RemoteProxyOfflineTests::authenticate()
|
||||
params.insert("token", token);
|
||||
if (!nonce.isEmpty()) params.insert("nonce", nonce);
|
||||
|
||||
QVariant response = invokeApiCall("Authentication.Authenticate", params);
|
||||
qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented));
|
||||
// WebSocket
|
||||
QVariantMap response;
|
||||
response = invokeWebSocketApiCall("Authentication.Authenticate", params).toMap();
|
||||
//qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented));
|
||||
verifyAuthenticationError(response, expectedError);
|
||||
|
||||
// TCP
|
||||
response = invokeTcpSocketApiCall("Authentication.Authenticate", params).toMap();
|
||||
verifyAuthenticationError(response, expectedError);
|
||||
|
||||
// Clean up
|
||||
@ -518,6 +656,9 @@ void RemoteProxyOfflineTests::authenticateSendData()
|
||||
// Start the server
|
||||
startServer();
|
||||
|
||||
m_mockAuthenticator->setTimeoutDuration(100);
|
||||
m_mockAuthenticator->setExpectedAuthenticationError();
|
||||
|
||||
QVariantMap params;
|
||||
params.insert("uuid", "uuid");
|
||||
params.insert("name", "name");
|
||||
@ -533,18 +674,18 @@ void RemoteProxyOfflineTests::authenticateSendData()
|
||||
// Connect socket
|
||||
QWebSocket *socket = new QWebSocket("proxy-testclient", QWebSocketProtocol::Version13);
|
||||
connect(socket, &QWebSocket::sslErrors, this, &BaseTest::sslErrors);
|
||||
QSignalSpy spyConnection(socket, SIGNAL(connected()));
|
||||
QSignalSpy spyConnection(socket, &QWebSocket::connected);
|
||||
socket->open(Engine::instance()->webSocketServer()->serverUrl());
|
||||
spyConnection.wait();
|
||||
QVERIFY(spyConnection.count() == 1);
|
||||
|
||||
// Authenticate
|
||||
QSignalSpy dataSpy(socket, SIGNAL(textMessageReceived(QString)));
|
||||
QSignalSpy dataSpy(socket, &QWebSocket::textMessageReceived);
|
||||
socket->sendTextMessage(QString(jsonDoc.toJson(QJsonDocument::Compact)));
|
||||
dataSpy.wait();
|
||||
QVERIFY(dataSpy.count() == 1);
|
||||
|
||||
// Send data and make sure we get disconnected
|
||||
// Send data again and make sure we get disconnected since sending data while waiting for the partner is forbidden
|
||||
QSignalSpy disconnectedSpy(socket, SIGNAL(disconnected()));
|
||||
socket->sendTextMessage(QString(jsonDoc.toJson(QJsonDocument::Compact)));
|
||||
disconnectedSpy.wait();
|
||||
@ -556,7 +697,7 @@ void RemoteProxyOfflineTests::authenticateSendData()
|
||||
stopServer();
|
||||
}
|
||||
|
||||
void RemoteProxyOfflineTests::clientConnection()
|
||||
void RemoteProxyOfflineTests::clientConnectionWebSocket()
|
||||
{
|
||||
// Start the server
|
||||
startServer();
|
||||
@ -565,7 +706,7 @@ void RemoteProxyOfflineTests::clientConnection()
|
||||
m_mockAuthenticator->setTimeoutDuration(100);
|
||||
m_mockAuthenticator->setExpectedAuthenticationError();
|
||||
|
||||
// Connect to the server (insecure disabled)
|
||||
// Connect to the server using WebSocket (insecure disabled)
|
||||
RemoteProxyConnection *connection = new RemoteProxyConnection(QUuid::createUuid(), "Test client one", this);
|
||||
connect(connection, &RemoteProxyConnection::sslErrors, this, &BaseTest::ignoreConnectionSslError);
|
||||
|
||||
@ -606,6 +747,56 @@ void RemoteProxyOfflineTests::clientConnection()
|
||||
stopServer();
|
||||
}
|
||||
|
||||
void RemoteProxyOfflineTests::clientConnectionTcpSocket()
|
||||
{
|
||||
// Start the server
|
||||
startServer();
|
||||
|
||||
// Configure mock authenticator
|
||||
m_mockAuthenticator->setTimeoutDuration(100);
|
||||
m_mockAuthenticator->setExpectedAuthenticationError();
|
||||
|
||||
// Connect to the server using TCP (insecure disabled)
|
||||
RemoteProxyConnection *connection = new RemoteProxyConnection(QUuid::createUuid(), "Test client one", RemoteProxyConnection::ConnectionTypeTcpSocket, this);
|
||||
connect(connection, &RemoteProxyConnection::sslErrors, this, &BaseTest::ignoreConnectionSslError);
|
||||
|
||||
// Connect to server (insecue enabled for testing)
|
||||
QSignalSpy readySpy(connection, &RemoteProxyConnection::ready);
|
||||
QVERIFY(connection->connectServer(m_serverUrlTcp));
|
||||
readySpy.wait();
|
||||
QVERIFY(readySpy.count() == 1);
|
||||
QVERIFY(connection->isConnected());
|
||||
QVERIFY(!connection->isRemoteConnected());
|
||||
QVERIFY(connection->state() == RemoteProxyConnection::StateReady);
|
||||
QVERIFY(connection->error() == QAbstractSocket::UnknownSocketError);
|
||||
QVERIFY(connection->serverUrl() == m_serverUrlTcp);
|
||||
QVERIFY(connection->connectionType() == RemoteProxyConnection::ConnectionTypeTcpSocket);
|
||||
QVERIFY(connection->serverName() == SERVER_NAME_STRING);
|
||||
QVERIFY(connection->proxyServerName() == Engine::instance()->serverName());
|
||||
QVERIFY(connection->proxyServerVersion() == SERVER_VERSION_STRING);
|
||||
QVERIFY(connection->proxyServerApiVersion() == API_VERSION_STRING);
|
||||
|
||||
QSignalSpy authenticatedSpy(connection, &RemoteProxyConnection::authenticated);
|
||||
QVERIFY(connection->authenticate("foobar"));
|
||||
authenticatedSpy.wait();
|
||||
QVERIFY(authenticatedSpy.count() == 1);
|
||||
QVERIFY(connection->isConnected());
|
||||
QVERIFY(connection->isAuthenticated());
|
||||
QVERIFY(connection->state() == RemoteProxyConnection::StateAuthenticated);
|
||||
|
||||
// Disconnect and clean up
|
||||
QSignalSpy spyDisconnected(connection, &RemoteProxyConnection::disconnected);
|
||||
connection->disconnectServer();
|
||||
// FIXME: check why it waits the full time here
|
||||
spyDisconnected.wait(500);
|
||||
|
||||
QVERIFY(spyDisconnected.count() >= 1);
|
||||
QVERIFY(!connection->isConnected());
|
||||
|
||||
connection->deleteLater();
|
||||
stopServer();
|
||||
}
|
||||
|
||||
void RemoteProxyOfflineTests::remoteConnection()
|
||||
{
|
||||
// Start the server
|
||||
@ -912,6 +1103,10 @@ void RemoteProxyOfflineTests::jsonRpcTimeout()
|
||||
// Start the server
|
||||
startServer();
|
||||
|
||||
m_configuration->setAuthenticationTimeout(3000);
|
||||
m_configuration->setJsonRpcTimeout(1000);
|
||||
m_configuration->setInactiveTimeout(2000);
|
||||
|
||||
// Configure result (authentication takes longer than json rpc timeout
|
||||
m_mockAuthenticator->setExpectedAuthenticationError();
|
||||
m_mockAuthenticator->setTimeoutDuration(4000);
|
||||
@ -925,7 +1120,7 @@ void RemoteProxyOfflineTests::jsonRpcTimeout()
|
||||
params.insert("name", "name");
|
||||
params.insert("token", "token");
|
||||
|
||||
QVariant response = invokeApiCall("Authentication.Authenticate", params);
|
||||
QVariant response = invokeWebSocketApiCall("Authentication.Authenticate", params);
|
||||
qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented));
|
||||
|
||||
QVERIFY(response.toMap().value("status").toString() == "error");
|
||||
@ -964,7 +1159,7 @@ void RemoteProxyOfflineTests::authenticationReplyTimeout()
|
||||
|
||||
// Configure result (authentication takes longer than json rpc timeout
|
||||
m_mockAuthenticator->setExpectedAuthenticationError();
|
||||
m_mockAuthenticator->setTimeoutDuration(1000);
|
||||
m_mockAuthenticator->setTimeoutDuration(2000);
|
||||
|
||||
m_configuration->setAuthenticationTimeout(500);
|
||||
m_configuration->setJsonRpcTimeout(1000);
|
||||
@ -976,7 +1171,7 @@ void RemoteProxyOfflineTests::authenticationReplyTimeout()
|
||||
params.insert("name", "Sleepy test client");
|
||||
params.insert("token", "sleepy token zzzZZZ");
|
||||
|
||||
QVariant response = invokeApiCall("Authentication.Authenticate", params);
|
||||
QVariant response = invokeWebSocketApiCall("Authentication.Authenticate", params);
|
||||
qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented));
|
||||
verifyAuthenticationError(response, Authenticator::AuthenticationErrorTimeout);
|
||||
|
||||
@ -1017,4 +1212,192 @@ void RemoteProxyOfflineTests::authenticationReplyConnection()
|
||||
stopServer();
|
||||
}
|
||||
|
||||
void RemoteProxyOfflineTests::tcpRemoteConnection()
|
||||
{
|
||||
// Start the server
|
||||
startServer();
|
||||
|
||||
// Configure mock authenticator
|
||||
m_mockAuthenticator->setTimeoutDuration(100);
|
||||
m_mockAuthenticator->setExpectedAuthenticationError();
|
||||
|
||||
QString nameConnectionOne = "Test client one";
|
||||
QUuid uuidConnectionOne = QUuid::createUuid();
|
||||
|
||||
QString nameConnectionTwo = "Test client two";
|
||||
QUuid uuidConnectionTwo = QUuid::createUuid();
|
||||
|
||||
QByteArray dataOne = "Hello from client one :-)";
|
||||
QByteArray dataTwo = "Hello from client two :-)";
|
||||
|
||||
// Create two connection
|
||||
RemoteProxyConnection *connectionOne = new RemoteProxyConnection(uuidConnectionOne, nameConnectionOne, RemoteProxyConnection::ConnectionTypeTcpSocket, this);
|
||||
connect(connectionOne, &RemoteProxyConnection::sslErrors, this, &BaseTest::ignoreConnectionSslError);
|
||||
|
||||
RemoteProxyConnection *connectionTwo = new RemoteProxyConnection(uuidConnectionTwo, nameConnectionTwo, RemoteProxyConnection::ConnectionTypeTcpSocket, this);
|
||||
connect(connectionTwo, &RemoteProxyConnection::sslErrors, this, &BaseTest::ignoreConnectionSslError);
|
||||
|
||||
// Connect one
|
||||
QSignalSpy connectionOneReadySpy(connectionOne, &RemoteProxyConnection::ready);
|
||||
QVERIFY(connectionOne->connectServer(m_serverUrlTcp));
|
||||
connectionOneReadySpy.wait();
|
||||
QVERIFY(connectionOneReadySpy.count() == 1);
|
||||
QVERIFY(connectionOne->isConnected());
|
||||
|
||||
// Connect two
|
||||
QSignalSpy connectionTwoReadySpy(connectionTwo, &RemoteProxyConnection::ready);
|
||||
QVERIFY(connectionTwo->connectServer(m_serverUrlTcp));
|
||||
connectionTwoReadySpy.wait();
|
||||
QVERIFY(connectionTwoReadySpy.count() == 1);
|
||||
QVERIFY(connectionTwo->isConnected());
|
||||
|
||||
// Authenticate one
|
||||
QSignalSpy remoteConnectionEstablishedOne(connectionOne, &RemoteProxyConnection::remoteConnectionEstablished);
|
||||
QSignalSpy connectionOneAuthenticatedSpy(connectionOne, &RemoteProxyConnection::authenticated);
|
||||
QVERIFY(connectionOne->authenticate(m_testToken));
|
||||
connectionOneAuthenticatedSpy.wait();
|
||||
QVERIFY(connectionOneAuthenticatedSpy.count() == 1);
|
||||
QVERIFY(connectionOne->isConnected());
|
||||
QVERIFY(connectionOne->isAuthenticated());
|
||||
QVERIFY(connectionOne->state() == RemoteProxyConnection::StateAuthenticated);
|
||||
|
||||
// Authenticate two
|
||||
QSignalSpy remoteConnectionEstablishedTwo(connectionTwo, &RemoteProxyConnection::remoteConnectionEstablished);
|
||||
QSignalSpy connectionTwoAuthenticatedSpy(connectionTwo, &RemoteProxyConnection::authenticated);
|
||||
QVERIFY(connectionTwo->authenticate(m_testToken));
|
||||
connectionTwoAuthenticatedSpy.wait();
|
||||
qDebug() << connectionTwoAuthenticatedSpy.count();
|
||||
QVERIFY(connectionTwoAuthenticatedSpy.count() == 1);
|
||||
QVERIFY(connectionTwo->isConnected());
|
||||
QVERIFY(connectionTwo->isAuthenticated());
|
||||
|
||||
// Wait for both to be connected
|
||||
remoteConnectionEstablishedOne.wait(500);
|
||||
remoteConnectionEstablishedTwo.wait(500);
|
||||
|
||||
QVERIFY(remoteConnectionEstablishedOne.count() == 1);
|
||||
QVERIFY(remoteConnectionEstablishedTwo.count() == 1);
|
||||
QVERIFY(connectionOne->state() == RemoteProxyConnection::StateRemoteConnected);
|
||||
QVERIFY(connectionTwo->state() == RemoteProxyConnection::StateRemoteConnected);
|
||||
|
||||
QCOMPARE(connectionOne->tunnelPartnerName(), nameConnectionTwo);
|
||||
QCOMPARE(connectionOne->tunnelPartnerUuid(), uuidConnectionTwo.toString());
|
||||
QCOMPARE(connectionTwo->tunnelPartnerName(), nameConnectionOne);
|
||||
QCOMPARE(connectionTwo->tunnelPartnerUuid(), uuidConnectionOne.toString());
|
||||
|
||||
// Pipe data trought the tunnel
|
||||
QSignalSpy remoteConnectionDataOne(connectionOne, &RemoteProxyConnection::dataReady);
|
||||
QSignalSpy remoteConnectionDataTwo(connectionTwo, &RemoteProxyConnection::dataReady);
|
||||
|
||||
connectionOne->sendData(dataOne);
|
||||
remoteConnectionDataTwo.wait(500);
|
||||
QVERIFY(remoteConnectionDataTwo.count() == 1);
|
||||
QCOMPARE(remoteConnectionDataTwo.at(0).at(0).toByteArray().trimmed(), dataOne);
|
||||
|
||||
connectionTwo->sendData(dataTwo);
|
||||
remoteConnectionDataOne.wait(500);
|
||||
QVERIFY(remoteConnectionDataOne.count() == 1);
|
||||
QCOMPARE(remoteConnectionDataOne.at(0).at(0).toByteArray().trimmed(), dataTwo);
|
||||
|
||||
connectionOne->deleteLater();
|
||||
connectionTwo->deleteLater();
|
||||
|
||||
// Clean up
|
||||
stopServer();
|
||||
}
|
||||
|
||||
void RemoteProxyOfflineTests::tcpWebsocketRemoteConnection()
|
||||
{
|
||||
// Start the server
|
||||
startServer();
|
||||
|
||||
// Configure mock authenticator
|
||||
m_mockAuthenticator->setTimeoutDuration(100);
|
||||
m_mockAuthenticator->setExpectedAuthenticationError();
|
||||
|
||||
QString nameConnectionOne = "Test client one";
|
||||
QUuid uuidConnectionOne = QUuid::createUuid();
|
||||
|
||||
QString nameConnectionTwo = "Test client two";
|
||||
QUuid uuidConnectionTwo = QUuid::createUuid();
|
||||
|
||||
QByteArray dataOne = "Hello from client one :-)";
|
||||
QByteArray dataTwo = "Hello from client two :-)";
|
||||
|
||||
// Create two connection
|
||||
RemoteProxyConnection *connectionOne = new RemoteProxyConnection(uuidConnectionOne, nameConnectionOne, RemoteProxyConnection::ConnectionTypeWebSocket, this);
|
||||
connect(connectionOne, &RemoteProxyConnection::sslErrors, this, &BaseTest::ignoreConnectionSslError);
|
||||
|
||||
RemoteProxyConnection *connectionTwo = new RemoteProxyConnection(uuidConnectionTwo, nameConnectionTwo, RemoteProxyConnection::ConnectionTypeTcpSocket, this);
|
||||
connect(connectionTwo, &RemoteProxyConnection::sslErrors, this, &BaseTest::ignoreConnectionSslError);
|
||||
|
||||
// Connect one
|
||||
QSignalSpy connectionOneReadySpy(connectionOne, &RemoteProxyConnection::ready);
|
||||
QVERIFY(connectionOne->connectServer(m_serverUrl));
|
||||
connectionOneReadySpy.wait();
|
||||
QVERIFY(connectionOneReadySpy.count() == 1);
|
||||
QVERIFY(connectionOne->isConnected());
|
||||
|
||||
// Connect two
|
||||
QSignalSpy connectionTwoReadySpy(connectionTwo, &RemoteProxyConnection::ready);
|
||||
QVERIFY(connectionTwo->connectServer(m_serverUrlTcp));
|
||||
connectionTwoReadySpy.wait();
|
||||
QVERIFY(connectionTwoReadySpy.count() == 1);
|
||||
QVERIFY(connectionTwo->isConnected());
|
||||
|
||||
// Authenticate one
|
||||
QSignalSpy remoteConnectionEstablishedOne(connectionOne, &RemoteProxyConnection::remoteConnectionEstablished);
|
||||
QSignalSpy connectionOneAuthenticatedSpy(connectionOne, &RemoteProxyConnection::authenticated);
|
||||
QVERIFY(connectionOne->authenticate(m_testToken));
|
||||
connectionOneAuthenticatedSpy.wait();
|
||||
QVERIFY(connectionOneAuthenticatedSpy.count() == 1);
|
||||
QVERIFY(connectionOne->isConnected());
|
||||
QVERIFY(connectionOne->isAuthenticated());
|
||||
QVERIFY(connectionOne->state() == RemoteProxyConnection::StateAuthenticated);
|
||||
|
||||
// Authenticate two
|
||||
QSignalSpy remoteConnectionEstablishedTwo(connectionTwo, &RemoteProxyConnection::remoteConnectionEstablished);
|
||||
QSignalSpy connectionTwoAuthenticatedSpy(connectionTwo, &RemoteProxyConnection::authenticated);
|
||||
QVERIFY(connectionTwo->authenticate(m_testToken));
|
||||
connectionTwoAuthenticatedSpy.wait();
|
||||
qDebug() << connectionTwoAuthenticatedSpy.count();
|
||||
QVERIFY(connectionTwoAuthenticatedSpy.count() == 1);
|
||||
QVERIFY(connectionTwo->isConnected());
|
||||
QVERIFY(connectionTwo->isAuthenticated());
|
||||
|
||||
// Wait for both to be connected
|
||||
remoteConnectionEstablishedOne.wait(500);
|
||||
remoteConnectionEstablishedTwo.wait(500);
|
||||
|
||||
QVERIFY(remoteConnectionEstablishedOne.count() == 1);
|
||||
QVERIFY(remoteConnectionEstablishedTwo.count() == 1);
|
||||
QVERIFY(connectionOne->state() == RemoteProxyConnection::StateRemoteConnected);
|
||||
QVERIFY(connectionTwo->state() == RemoteProxyConnection::StateRemoteConnected);
|
||||
|
||||
QCOMPARE(connectionOne->tunnelPartnerName(), nameConnectionTwo);
|
||||
QCOMPARE(connectionOne->tunnelPartnerUuid(), uuidConnectionTwo.toString());
|
||||
QCOMPARE(connectionTwo->tunnelPartnerName(), nameConnectionOne);
|
||||
QCOMPARE(connectionTwo->tunnelPartnerUuid(), uuidConnectionOne.toString());
|
||||
|
||||
// Pipe data trought the tunnel
|
||||
QSignalSpy remoteConnectionDataOne(connectionOne, &RemoteProxyConnection::dataReady);
|
||||
QSignalSpy remoteConnectionDataTwo(connectionTwo, &RemoteProxyConnection::dataReady);
|
||||
|
||||
connectionOne->sendData(dataOne);
|
||||
remoteConnectionDataTwo.wait(500);
|
||||
QVERIFY(remoteConnectionDataTwo.count() == 1);
|
||||
QCOMPARE(remoteConnectionDataTwo.at(0).at(0).toByteArray().trimmed(), dataOne);
|
||||
|
||||
connectionTwo->sendData(dataTwo);
|
||||
remoteConnectionDataOne.wait(500);
|
||||
QVERIFY(remoteConnectionDataOne.count() == 1);
|
||||
QCOMPARE(remoteConnectionDataOne.at(0).at(0).toByteArray().trimmed(), dataTwo);
|
||||
|
||||
connectionOne->deleteLater();
|
||||
connectionTwo->deleteLater();
|
||||
|
||||
// Clean up
|
||||
stopServer();
|
||||
}
|
||||
|
||||
QTEST_MAIN(RemoteProxyOfflineTests)
|
||||
|
||||
@ -54,7 +54,7 @@ private slots:
|
||||
void websocketBinaryData();
|
||||
void websocketPing();
|
||||
|
||||
// Api
|
||||
// WebSocket connection API
|
||||
void getIntrospect();
|
||||
void getHello();
|
||||
|
||||
@ -68,18 +68,23 @@ private slots:
|
||||
void authenticateSendData();
|
||||
|
||||
// Client lib
|
||||
void clientConnection();
|
||||
void clientConnectionTcpSocket();
|
||||
void clientConnectionWebSocket();
|
||||
void remoteConnection();
|
||||
void multipleRemoteConnection();
|
||||
void trippleConnection();
|
||||
void duplicateUuid();
|
||||
void sslConfigurations();
|
||||
|
||||
void jsonRpcTimeout();
|
||||
void inactiveTimeout();
|
||||
void jsonRpcTimeout();
|
||||
void authenticationReplyTimeout();
|
||||
void authenticationReplyConnection();
|
||||
|
||||
// TCP Websocket combinations
|
||||
void tcpRemoteConnection();
|
||||
void tcpWebsocketRemoteConnection();
|
||||
|
||||
};
|
||||
|
||||
#endif // NYMEA_REMOTEPROXY_TESTS_OFFLINE_H
|
||||
|
||||
@ -10,5 +10,5 @@ HEADERS += nymea-remoteproxy-tests-offline.h
|
||||
|
||||
SOURCES += nymea-remoteproxy-tests-offline.cpp
|
||||
|
||||
target.path = /usr/bin
|
||||
target.path = $$[QT_INSTALL_PREFIX]/bin
|
||||
INSTALLS += target
|
||||
|
||||
@ -7,5 +7,5 @@ HEADERS += nymea-remoteproxy-tests-online.h
|
||||
|
||||
SOURCES += nymea-remoteproxy-tests-online.cpp
|
||||
|
||||
target.path = /usr/bin
|
||||
target.path = $$[QT_INSTALL_PREFIX]/bin
|
||||
INSTALLS += target
|
||||
|
||||
@ -31,8 +31,10 @@
|
||||
#include "loggingcategories.h"
|
||||
#include "remoteproxyconnection.h"
|
||||
|
||||
#include <QSslError>
|
||||
#include <QMetaType>
|
||||
#include <QSignalSpy>
|
||||
#include <QSslSocket>
|
||||
#include <QWebSocket>
|
||||
#include <QJsonDocument>
|
||||
#include <QWebSocketServer>
|
||||
@ -47,16 +49,34 @@ void BaseTest::loadConfiguration(const QString &fileName)
|
||||
{
|
||||
qDebug() << "Load test configurations" << fileName;
|
||||
m_configuration->loadConfiguration(fileName);
|
||||
restartEngine();
|
||||
//restartEngine();
|
||||
}
|
||||
|
||||
void BaseTest::cleanUpEngine()
|
||||
{
|
||||
qDebug() << "Clean up engine";
|
||||
if (Engine::exists()) {
|
||||
Engine::instance()->stop();
|
||||
Engine::instance()->destroy();
|
||||
QVERIFY(!Engine::exists());
|
||||
}
|
||||
|
||||
if (m_mockAuthenticator) {
|
||||
delete m_mockAuthenticator;
|
||||
m_mockAuthenticator = nullptr;
|
||||
}
|
||||
|
||||
if (m_dummyAuthenticator) {
|
||||
delete m_dummyAuthenticator;
|
||||
m_dummyAuthenticator = nullptr;
|
||||
}
|
||||
|
||||
m_authenticator = nullptr;
|
||||
|
||||
if (m_configuration) {
|
||||
delete m_configuration;
|
||||
m_configuration = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void BaseTest::restartEngine()
|
||||
@ -67,28 +87,36 @@ void BaseTest::restartEngine()
|
||||
|
||||
void BaseTest::startEngine()
|
||||
{
|
||||
m_configuration = new ProxyConfiguration(this);
|
||||
loadConfiguration(":/test-configuration.conf");
|
||||
|
||||
m_dummyAuthenticator = new DummyAuthenticator(this);
|
||||
m_mockAuthenticator = new MockAuthenticator(this);
|
||||
m_authenticator = qobject_cast<Authenticator *>(m_mockAuthenticator);
|
||||
if (!Engine::exists()) {
|
||||
Engine::instance()->setAuthenticator(m_authenticator);
|
||||
Engine::instance()->setDeveloperModeEnabled(true);
|
||||
QVERIFY(Engine::exists());
|
||||
QVERIFY(!Engine::instance()->running());
|
||||
}
|
||||
}
|
||||
|
||||
void BaseTest::startServer()
|
||||
{
|
||||
startEngine();
|
||||
restartEngine();
|
||||
|
||||
if (!Engine::instance()->running()) {
|
||||
QSignalSpy runningSpy(Engine::instance(), &Engine::runningChanged);
|
||||
Engine::instance()->setDeveloperModeEnabled(true);
|
||||
Engine::instance()->start(m_configuration);
|
||||
runningSpy.wait(100);
|
||||
runningSpy.wait(200);
|
||||
QVERIFY(runningSpy.count() == 1);
|
||||
}
|
||||
|
||||
QVERIFY(Engine::instance()->running());
|
||||
QVERIFY(Engine::instance()->developerMode());
|
||||
QVERIFY(Engine::instance()->webSocketServer()->running());
|
||||
QVERIFY(Engine::instance()->tcpSocketServer()->running());
|
||||
QVERIFY(Engine::instance()->monitorServer()->running());
|
||||
}
|
||||
|
||||
@ -99,9 +127,11 @@ void BaseTest::stopServer()
|
||||
|
||||
Engine::instance()->stop();
|
||||
QVERIFY(!Engine::instance()->running());
|
||||
|
||||
cleanUpEngine();
|
||||
}
|
||||
|
||||
QVariant BaseTest::invokeApiCall(const QString &method, const QVariantMap params, bool remainsConnected)
|
||||
QVariant BaseTest::invokeWebSocketApiCall(const QString &method, const QVariantMap params, bool remainsConnected)
|
||||
{
|
||||
Q_UNUSED(remainsConnected)
|
||||
|
||||
@ -152,11 +182,12 @@ QVariant BaseTest::invokeApiCall(const QString &method, const QVariantMap params
|
||||
return jsonDoc.toVariant();
|
||||
}
|
||||
}
|
||||
|
||||
m_commandCounter++;
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariant BaseTest::injectSocketData(const QByteArray &data)
|
||||
QVariant BaseTest::injectWebSocketData(const QByteArray &data)
|
||||
{
|
||||
QWebSocket *socket = new QWebSocket("proxy-testclient", QWebSocketProtocol::Version13);
|
||||
connect(socket, &QWebSocket::sslErrors, this, &BaseTest::sslErrors);
|
||||
@ -195,6 +226,66 @@ QVariant BaseTest::injectSocketData(const QByteArray &data)
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariant BaseTest::invokeTcpSocketApiCall(const QString &method, const QVariantMap params, bool remainsConnected)
|
||||
{
|
||||
Q_UNUSED(remainsConnected)
|
||||
|
||||
QVariantMap request;
|
||||
request.insert("id", m_commandCounter);
|
||||
request.insert("method", method);
|
||||
request.insert("params", params);
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromVariant(request);
|
||||
|
||||
QSslSocket *socket = new QSslSocket(this);
|
||||
typedef void (QSslSocket:: *sslErrorsSignal)(const QList<QSslError> &);
|
||||
QObject::connect(socket, static_cast<sslErrorsSignal>(&QSslSocket::sslErrors), this, &BaseTest::sslSocketSslErrors);
|
||||
|
||||
QSignalSpy spyConnection(socket, &QSslSocket::connected);
|
||||
socket->connectToHostEncrypted(Engine::instance()->tcpSocketServer()->serverUrl().host(),
|
||||
static_cast<quint16>(Engine::instance()->tcpSocketServer()->serverUrl().port()));
|
||||
spyConnection.wait();
|
||||
if (spyConnection.count() == 0) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QSignalSpy dataSpy(socket, &QSslSocket::readyRead);
|
||||
socket->write(jsonDoc.toJson(QJsonDocument::Compact) + '\n');
|
||||
// FIXME: check why it waits the full time here
|
||||
dataSpy.wait(500);
|
||||
if (dataSpy.count() != 1) {
|
||||
qWarning() << "No data received";
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QByteArray data = socket->readAll();
|
||||
socket->close();
|
||||
socket->deleteLater();
|
||||
|
||||
// Make sure the response ends with '}\n'
|
||||
if (!data.endsWith("}\n")) {
|
||||
qWarning() << "JSON data does not end with \"}\n\"";
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
// Make sure the response it a valid JSON string
|
||||
QJsonParseError error;
|
||||
jsonDoc = QJsonDocument::fromJson(data, &error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
qWarning() << "JSON parser error" << error.errorString();
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariantMap response = jsonDoc.toVariant().toMap();
|
||||
|
||||
if (response.value("id").toInt() == m_commandCounter) {
|
||||
m_commandCounter++;
|
||||
return jsonDoc.toVariant();
|
||||
}
|
||||
|
||||
m_commandCounter++;
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
bool BaseTest::createRemoteConnection(const QString &token, const QString &nonce, QObject *parent)
|
||||
{
|
||||
// Configure mock authenticator
|
||||
@ -308,20 +399,58 @@ bool BaseTest::createRemoteConnection(const QString &token, const QString &nonce
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
QVariant BaseTest::injectTcpSocketData(const QByteArray &data)
|
||||
{
|
||||
QSslSocket *socket = new QSslSocket(this);
|
||||
typedef void (QSslSocket:: *sslErrorsSignal)(const QList<QSslError> &);
|
||||
QObject::connect(socket, static_cast<sslErrorsSignal>(&QSslSocket::sslErrors), this, &BaseTest::sslSocketSslErrors);
|
||||
|
||||
QSignalSpy spyConnection(socket, &QSslSocket::connected);
|
||||
socket->connectToHostEncrypted(Engine::instance()->tcpSocketServer()->serverUrl().host(),
|
||||
static_cast<quint16>(Engine::instance()->tcpSocketServer()->serverUrl().port()));
|
||||
spyConnection.wait();
|
||||
if (spyConnection.count() == 0) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QSignalSpy dataSpy(socket, &QSslSocket::readyRead);
|
||||
socket->write(data + '\n');
|
||||
dataSpy.wait();
|
||||
// FIXME: check why it waits the full time here
|
||||
dataSpy.wait(500);
|
||||
if (dataSpy.count() != 1) {
|
||||
qWarning() << "No data received";
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QByteArray socketData = socket->readAll();
|
||||
socket->close();
|
||||
socket->deleteLater();
|
||||
|
||||
// Make sure the response ends with '}\n'
|
||||
if (!socketData.endsWith("}\n")) {
|
||||
qWarning() << "JSON data does not end with \"}\n\"";
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
// Make sure the response it a valid JSON string
|
||||
QJsonParseError error;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(socketData, &error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
qWarning() << "JSON parser error" << error.errorString();
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
m_commandCounter++;
|
||||
return jsonDoc.toVariant();
|
||||
}
|
||||
|
||||
void BaseTest::initTestCase()
|
||||
{
|
||||
qRegisterMetaType<RemoteProxyConnection::State>();
|
||||
qRegisterMetaType<RemoteProxyConnection::ConnectionType>();
|
||||
|
||||
m_configuration = new ProxyConfiguration(this);
|
||||
m_configuration->loadConfiguration(":/test-configuration.conf");
|
||||
|
||||
m_mockAuthenticator = new MockAuthenticator(this);
|
||||
m_dummyAuthenticator = new DummyAuthenticator(this);
|
||||
//m_awsAuthenticator = new AwsAuthenticator(m_configuration->awsCredentialsUrl(), this);
|
||||
|
||||
m_authenticator = qobject_cast<Authenticator *>(m_mockAuthenticator);
|
||||
|
||||
m_testToken = "eyJraWQiOiJXdnFFT3prVVh5VDlINzFyRUpoNWdxRnkxNFhnR2l3SFAzVEIzUFQ1V3ZrPSIsImFsZyI6IlJT"
|
||||
"MjU2In0.eyJzdWIiOiJmZTJmZDNlNC1hMGJhLTQ1OTUtOWRiZS00ZDkxYjRiMjFlMzUiLCJhdWQiOiI4cmpoZ"
|
||||
"mRsZjlqZjFzdW9rMmpjcmx0ZDZ2IiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV2ZW50X2lkIjoiN2Y5NTRiNm"
|
||||
@ -335,20 +464,13 @@ void BaseTest::initTestCase()
|
||||
"pQbj58v1vktaAEATdmKmlzmcix-HJK9wWHRSuv3TsNa8DGxvcPOoeTu8Vql7krZ-y7Zu-s2WsgeP4VxyT80VE"
|
||||
"T_xh6pMkOhE6g";
|
||||
|
||||
qCDebug(dcApplication()) << "Init test case.";
|
||||
restartEngine();
|
||||
qCDebug(dcApplication()) << "Init test case done.";
|
||||
//restartEngine();
|
||||
}
|
||||
|
||||
void BaseTest::cleanupTestCase()
|
||||
{
|
||||
qCDebug(dcApplication()) << "Clean up test case.";
|
||||
delete m_configuration;
|
||||
delete m_mockAuthenticator;
|
||||
delete m_dummyAuthenticator;
|
||||
//delete m_awsAuthenticator;
|
||||
|
||||
m_authenticator = nullptr;
|
||||
|
||||
cleanUpEngine();
|
||||
}
|
||||
|
||||
|
||||
@ -57,6 +57,7 @@ protected:
|
||||
ProxyConfiguration *m_configuration = nullptr;
|
||||
|
||||
QUrl m_serverUrl = QUrl("wss://127.0.0.1:1212");
|
||||
QUrl m_serverUrlTcp = QUrl("ssl://127.0.0.1:1213");
|
||||
|
||||
QSslConfiguration m_sslConfiguration;
|
||||
|
||||
@ -78,8 +79,11 @@ protected:
|
||||
void startServer();
|
||||
void stopServer();
|
||||
|
||||
QVariant invokeApiCall(const QString &method, const QVariantMap params = QVariantMap(), bool remainsConnected = true);
|
||||
QVariant injectSocketData(const QByteArray &data);
|
||||
QVariant invokeWebSocketApiCall(const QString &method, const QVariantMap params = QVariantMap(), bool remainsConnected = true);
|
||||
QVariant injectWebSocketData(const QByteArray &data);
|
||||
|
||||
QVariant invokeTcpSocketApiCall(const QString &method, const QVariantMap params = QVariantMap(), bool remainsConnected = true);
|
||||
QVariant injectTcpSocketData(const QByteArray &data);
|
||||
|
||||
bool createRemoteConnection(const QString &token, const QString &nonce, QObject *parent);
|
||||
|
||||
@ -88,8 +92,13 @@ protected slots:
|
||||
void cleanupTestCase();
|
||||
|
||||
public slots:
|
||||
inline void sslSocketSslErrors(const QList<QSslError> &) {
|
||||
QSslSocket *socket = static_cast<QSslSocket *>(sender());
|
||||
socket->ignoreSslErrors();
|
||||
}
|
||||
|
||||
inline void sslErrors(const QList<QSslError> &) {
|
||||
QWebSocket *socket = static_cast<QWebSocket*>(sender());
|
||||
QWebSocket *socket = static_cast<QWebSocket *>(sender());
|
||||
socket->ignoreSslErrors();
|
||||
}
|
||||
|
||||
|
||||
@ -55,8 +55,7 @@ void MockAuthenticator::replyFinished()
|
||||
{
|
||||
MockAuthenticationReply *reply = static_cast<MockAuthenticationReply *>(sender());
|
||||
|
||||
qCDebug(dcAuthentication()) << name() << "Authentication finished.";
|
||||
|
||||
qCDebug(dcAuthentication()) << name() << "Authentication finished" << reply << reply->authenticationReply();
|
||||
setReplyError(reply->authenticationReply(), reply->error());
|
||||
setReplyFinished(reply->authenticationReply());
|
||||
reply->deleteLater();
|
||||
@ -64,7 +63,7 @@ void MockAuthenticator::replyFinished()
|
||||
|
||||
AuthenticationReply *MockAuthenticator::authenticate(ProxyClient *proxyClient)
|
||||
{
|
||||
qCDebug(dcAuthentication()) << name() << "Start authentication for" << proxyClient << "using token" << proxyClient->token();
|
||||
qCDebug(dcAuthentication()) << name() << "Start authentication for" << proxyClient << "using token" << proxyClient->token() << "Auth duration" << m_timeoutDuration << "[ms]";
|
||||
|
||||
AuthenticationReply *authenticationReply = createAuthenticationReply(proxyClient, proxyClient);
|
||||
|
||||
@ -84,3 +83,8 @@ MockAuthenticationReply::MockAuthenticationReply(int timeout, Authenticator::Aut
|
||||
QTimer::singleShot(timeout, this, &MockAuthenticationReply::finished);
|
||||
}
|
||||
|
||||
MockAuthenticationReply::~MockAuthenticationReply()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -40,6 +40,7 @@ class MockAuthenticationReply : public QObject
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MockAuthenticationReply(int timeout, Authenticator::AuthenticationError error, AuthenticationReply *authenticationReply, QObject *parent = nullptr);
|
||||
~MockAuthenticationReply();
|
||||
|
||||
AuthenticationReply *authenticationReply() const { return m_authenticationReply; }
|
||||
Authenticator::AuthenticationError error() const { return m_error; }
|
||||
@ -66,7 +67,7 @@ public:
|
||||
void setExpectedAuthenticationError(Authenticator::AuthenticationError error = AuthenticationErrorNoError);
|
||||
|
||||
private:
|
||||
int m_timeoutDuration = 1000;
|
||||
int m_timeoutDuration = 500;
|
||||
Authenticator::AuthenticationError m_expectedError;
|
||||
|
||||
private slots:
|
||||
|
||||
Reference in New Issue
Block a user