531 lines
18 KiB
C++
531 lines
18 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
*
|
|
* Copyright (C) 2013 - 2024, nymea GmbH
|
|
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
|
|
*
|
|
* This file is part of nymea-remoteproxy.
|
|
*
|
|
* nymea-remoteproxy 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.
|
|
*
|
|
* nymea-remoteproxy 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 nymea-remoteproxy. If not, see <https://www.gnu.org/licenses/>.
|
|
*
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
#include "basetest.h"
|
|
|
|
#include "engine.h"
|
|
#include "loggingcategories.h"
|
|
|
|
#include "../common/slipdataprocessor.h"
|
|
|
|
#include <QSslError>
|
|
#include <QMetaType>
|
|
#include <QSignalSpy>
|
|
#include <QSslSocket>
|
|
#include <QWebSocket>
|
|
#include <QJsonDocument>
|
|
#include <QWebSocketServer>
|
|
|
|
BaseTest::BaseTest(QObject *parent) :
|
|
QObject(parent)
|
|
{
|
|
resetDebugCategories();
|
|
|
|
qRegisterMetaType<TransportClient*>("TransportClient*");
|
|
}
|
|
|
|
void BaseTest::loadConfiguration(const QString &fileName)
|
|
{
|
|
qDebug() << "Load test configurations" << fileName;
|
|
m_configuration->loadConfiguration(fileName);
|
|
//restartEngine();
|
|
}
|
|
|
|
void BaseTest::resetDebugCategories()
|
|
{
|
|
m_currentDebugCategories = m_defaultDebugCategories;
|
|
QLoggingCategory::setFilterRules(m_currentDebugCategories);
|
|
}
|
|
|
|
void BaseTest::addDebugCategory(const QString &debugCategory)
|
|
{
|
|
m_currentDebugCategories += debugCategory + "\n";
|
|
//qDebug() << m_currentDebugCategories;
|
|
QLoggingCategory::setFilterRules(m_currentDebugCategories);
|
|
}
|
|
|
|
void BaseTest::cleanUpEngine()
|
|
{
|
|
qDebug() << "Clean up engine";
|
|
if (Engine::exists()) {
|
|
Engine::instance()->stop();
|
|
Engine::instance()->destroy();
|
|
QVERIFY(!Engine::exists());
|
|
}
|
|
|
|
if (m_configuration) {
|
|
m_configuration = nullptr;
|
|
}
|
|
}
|
|
|
|
void BaseTest::restartEngine()
|
|
{
|
|
cleanUpEngine();
|
|
startEngine();
|
|
}
|
|
|
|
void BaseTest::startEngine()
|
|
{
|
|
if (!m_configuration)
|
|
m_configuration = new ProxyConfiguration(Engine::instance());
|
|
|
|
loadConfiguration(":/test-configuration.conf");
|
|
|
|
QSignalSpy runningSpy(Engine::instance(), &Engine::runningChanged);
|
|
Engine::instance()->start(m_configuration);
|
|
runningSpy.wait();
|
|
QVERIFY(runningSpy.count() == 1);
|
|
QVERIFY(Engine::instance()->running());
|
|
}
|
|
|
|
void BaseTest::startServer()
|
|
{
|
|
restartEngine();
|
|
|
|
QVERIFY(Engine::instance()->running());
|
|
QVERIFY(Engine::instance()->webSocketServerTunnelProxy()->running());
|
|
QVERIFY(Engine::instance()->tcpSocketServerTunnelProxy()->running());
|
|
QVERIFY(Engine::instance()->unixSocketServerTunnelProxy()->running());
|
|
QVERIFY(Engine::instance()->tunnelProxyServer()->running());
|
|
QVERIFY(Engine::instance()->monitorServer()->running());
|
|
}
|
|
|
|
void BaseTest::stopServer()
|
|
{
|
|
if (!Engine::instance()->running())
|
|
return;
|
|
|
|
Engine::instance()->stop();
|
|
QVERIFY(!Engine::instance()->running());
|
|
|
|
cleanUpEngine();
|
|
}
|
|
|
|
|
|
QVariant BaseTest::invokeWebSocketTunnelProxyApiCall(const QString &method, const QVariantMap params)
|
|
{
|
|
QVariantMap request;
|
|
request.insert("id", m_commandCounter);
|
|
request.insert("method", method);
|
|
request.insert("params", params);
|
|
QJsonDocument jsonDoc = QJsonDocument::fromVariant(request);
|
|
|
|
QWebSocket *socket = new QWebSocket("tunnelproxy-testclient", QWebSocketProtocol::Version13);
|
|
connect(socket, &QWebSocket::sslErrors, this, &BaseTest::sslErrors);
|
|
QSignalSpy spyConnection(socket, SIGNAL(connected()));
|
|
socket->open(Engine::instance()->webSocketServerTunnelProxy()->serverUrl());
|
|
spyConnection.wait();
|
|
if (spyConnection.count() == 0) {
|
|
qWarning() << "Failed to connect websocket on tunnel proxy";
|
|
return QVariant();
|
|
}
|
|
|
|
QSignalSpy dataSpy(socket, SIGNAL(textMessageReceived(QString)));
|
|
socket->sendTextMessage(QString(jsonDoc.toJson(QJsonDocument::Compact)));
|
|
dataSpy.wait();
|
|
|
|
socket->close();
|
|
socket->deleteLater();
|
|
|
|
for (int i = 0; i < dataSpy.count(); i++) {
|
|
// Make sure the response ends with '}\n'
|
|
if (!dataSpy.at(i).last().toByteArray().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(dataSpy.at(i).last().toByteArray(), &error);
|
|
if (error.error != QJsonParseError::NoError) {
|
|
qWarning() << "JSON parser error" << error.errorString();
|
|
return QVariant();
|
|
}
|
|
QVariantMap response = jsonDoc.toVariant().toMap();
|
|
|
|
// skip notifications
|
|
if (response.contains("notification"))
|
|
continue;
|
|
|
|
if (response.value("id").toInt() == m_commandCounter) {
|
|
m_commandCounter++;
|
|
return jsonDoc.toVariant();
|
|
}
|
|
}
|
|
|
|
m_commandCounter++;
|
|
return QVariant();
|
|
}
|
|
|
|
QVariant BaseTest::injectWebSocketTunnelProxyData(const QByteArray &data)
|
|
{
|
|
QWebSocket *socket = new QWebSocket("tunnelproxy-testclient", QWebSocketProtocol::Version13);
|
|
connect(socket, &QWebSocket::sslErrors, this, &BaseTest::sslErrors);
|
|
|
|
QSignalSpy spyConnection(socket, SIGNAL(connected()));
|
|
socket->open(Engine::instance()->webSocketServerTunnelProxy()->serverUrl());
|
|
spyConnection.wait();
|
|
if (spyConnection.count() == 0) {
|
|
return QVariant();
|
|
}
|
|
|
|
QSignalSpy spy(socket, SIGNAL(textMessageReceived(QString)));
|
|
socket->sendTextMessage(QString(data));
|
|
spy.wait();
|
|
|
|
socket->close();
|
|
socket->deleteLater();
|
|
|
|
for (int i = 0; i < spy.count(); i++) {
|
|
// Make sure the response ends with '}\n'
|
|
if (!spy.at(i).last().toByteArray().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(spy.at(i).last().toByteArray(), &error);
|
|
if (error.error != QJsonParseError::NoError) {
|
|
qWarning() << "JSON parser error" << error.errorString();
|
|
return QVariant();
|
|
}
|
|
return jsonDoc.toVariant();
|
|
}
|
|
m_commandCounter++;
|
|
return QVariant();
|
|
}
|
|
|
|
QVariant BaseTest::invokeTcpSocketTunnelProxyApiCall(const QString &method, const QVariantMap params)
|
|
{
|
|
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()->tcpSocketServerTunnelProxy()->serverUrl().host(),
|
|
static_cast<quint16>(Engine::instance()->tcpSocketServerTunnelProxy()->serverUrl().port()));
|
|
spyConnection.wait();
|
|
if (spyConnection.count() == 0) {
|
|
return QVariant();
|
|
}
|
|
|
|
QSignalSpy dataSpy(socket, &QSslSocket::readyRead);
|
|
socket->write(jsonDoc.toJson(QJsonDocument::Compact) + "\n");
|
|
dataSpy.wait();
|
|
if (dataSpy.count() != 1) {
|
|
qWarning() << "No data received";
|
|
return QVariant();
|
|
}
|
|
|
|
QByteArray data = socket->readAll();
|
|
socket->close();
|
|
socket->deleteLater();
|
|
|
|
// Make sure the response ends with '}\n'
|
|
if (!data.endsWith("}\n")) {
|
|
qWarning() << "JSON data does not end with \"}\n\"";
|
|
return QVariant();
|
|
}
|
|
|
|
// Make sure the response it a valid JSON string
|
|
QJsonParseError error;
|
|
jsonDoc = QJsonDocument::fromJson(data, &error);
|
|
if (error.error != QJsonParseError::NoError) {
|
|
qWarning() << "JSON parser error" << error.errorString();
|
|
return QVariant();
|
|
}
|
|
|
|
QVariantMap response = jsonDoc.toVariant().toMap();
|
|
|
|
if (response.value("id").toInt() == m_commandCounter) {
|
|
m_commandCounter++;
|
|
return jsonDoc.toVariant();
|
|
}
|
|
|
|
m_commandCounter++;
|
|
return QVariant();
|
|
}
|
|
|
|
QVariant BaseTest::injectTcpSocketTunnelProxyData(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()->tcpSocketServerTunnelProxy()->serverUrl().host(),
|
|
static_cast<quint16>(Engine::instance()->tcpSocketServerTunnelProxy()->serverUrl().port()));
|
|
spyConnection.wait();
|
|
if (spyConnection.count() == 0) {
|
|
return QVariant();
|
|
}
|
|
|
|
QSignalSpy dataSpy(socket, &QSslSocket::readyRead);
|
|
socket->write(data + "\n");
|
|
dataSpy.wait();
|
|
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();
|
|
}
|
|
|
|
QPair<QVariant, QSslSocket *> BaseTest::invokeTcpSocketTunnelProxyApiCallPersistant(const QString &method, const QVariantMap params, bool slipEnabled, QSslSocket *existingSocket)
|
|
{
|
|
QSslSocket *socket = nullptr;
|
|
if (!existingSocket) {
|
|
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()->tcpSocketServerTunnelProxy()->serverUrl().host(),
|
|
static_cast<quint16>(Engine::instance()->tcpSocketServerTunnelProxy()->serverUrl().port()));
|
|
spyConnection.wait();
|
|
if (spyConnection.count() == 0) {
|
|
return QPair<QVariant, QSslSocket *>(QVariant(), socket);
|
|
}
|
|
} else {
|
|
socket = existingSocket;
|
|
}
|
|
|
|
QVariantMap request;
|
|
request.insert("id", m_commandCounter);
|
|
request.insert("method", method);
|
|
request.insert("params", params);
|
|
QJsonDocument jsonDoc = QJsonDocument::fromVariant(request);
|
|
QByteArray payload = jsonDoc.toJson(QJsonDocument::Compact) + "\n";
|
|
|
|
QSignalSpy dataSpy(socket, &QSslSocket::readyRead);
|
|
|
|
if (slipEnabled) {
|
|
SlipDataProcessor::Frame frame;
|
|
frame.socketAddress = 0x0000;
|
|
frame.data = payload;
|
|
socket->write(SlipDataProcessor::serializeData(SlipDataProcessor::buildFrame(frame)));
|
|
} else {
|
|
socket->write(payload);
|
|
}
|
|
|
|
dataSpy.wait();
|
|
if (dataSpy.count() < 1) {
|
|
qWarning() << "No data received";
|
|
return QPair<QVariant, QSslSocket *>(QVariant(), socket);
|
|
}
|
|
|
|
QByteArray data;
|
|
if (slipEnabled) {
|
|
QByteArray rawData = socket->readAll();
|
|
QByteArray messageData;
|
|
// Parse SLIP frame
|
|
for (int i = 0; i < rawData.length(); i++) {
|
|
quint8 byte = static_cast<quint8>(rawData.at(i));
|
|
if (byte == SlipDataProcessor::ProtocolByteEnd) {
|
|
// If there is no data...continue since it might be a starting END byte
|
|
if (messageData.isEmpty())
|
|
continue;
|
|
|
|
break;
|
|
} else {
|
|
messageData.append(rawData.at(i));
|
|
}
|
|
}
|
|
|
|
QByteArray deserializedData = SlipDataProcessor::deserializeData(messageData);
|
|
SlipDataProcessor::Frame frame = SlipDataProcessor::parseFrame(deserializedData);
|
|
if (frame.data.isEmpty()) {
|
|
qWarning() << "Could not deserialize SLIP data";
|
|
return QPair<QVariant, QSslSocket *>(QVariant(), socket);
|
|
}
|
|
data = frame.data;
|
|
} else {
|
|
data = socket->readAll();
|
|
// Make sure the response ends with '}\n'
|
|
if (!data.endsWith("}\n")) {
|
|
qWarning() << "JSON data does not end with \"}\n\"";
|
|
return QPair<QVariant, QSslSocket *>(QVariant(), socket);
|
|
}
|
|
}
|
|
|
|
// 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() << qUtf8Printable(data);
|
|
return QPair<QVariant, QSslSocket *>(QVariant(), socket);
|
|
}
|
|
|
|
QVariantMap response = jsonDoc.toVariant().toMap();
|
|
|
|
if (response.value("id").toInt() == m_commandCounter) {
|
|
m_commandCounter++;
|
|
return QPair<QVariant, QSslSocket *>(jsonDoc.toVariant(), socket);
|
|
}
|
|
|
|
m_commandCounter++;
|
|
return QPair<QVariant, QSslSocket *>(QVariant(), socket);
|
|
}
|
|
|
|
QPair<QVariant, QWebSocket *> BaseTest::invokeWebSocketTunnelProxyApiCallPersistant(const QString &method, const QVariantMap params, bool slipEnabled, QWebSocket *existingSocket)
|
|
{
|
|
QWebSocket *socket = nullptr;
|
|
if (!existingSocket) {
|
|
socket = new QWebSocket("tunnelproxy-testclient", QWebSocketProtocol::Version13);
|
|
connect(socket, &QWebSocket::sslErrors, this, &BaseTest::sslErrors);
|
|
QSignalSpy spyConnection(socket, SIGNAL(connected()));
|
|
socket->open(Engine::instance()->webSocketServerTunnelProxy()->serverUrl());
|
|
spyConnection.wait();
|
|
if (spyConnection.count() == 0) {
|
|
qWarning() << "Failed to connect websocket on tunnel proxy";
|
|
return QPair<QVariant, QWebSocket *>(QVariant(), socket);
|
|
}
|
|
} else {
|
|
socket = existingSocket;
|
|
}
|
|
|
|
QVariantMap request;
|
|
request.insert("id", m_commandCounter);
|
|
request.insert("method", method);
|
|
request.insert("params", params);
|
|
QJsonDocument jsonDoc = QJsonDocument::fromVariant(request);
|
|
QByteArray payload = jsonDoc.toJson(QJsonDocument::Compact) + "\n";
|
|
|
|
|
|
if (socket->state() != QAbstractSocket::ConnectedState) {
|
|
qWarning() << "Socket not connected";
|
|
return QPair<QVariant, QWebSocket *>(QVariant(), socket);
|
|
}
|
|
|
|
QSignalSpy dataSpy(socket, SIGNAL(textMessageReceived(QString)));
|
|
|
|
if (slipEnabled) {
|
|
SlipDataProcessor::Frame frame;
|
|
frame.socketAddress = 0x0000;
|
|
frame.data = payload;
|
|
socket->sendTextMessage(QString::fromUtf8(SlipDataProcessor::serializeData(SlipDataProcessor::buildFrame(frame))));
|
|
} else {
|
|
socket->sendTextMessage(QString(payload));
|
|
}
|
|
|
|
dataSpy.wait();
|
|
|
|
for (int i = 0; i < dataSpy.count(); i++) {
|
|
QByteArray data;
|
|
if (slipEnabled) {
|
|
QByteArray rawData = dataSpy.at(i).last().toByteArray();
|
|
QByteArray messageData;
|
|
// Parse SLIP frame
|
|
for (int i = 0; i < rawData.length(); i++) {
|
|
quint8 byte = static_cast<quint8>(rawData.at(i));
|
|
if (byte == SlipDataProcessor::ProtocolByteEnd) {
|
|
// If there is no data...continue since it might be a starting END byte
|
|
if (messageData.isEmpty())
|
|
continue;
|
|
|
|
break;
|
|
messageData.clear();
|
|
} else {
|
|
messageData.append(rawData.at(i));
|
|
}
|
|
}
|
|
|
|
QByteArray deserializedData = SlipDataProcessor::deserializeData(messageData);
|
|
SlipDataProcessor::Frame frame = SlipDataProcessor::parseFrame(deserializedData);
|
|
if (frame.data.isEmpty()) {
|
|
qWarning() << "Could not deserialize SLIP data";
|
|
return QPair<QVariant, QWebSocket *>(QVariant(), socket);
|
|
}
|
|
data = frame.data;
|
|
} else {
|
|
data = dataSpy.at(i).last().toByteArray();
|
|
// Make sure the response ends with '}\n'
|
|
if (!data.endsWith("}\n")) {
|
|
qWarning() << "JSON data does not end with \"}\n\"";
|
|
return QPair<QVariant, QWebSocket *>(QVariant(), socket);
|
|
}
|
|
}
|
|
|
|
// Make sure the response it a valid JSON string
|
|
QJsonParseError error;
|
|
jsonDoc = QJsonDocument::fromJson(dataSpy.at(i).last().toByteArray(), &error);
|
|
if (error.error != QJsonParseError::NoError) {
|
|
qWarning() << "JSON parser error" << error.errorString();
|
|
return QPair<QVariant, QWebSocket *>(QVariant(), socket);
|
|
}
|
|
QVariantMap response = jsonDoc.toVariant().toMap();
|
|
|
|
// skip notifications
|
|
if (response.contains("notification"))
|
|
continue;
|
|
|
|
if (response.value("id").toInt() == m_commandCounter) {
|
|
m_commandCounter++;
|
|
return QPair<QVariant, QWebSocket *>(jsonDoc.toVariant(), socket);
|
|
}
|
|
}
|
|
|
|
qWarning() << "No websocket data received...";
|
|
m_commandCounter++;
|
|
return QPair<QVariant, QWebSocket *>(QVariant(), socket);
|
|
}
|
|
|
|
void BaseTest::initTestCase()
|
|
{
|
|
qCDebug(dcApplication()) << "Init test case done.";
|
|
}
|
|
|
|
void BaseTest::cleanupTestCase()
|
|
{
|
|
qCDebug(dcApplication()) << "Clean up test case.";
|
|
cleanUpEngine();
|
|
}
|
|
|