First working connection mechanism for tunnel proxy

This commit is contained in:
Simon Stürz 2021-08-03 19:52:00 +02:00
parent 8f9bdcce13
commit 30d6a9dd43
39 changed files with 1425 additions and 141 deletions

7
common/common.pri Normal file
View File

@ -0,0 +1,7 @@
INCLUDEPATH += $$PWD
HEADERS += \
$$PWD/slipdataprocessor.h
SOURCES += \
$$PWD/slipdataprocessor.cpp

View File

@ -0,0 +1,108 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2021, 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 Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the terms of the GNU
* Lesser General Public License as published by the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser 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 "slipdataprocessor.h"
#include <QDebug>
QByteArray SlipDataProcessor::deserializeData(const QByteArray &data)
{
QByteArray deserializedData;
// Parse serial data
bool escaped = false;
for (int i = 0; i < data.length(); i++) {
quint8 byte = static_cast<quint8>(data.at(i));
if (escaped) {
if (byte == ProtocolByteTransposedEnd) {
deserializedData.append(static_cast<char>(ProtocolByteEnd));
} else if (byte == ProtocolByteTransposedEsc) {
deserializedData.append(static_cast<char>(ProtocolByteEsc));
} else {
qWarning() << "Error while deserializing data. Escape character received but the escaped character was not recognized.";
return QByteArray();
}
escaped = false;
continue;
}
// If escape byte, the next byte has to be a modified byte
if (byte == ProtocolByteEsc) {
escaped = true;
} else {
deserializedData.append(static_cast<char>(byte));
}
}
return deserializedData;
}
QByteArray SlipDataProcessor::serializeData(const QByteArray &data)
{
QByteArray serializedData;
QDataStream stream(&serializedData, QIODevice::WriteOnly);
// stream << static_cast<quint8>(ProtocolByteEnd);
for (int i = 0; i < data.length(); i++) {
quint8 byte = static_cast<quint8>(data.at(i));
switch (byte) {
case ProtocolByteEnd:
stream << static_cast<quint8>(ProtocolByteEsc);
stream << static_cast<quint8>(ProtocolByteTransposedEnd);
break;
case ProtocolByteEsc:
stream << static_cast<quint8>(ProtocolByteEsc);
stream << static_cast<quint8>(ProtocolByteTransposedEsc);
break;
default:
stream << byte;
break;
}
}
// Add the protocol end byte
stream << static_cast<quint8>(ProtocolByteEnd);
return serializedData;
}
SlipDataProcessor::Frame SlipDataProcessor::parseFrame(const QByteArray &data)
{
Frame frame;
frame.socketAddress = static_cast<quint16>(static_cast<quint16>(data.at(0)) << 8 | data.at(1));
frame.data = data.right(data.length() - 2);
return frame;
}
QByteArray SlipDataProcessor::buildFrame(const Frame &frame)
{
QByteArray addressData;
QDataStream stream(&addressData, QIODevice::WriteOnly);
stream << frame.socketAddress;
return addressData + frame.data;
}

View File

@ -0,0 +1,64 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2021, 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 Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the terms of the GNU
* Lesser General Public License as published by the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser 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 SLIPDATAPROCESSOR_H
#define SLIPDATAPROCESSOR_H
#include <QObject>
#include <QDataStream>
// SLIP: https://tools.ietf.org/html/rfc1055
class SlipDataProcessor
{
Q_GADGET
public:
enum ProtocolByte {
ProtocolByteEnd = 0xC0,
ProtocolByteEsc = 0xDB,
ProtocolByteTransposedEnd = 0xDC,
ProtocolByteTransposedEsc = 0xDD
};
Q_ENUM(ProtocolByte)
typedef struct Frame {
quint16 socketAddress;
QByteArray data;
} Frame;
explicit SlipDataProcessor() = default;
static QByteArray deserializeData(const QByteArray &data);
static QByteArray serializeData(const QByteArray &data);
static Frame parseFrame(const QByteArray &data);
static QByteArray buildFrame(const Frame &frame);
};
#endif // SLIPDATAPROCESSOR_H

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 102 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -18,11 +18,8 @@ note over proxy: Server: Assign address for this client socket (0x0001)
proxy->nymea: SLIP:0x0000: ProxyTunnel.ClientConnected (address: 0x0001)
proxy<-nymea: SLIP:0x0000: ProxyTunnel.AcceptClient (address: 0x0001)
proxy->nymea: TunnelProxyErrorNoError
proxy->client: ProxyTunnel.ConfirmClient(uuid, name, serverUuid)
proxy->client: TunnelProxyErrorNoError
note over client: Connected\nAny incomming and outgoing data will\nbe from the connected nymea instance\nuntil disconnected.
@ -30,10 +27,14 @@ note over nymea, client: Connected: The client can now communicate with nymea di
proxy<-client: "data"
proxy<-client: "request data"
nymea<-proxy: SLIP:0x0001 "data"
nymea<-proxy: SLIP:0x0001 "request data"
nymea->proxy: SLIP:0x0001 "data"
nymea->proxy: SLIP:0x0001 "response data"
proxy->client: "data"
proxy->client: "response data"
nymea->proxy: SLIP:0x0001 "notification data"
proxy->client: "notification data"

View File

@ -101,7 +101,7 @@ QPair<bool, QString> JsonHandler::validateReturns(const QString &methodName, con
void JsonHandler::setDescription(const QString &methodName, const QString &description)
{
for(int i = 0; i < metaObject()->methodCount(); ++i) {
for (int i = 0; i < metaObject()->methodCount(); ++i) {
QMetaMethod method = metaObject()->method(i);
if (method.name() == methodName) {
m_descriptions.insert(methodName, description);
@ -113,7 +113,7 @@ void JsonHandler::setDescription(const QString &methodName, const QString &descr
void JsonHandler::setParams(const QString &methodName, const QVariantMap &params)
{
for(int i = 0; i < metaObject()->methodCount(); ++i) {
for (int i = 0; i < metaObject()->methodCount(); ++i) {
QMetaMethod method = metaObject()->method(i);
if (method.name() == methodName) {
m_params.insert(methodName, params);
@ -125,7 +125,7 @@ void JsonHandler::setParams(const QString &methodName, const QVariantMap &params
void JsonHandler::setReturns(const QString &methodName, const QVariantMap &returns)
{
for(int i = 0; i < metaObject()->methodCount(); ++i) {
for (int i = 0; i < metaObject()->methodCount(); ++i) {
QMetaMethod method = metaObject()->method(i);
if (method.name() == methodName) {
m_returns.insert(methodName, returns);

View File

@ -47,11 +47,21 @@ TunnelProxyHandler::TunnelProxyHandler(QObject *parent) : JsonHandler(parent)
params.insert("serverUuid", JsonTypes::basicTypeToString(JsonTypes::Uuid));
setParams("RegisterServer", params);
returns.insert("tunnelProxyError", JsonTypes::tunnelProxyErrorRef());
returns.insert("slipEnabled", JsonTypes::basicTypeToString(JsonTypes::Bool));
setReturns("RegisterServer", returns);
params.clear(); returns.clear();
setDescription("DisconnectClient", "A registered server can ask the remote proxy connection to disconnect a client for whatever reason.");
params.insert("socketAddress", JsonTypes::basicTypeToString(JsonTypes::UInt));
setParams("DisconnectClient", params);
returns.insert("tunnelProxyError", JsonTypes::tunnelProxyErrorRef());
setReturns("DisconnectClient", returns);
// Client
params.clear(); returns.clear();
setDescription("RegisterClient", "Register a new TunnelProxy client on TunnelProxy server with the given serverUuid..");
setDescription("RegisterClient", "Register a new TunnelProxy client on TunnelProxy server with the given serverUuid. "
"On success, the remote connection has been accepted and any further data will come from the connected server.");
params.insert("clientName", JsonTypes::basicTypeToString(JsonTypes::String));
params.insert("clientUuid", JsonTypes::basicTypeToString(JsonTypes::Uuid));
params.insert("serverUuid", JsonTypes::basicTypeToString(JsonTypes::Uuid));
@ -64,10 +74,11 @@ TunnelProxyHandler::TunnelProxyHandler(QObject *parent) : JsonHandler(parent)
// Server
params.clear(); returns.clear();
setDescription("ClientConnected", "Emitted whenever a new client has been connected to a registered server. "
"Only tunnel proxy clients registered as server will receive this notification.");
params.insert("clientId", JsonTypes::basicTypeToString(JsonTypes::UInt));
params.insert("name", JsonTypes::basicTypeToString(JsonTypes::String));
params.insert("address", JsonTypes::basicTypeToString(JsonTypes::String));
"Only tunnel proxy clients registered as server will receive this notification. The socket address will be used for framing.");
params.insert("clientName", JsonTypes::basicTypeToString(JsonTypes::String));
params.insert("clientUuid", JsonTypes::basicTypeToString(JsonTypes::String));
params.insert("clientPeerAddress", JsonTypes::basicTypeToString(JsonTypes::String));
params.insert("socketAddress", JsonTypes::basicTypeToString(JsonTypes::UInt));
setParams("ClientConnected", params);
params.clear(); returns.clear();
@ -100,9 +111,21 @@ JsonReply *TunnelProxyHandler::RegisterServer(const QVariantMap &params, Transpo
QVariantMap response;
response.insert("tunnelProxyError", JsonTypes::tunnelProxyErrorToString(error));
response.insert("slipEnabled", error == TunnelProxyServer::TunnelProxyErrorNoError);
return createReply("RegisterServer", response);
}
JsonReply *TunnelProxyHandler::DisconnectClient(const QVariantMap &params, TransportClient *transportClient)
{
qCDebug(dcJsonRpc()) << name() << "disconnect client requested" << params << transportClient;
quint16 socketAddress = static_cast<quint16>(params.value("socketAddress").toUInt());
TunnelProxyServer::TunnelProxyError error = Engine::instance()->tunnelProxyServer()->disconnectClient(transportClient->clientId(), socketAddress);
QVariantMap response;
response.insert("tunnelProxyError", JsonTypes::tunnelProxyErrorToString(error));
return createReply("DisconnectClient", response);
}
JsonReply *TunnelProxyHandler::RegisterClient(const QVariantMap &params, TransportClient *transportClient)
{
qCDebug(dcJsonRpc()) << name() << "register client" << params << transportClient;
@ -122,9 +145,10 @@ JsonReply *TunnelProxyHandler::RegisterClient(const QVariantMap &params, Transpo
QVariantMap response;
response.insert("tunnelProxyError", JsonTypes::tunnelProxyErrorToString(error));
return createReply("RegisterServer", response);
return createReply("RegisterClient", response);
}
//JsonReply *TunnelProxyHandler::RemoveClient(const QVariantMap &params, TransportClient *transportClient)
//{

View File

@ -46,9 +46,9 @@ public:
QString name() const override;
Q_INVOKABLE JsonReply *RegisterServer(const QVariantMap &params, TransportClient *transportClient);
Q_INVOKABLE JsonReply *DisconnectClient(const QVariantMap &params, TransportClient *transportClient);
Q_INVOKABLE JsonReply *RegisterClient(const QVariantMap &params, TransportClient *transportClient);
// Q_INVOKABLE JsonReply *RemoveClient(const QVariantMap &params, TransportClient *transportClient);
signals:
void ClientConnected(const QVariantMap &params, TransportClient *transportClient);

View File

@ -1,4 +1,5 @@
include(../nymea-remoteproxy.pri)
include(../common/common.pri)
TEMPLATE = lib
TARGET = nymea-remoteproxy

View File

@ -130,16 +130,16 @@ QList<QByteArray> ProxyClient::processData(const QByteArray &data)
QList<QByteArray> packages;
// Handle packet fragmentation
m_dataBuffers.append(data);
int splitIndex = m_dataBuffers.indexOf("}\n{");
m_dataBuffer.append(data);
int splitIndex = m_dataBuffer.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{");
packages.append(m_dataBuffer.left(splitIndex + 1));
m_dataBuffer = m_dataBuffer.right(m_dataBuffer.length() - splitIndex - 2);
splitIndex = m_dataBuffer.indexOf("}\n{");
}
if (m_dataBuffers.trimmed().endsWith("}")) {
packages.append(m_dataBuffers);
m_dataBuffers.clear();
if (m_dataBuffer.trimmed().endsWith("}")) {
packages.append(m_dataBuffer);
m_dataBuffer.clear();
}
return packages;

View File

@ -29,6 +29,8 @@
#include "jsonrpcserver.h"
#include "loggingcategories.h"
#include "jsonrpc/jsontypes.h"
#include "transportclient.h"
#include "slipdataprocessor.h"
#include <QJsonDocument>
#include <QJsonParseError>
@ -135,7 +137,14 @@ void JsonRpcServer::sendResponse(TransportClient *client, int commandId, const Q
QByteArray data = QJsonDocument::fromVariant(response).toJson(QJsonDocument::Compact);
qCDebug(dcJsonRpcTraffic()) << "Sending data:" << data;
client->sendData(data);
if (client->slipEnabled()) {
SlipDataProcessor::Frame frame;
frame.socketAddress = 0x0000;
frame.data = data;
client->sendData(SlipDataProcessor::serializeData(SlipDataProcessor::buildFrame(frame)));
} else {
client->sendData(data);
}
}
void JsonRpcServer::sendErrorResponse(TransportClient *client, int commandId, const QString &error)
@ -147,7 +156,14 @@ void JsonRpcServer::sendErrorResponse(TransportClient *client, int commandId, co
QByteArray data = QJsonDocument::fromVariant(errorResponse).toJson(QJsonDocument::Compact);
qCDebug(dcJsonRpcTraffic()) << "Sending data:" << data;
client->sendData(data);
if (client->slipEnabled()) {
SlipDataProcessor::Frame frame;
frame.socketAddress = 0x0000;
frame.data = data;
client->sendData(SlipDataProcessor::serializeData(SlipDataProcessor::buildFrame(frame)));
} else {
client->sendData(data);
}
}
QString JsonRpcServer::formatAssertion(const QString &targetNamespace, const QString &method, JsonHandler *handler, const QVariantMap &data) const
@ -239,18 +255,21 @@ void JsonRpcServer::processDataPackage(TransportClient *transportClient, const Q
Q_ASSERT_X((targetNamespace == "RemoteProxy" && method == "Introspect") || handler->validateReturns(method, reply->data()).first
,"validating return value", formatAssertion(targetNamespace, method, handler, reply->data()).toLatin1().data());
// QPair<bool, QString> returnValidation = reply->handler()->validateReturns(reply->method(), reply->data());
// if (!returnValidation.first) {
// qCWarning(dcJsonRpc()) << "Return value validation failed of sync reply. This should never happen. Please check the source code.";
// }
reply->setClientId(transportClient->clientId());
reply->setCommandId(commandId);
sendResponse(transportClient, commandId, reply->data());
// TODO: check if the client should be disconnected after this response
reply->deleteLater();
// If the server decided to kill the connection after the response, do it now
if (transportClient->killConnectionRequested()) {
transportClient->killConnection(transportClient->killConnectionReason());
return;
}
// Enable SLIP from now on since requested
if (transportClient->slipAfterResponseEnabled()) {
transportClient->setSlipEnabled(true);
}
}
}
@ -325,7 +344,7 @@ void JsonRpcServer::processData(TransportClient *transportClient, const QByteArr
if (!m_clients.contains(transportClient))
return;
qCDebug(dcJsonRpcTraffic()) << "Incoming data from" << transportClient << ": " << data;
qCDebug(dcJsonRpcTraffic()) << "Incoming data from" << transportClient << ": " << qUtf8Printable(data);
// Handle package fragmentation
QList<QByteArray> packages = transportClient->processData(data);
@ -350,8 +369,16 @@ void JsonRpcServer::sendNotification(const QString &nameSpace, const QString &me
notification.insert("params", params);
QByteArray data = QJsonDocument::fromVariant(notification).toJson(QJsonDocument::Compact);
qCDebug(dcJsonRpcTraffic()) << "Sending notification:" << data;
transportClient->sendData(data);
if (transportClient->slipEnabled()) {
SlipDataProcessor::Frame frame;
frame.socketAddress = 0x0000;
frame.data = data;
qCDebug(dcJsonRpcTraffic()) << "Sending notification frame:" <<frame.socketAddress << qUtf8Printable(frame.data);
transportClient->sendData(SlipDataProcessor::serializeData(SlipDataProcessor::buildFrame(frame)));
} else {
qCDebug(dcJsonRpcTraffic()) << "Sending notification:" << data;
transportClient->sendData(data);
}
}
}

View File

@ -69,12 +69,13 @@ private:
QString formatAssertion(const QString &targetNamespace, const QString &method, JsonHandler *handler, const QVariantMap &data) const;
void processDataPackage(TransportClient *transportClient, const QByteArray &data);
private slots:
void asyncReplyFinished();
public slots:
void processDataPackage(TransportClient *transportClient, const QByteArray &data);
// Client registration for JSON RPC traffic
void registerClient(TransportClient *transportClient);
void unregisterClient(TransportClient *transportClient);

View File

@ -65,7 +65,6 @@ void TcpSocketServer::killClientConnection(const QUuid &clientId, const QString
return;
qCWarning(dcTcpSocketServer()) << "Killing client connection" << clientId.toString() << "Reason:" << killReason;
client->flush();
client->close();
}

View File

@ -61,6 +61,42 @@ QString TransportClient::creationTimeString() const
return QDateTime::fromMSecsSinceEpoch(creationTime() * 1000).toString("dd.MM.yyyy hh:mm:ss");
}
void TransportClient::killConnectionAfterResponse(const QString &killConnectionReason)
{
m_killConnectionRequested = true;
m_killConnectionReason = killConnectionReason;
}
bool TransportClient::killConnectionRequested() const
{
return m_killConnectionRequested;
}
QString TransportClient::killConnectionReason() const
{
return m_killConnectionReason;
}
void TransportClient::enableSlipAfterResponse()
{
m_slipAfterResponseEnabled = true;
}
bool TransportClient::slipAfterResponseEnabled() const
{
return m_slipAfterResponseEnabled;
}
bool TransportClient::slipEnabled() const
{
return m_slipEnabled;
}
void TransportClient::setSlipEnabled(bool slipEnabled)
{
m_slipEnabled = slipEnabled;
}
TransportInterface *TransportClient::interface() const
{
return m_interface;
@ -108,7 +144,7 @@ void TransportClient::addTxDataCount(int dataCount)
int TransportClient::bufferSize() const
{
return m_dataBuffers.size();
return m_dataBuffer.size();
}
int TransportClient::generateMessageId()

View File

@ -50,6 +50,18 @@ public:
uint creationTime() const;
QString creationTimeString() const;
// Schedule a disconnect after the response
void killConnectionAfterResponse(const QString &killConnectionReason);
bool killConnectionRequested() const;
QString killConnectionReason() const;
// Schedule SLIP enable after response
void enableSlipAfterResponse();
bool slipAfterResponseEnabled() const;
bool slipEnabled() const;
void setSlipEnabled(bool slipEnabled);
TransportInterface *interface() const;
// Properties from auth request
@ -85,7 +97,13 @@ protected:
QString m_name;
QUuid m_uuid;
QByteArray m_dataBuffers;
QByteArray m_dataBuffer;
bool m_killConnectionRequested = false;
QString m_killConnectionReason;
bool m_slipAfterResponseEnabled = false;
bool m_slipEnabled = false;
// Json data information
int m_messageId = 0;

View File

@ -1,5 +1,7 @@
#include "tunnelproxyclient.h"
#include "loggingcategories.h"
#include "server/transportinterface.h"
#include "slipdataprocessor.h"
namespace remoteproxy {
@ -23,35 +25,44 @@ void TunnelProxyClient::setType(Type type)
emit typeChanged(m_type);
}
bool TunnelProxyClient::slipEnabled() const
{
return m_slipEnabled;
}
void TunnelProxyClient::setSlipEnabled(bool slipEnabled)
{
m_slipEnabled = slipEnabled;
}
QList<QByteArray> TunnelProxyClient::processData(const QByteArray &data)
{
// TODO: unescape if this data is for the json handler
QList<QByteArray> packages;
// Parse packages depending on the encoded
if (m_slipEnabled) {
// Read each byte until we get END byte, then unescape the package
for (int i = 0; i < data.length(); i++) {
quint8 byte = static_cast<quint8>(data.at(i));
if (byte == SlipDataProcessor::ProtocolByteEnd) {
// If there is no data...continue since it might be a starting END byte
if (m_dataBuffer.isEmpty())
continue;
QByteArray frame = SlipDataProcessor::deserializeData(m_dataBuffer);
if (frame.isNull()) {
qCWarning(dcTunnelProxyServerTraffic()) << "Received inconsistant SLIP encoded message. Ignoring data...";
} else {
qCDebug(dcTunnelProxyServerTraffic()) << "Frame received";
packages.append(m_dataBuffer);
}
} else {
m_dataBuffer.append(data.at(i));
}
}
} else {
// Handle json packet fragmentation
m_dataBuffers.append(data);
int splitIndex = m_dataBuffers.indexOf("}\n{");
m_dataBuffer.append(data);
int splitIndex = m_dataBuffer.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{");
packages.append(m_dataBuffer.left(splitIndex + 1));
m_dataBuffer = m_dataBuffer.right(m_dataBuffer.length() - splitIndex - 2);
splitIndex = m_dataBuffer.indexOf("}\n{");
}
if (m_dataBuffers.trimmed().endsWith("}")) {
packages.append(m_dataBuffers);
m_dataBuffers.clear();
if (m_dataBuffer.trimmed().endsWith("}")) {
packages.append(m_dataBuffer);
m_dataBuffer.clear();
}
}

View File

@ -23,9 +23,6 @@ public:
Type type() const;
void setType(Type type);
bool slipEnabled() const;
void setSlipEnabled(bool slipEnabled);
// Json server methods
QList<QByteArray> processData(const QByteArray &data) override;
@ -34,7 +31,6 @@ signals:
private:
Type m_type = TypeNone;
bool m_slipEnabled = false;
};

View File

@ -66,6 +66,16 @@ QUuid TunnelProxyClientConnection::serverUuid() const
return m_serverUuid;
}
quint16 TunnelProxyClientConnection::socketAddress() const
{
return m_socketAddress;
}
void TunnelProxyClientConnection::setSocketAddress(quint16 socketAddress)
{
m_socketAddress = socketAddress;
}
QDebug operator<<(QDebug debug, TunnelProxyClientConnection *clientConnection)
{
debug.nospace() << "TunnelProxyClientConnection(";

View File

@ -51,6 +51,9 @@ public:
QString clientName() const;
QUuid serverUuid() const;
quint16 socketAddress() const;
void setSocketAddress(quint16 socketAddress);
private:
TransportClient *m_transportClient = nullptr;
TunnelProxyServerConnection *m_serverConnection = nullptr;
@ -58,7 +61,7 @@ private:
QUuid m_clientUuid;
QString m_clientName;
QUuid m_serverUuid;
quint16 m_socketAddress = 0xFFFF;
};
QDebug operator<<(QDebug debug, TunnelProxyClientConnection *clientConnection);

View File

@ -32,6 +32,8 @@
#include "tunnelproxyserverconnection.h"
#include "tunnelproxyclientconnection.h"
#include "../common/slipdataprocessor.h"
namespace remoteproxy {
TunnelProxyServer::TunnelProxyServer(QObject *parent) :
@ -86,12 +88,14 @@ TunnelProxyServer::TunnelProxyError TunnelProxyServer::registerServer(const QUui
TunnelProxyClient *tunnelProxyClient = m_proxyClients.value(clientId);
if (!tunnelProxyClient) {
qCWarning(dcTunnelProxyServer()) << "There is no client with client uuid" << clientId.toString();
tunnelProxyClient->killConnectionAfterResponse("Internal server error");
return TunnelProxyServer::TunnelProxyErrorInternalServerError;
}
// Make sure this client has not been registered as client or re-registration has been called...
if (tunnelProxyClient->type() != TunnelProxyClient::TypeNone) {
qCWarning(dcTunnelProxyServer()) << "Client tried to register as server but has already been registerd as" << tunnelProxyClient->type();
tunnelProxyClient->killConnectionAfterResponse("Already registered");
return TunnelProxyServer::TunnelProxyErrorAlreadyRegistered;
}
@ -100,7 +104,7 @@ TunnelProxyServer::TunnelProxyError TunnelProxyServer::registerServer(const QUui
tunnelProxyClient->setName(serverName);
// Enable SLIP from now on
// tunnelProxyClient->setSlipEnabled(true);
tunnelProxyClient->enableSlipAfterResponse();
TunnelProxyServerConnection *serverConnection = new TunnelProxyServerConnection(tunnelProxyClient, serverUuid, serverName, this);
m_tunnelProxyServerConnections.insert(serverUuid, serverConnection);
@ -110,22 +114,23 @@ TunnelProxyServer::TunnelProxyError TunnelProxyServer::registerServer(const QUui
TunnelProxyServer::TunnelProxyError TunnelProxyServer::registerClient(const QUuid &clientId, const QUuid &clientUuid, const QString &clientName, const QUuid &serverUuid)
{
qCDebug(dcTunnelProxyServer()) << "Register new client" << m_proxyClients.value(clientId) << clientName << clientUuid.toString() << "--> server" << serverUuid.toString();
TunnelProxyClient *tunnelProxyClient = m_proxyClients.value(clientId);
if (!tunnelProxyClient) {
qCWarning(dcTunnelProxyServer()) << "There is no client with client uuid" << clientId.toString();
tunnelProxyClient->killConnectionAfterResponse("Internal server error");
return TunnelProxyServer::TunnelProxyErrorInternalServerError;
}
// Make sure this client has not been registered as client or re-registration has been called...
if (tunnelProxyClient->type() != TunnelProxyClient::TypeNone) {
qCWarning(dcTunnelProxyServer()) << "Client tried to register as client but has already been registerd as" << tunnelProxyClient->type();
tunnelProxyClient->killConnectionAfterResponse("Already registered");
return TunnelProxyServer::TunnelProxyErrorAlreadyRegistered;
}
if (m_tunnelProxyClientConnections.contains(clientUuid)) {
qCWarning(dcTunnelProxyServer()) << "There is a client already registered with client uuid" << clientUuid.toString();
tunnelProxyClient->killConnectionAfterResponse("Already registered");
return TunnelProxyServer::TunnelProxyErrorAlreadyRegistered;
}
@ -133,6 +138,7 @@ TunnelProxyServer::TunnelProxyError TunnelProxyServer::registerClient(const QUui
TunnelProxyServerConnection *serverConnection = m_tunnelProxyServerConnections.value(serverUuid);
if (!serverConnection) {
qCWarning(dcTunnelProxyServer()) << "There is no server registered with server uuid" << serverUuid.toString();
tunnelProxyClient->killConnectionAfterResponse("Unknown server");
return TunnelProxyServer::TunnelProxyErrorServerNotFound;
}
@ -147,8 +153,47 @@ TunnelProxyServer::TunnelProxyError TunnelProxyServer::registerClient(const QUui
qCDebug(dcTunnelProxyServer()) << "Register client" << clientConnection << "-->" << serverConnection;
serverConnection->registerClientConnection(clientConnection);
// TODO: wait for te aprovement from the server
// Tell the server a new client want's to connect
QVariantMap params;
params.insert("clientName", tunnelProxyClient->name());
params.insert("clientUuid", tunnelProxyClient->uuid().toString());
params.insert("clientPeerAddress", tunnelProxyClient->peerAddress().toString());
params.insert("socketAddress", clientConnection->socketAddress());
m_jsonRpcServer->sendNotification("TunnelProxy", "ClientConnected", params, serverConnection->transportClient());
// Note: check if a confirmation from the server would be needed, for rejection or limit or something. For now they are directly connected.
return TunnelProxyServer::TunnelProxyErrorNoError;
}
TunnelProxyServer::TunnelProxyError TunnelProxyServer::disconnectClient(const QUuid &clientId, quint16 socketAddress)
{
TunnelProxyClient *tunnelProxyClient = m_proxyClients.value(clientId);
if (!tunnelProxyClient) {
qCWarning(dcTunnelProxyServer()) << "There is no client with client uuid" << clientId.toString();
return TunnelProxyServer::TunnelProxyErrorInternalServerError;
}
if (tunnelProxyClient->type() != TunnelProxyClient::TypeServer) {
qCWarning(dcTunnelProxyServer()) << "Client tried to disconnect a client but has not been registerd as server" << tunnelProxyClient->type();
tunnelProxyClient->killConnectionAfterResponse("Already registered");
return TunnelProxyServer::TunnelProxyErrorAlreadyRegistered;
}
TunnelProxyServerConnection *serverConnection = m_tunnelProxyServerConnections.value(tunnelProxyClient->uuid());
if (!serverConnection) {
qCWarning(dcTunnelProxyServer()) << "Could not find server connection for" << tunnelProxyClient;
tunnelProxyClient->killConnectionAfterResponse("Internal server error");
return TunnelProxyServer::TunnelProxyErrorInternalServerError;
}
TunnelProxyClientConnection *clientConnection = serverConnection->getClientConnection(socketAddress);
if (!clientConnection) {
qCWarning(dcTunnelProxyServer()) << "Could not find client connection for socket address" << socketAddress << "on" << serverConnection;
return TunnelProxyServer::TunnelProxyErrorUnknownSocketAddress;
}
clientConnection->transportClient()->killConnection("Server requested disconnect.");
return TunnelProxyServer::TunnelProxyErrorNoError;
}
@ -202,8 +247,11 @@ void TunnelProxyServer::onClientDisconnected(const QUuid &clientId)
qCWarning(dcTunnelProxyServer()) << "Could not find server connection for disconnected tunnel proxy client claiming to be a server.";
} else {
// TODO: kill all related clients
foreach (TunnelProxyClientConnection *clientConnection, serverConnection->clientConnections()) {
serverConnection->unregisterClientConnection(clientConnection);
clientConnection->setSocketAddress(0xFFFF);
clientConnection->transportClient()->killConnection("Server disconnected");
}
serverConnection->deleteLater();
}
@ -215,8 +263,10 @@ void TunnelProxyServer::onClientDisconnected(const QUuid &clientId)
qCWarning(dcTunnelProxyServer()) << "Could not find client connection for disconnected tunnel proxy client claiming to be a client.";
} else {
if (clientConnection->serverConnection()) {
QVariantMap params;
params.insert("socketAddress", clientConnection->socketAddress());
m_jsonRpcServer->sendNotification("TunnelProxy", "ClientDisconnected", params, clientConnection->serverConnection()->transportClient());
clientConnection->serverConnection()->unregisterClientConnection(clientConnection);
// TODO: Send client disconnected to the server
}
clientConnection->deleteLater();
@ -239,23 +289,63 @@ void TunnelProxyServer::onClientDataAvailable(const QUuid &clientId, const QByte
}
qCDebug(dcTunnelProxyServerTraffic()) << "Client data available" << tunnelProxyClient << qUtf8Printable(data);
if (tunnelProxyClient->type() == TunnelProxyClient::TypeClient) {
// Send the data to the server using slip + frame
// Send the data to the server using slip encoded frame
TunnelProxyClientConnection *clientConnection = m_tunnelProxyClientConnections.value(tunnelProxyClient->uuid());
if (!clientConnection) {
qCWarning(dcTunnelProxyServer()) << "Could not find a client connection for client uuid" << tunnelProxyClient->uuid().toString();
// FIXME: check what do with this client
return;
}
Q_ASSERT_X(clientConnection->serverConnection(), "TunnelProxyServer", "The client has not been registered to a server connection");
SlipDataProcessor::Frame frame;
frame.socketAddress = clientConnection->socketAddress();
frame.data = data;
qCDebug(dcTunnelProxyServerTraffic()) << "Write client data to server using socket address" << clientConnection->socketAddress();
clientConnection->serverConnection()->transportClient()->sendData(SlipDataProcessor::serializeData(SlipDataProcessor::buildFrame(frame)));
} else if (tunnelProxyClient->type() == TunnelProxyClient::TypeServer) {
// Data coming from a connected server connection
if (tunnelProxyClient->slipEnabled()) {
// Unpack SLIP data, get address, pipe to client or give it to the json rpc server if address 0x0000
// Handle package fragmentation
QList<QByteArray> frames = tunnelProxyClient->processData(data);
foreach (const QByteArray &frameData, frames) {
SlipDataProcessor::Frame frame = SlipDataProcessor::parseFrame(frameData);
if (frame.socketAddress == 0x0000) {
qCDebug(dcTunnelProxyServerTraffic()) << "Received frame for the JSON server" << tunnelProxyClient;
m_jsonRpcServer->processDataPackage(tunnelProxyClient, frame.data);
} else {
// This data seems to be for a client with the given address
TunnelProxyServerConnection *serverConnection = m_tunnelProxyServerConnections.value(tunnelProxyClient->uuid());
if (!serverConnection) {
qCWarning(dcTunnelProxyServer()) << "Could not find server connection for" << tunnelProxyClient;
continue;
}
// Get the client and send the data to the client
TunnelProxyClientConnection *clientConnection = serverConnection->getClientConnection(frame.socketAddress);
if (!clientConnection) {
qCWarning(dcTunnelProxyServer()) << "The server connection wants to send data to a client connection which has not been registered to the server.";
// FIXME: tell the server this client does not exist
return;
}
qCDebug(dcTunnelProxyServerTraffic()) << "Sending data to" << clientConnection << qUtf8Printable(data);
clientConnection->transportClient()->sendData(frame.data);
}
}
} else {
m_jsonRpcServer->processData(tunnelProxyClient, data);
}
// Unpack SLIP data, get address, pipe to client or give it to the json rpc server if address 0x0000
} else {
// Not registered yet or doing other stuff...let the JSON RPC handle this data
// Not registered yet or doing other stuff...let the JSON RPC server handle this data
m_jsonRpcServer->processData(tunnelProxyClient, data);
}
}
}

View File

@ -48,7 +48,10 @@ public:
TunnelProxyErrorInvalidUuid,
TunnelProxyErrorInternalServerError,
TunnelProxyErrorServerNotFound,
TunnelProxyErrorAlreadyRegistered
TunnelProxyErrorForbiddenCall,
TunnelProxyErrorAlreadyRegistered,
TunnelProxyErrorNotRegistered,
TunnelProxyErrorUnknownSocketAddress
};
Q_ENUM(TunnelProxyError)
@ -62,6 +65,7 @@ public:
TunnelProxyServer::TunnelProxyError registerServer(const QUuid &clientId, const QUuid &serverUuid, const QString &serverName);
TunnelProxyServer::TunnelProxyError registerClient(const QUuid &clientId, const QUuid &clientUuid, const QString &clientName, const QUuid &serverUuid);
TunnelProxyServer::TunnelProxyError disconnectClient(const QUuid &clientId, quint16 socketAddress);
public slots:
void startServer();

View File

@ -55,14 +55,44 @@ QString TunnelProxyServerConnection::serverName() const
return m_serverName;
}
QList<TunnelProxyClientConnection *> TunnelProxyServerConnection::clientConnections() const
{
return m_clientConnections.values();
}
void TunnelProxyServerConnection::registerClientConnection(TunnelProxyClientConnection *clientConnection)
{
m_clientConnections.insert(clientConnection->clientUuid(), clientConnection);
quint16 socketAddress = getFreeAddress();
clientConnection->setSocketAddress(socketAddress);
m_clientConnectionsAddresses.insert(socketAddress, clientConnection);
}
void TunnelProxyServerConnection::unregisterClientConnection(TunnelProxyClientConnection *clientConnection)
{
m_clientConnections.remove(clientConnection->clientUuid());
m_clientConnectionsAddresses.remove(clientConnection->socketAddress());
}
TunnelProxyClientConnection *TunnelProxyServerConnection::getClientConnection(quint16 socketAddress)
{
return m_clientConnectionsAddresses.value(socketAddress);
}
quint16 TunnelProxyServerConnection::getFreeAddress()
{
m_currentAddressCounter += 1;
quint16 address = m_currentAddressCounter;
for (int i = 0; i < m_connectionLimit; i++) {
if (m_clientConnectionsAddresses.contains(address) || address == 0x0000 || address == 0xFFFF) {
address++;
} else {
break;
}
}
Q_ASSERT_X(!m_clientConnectionsAddresses.contains(address), "TunnelProxyServerConnection", "no free address but the maximum of connections has been reached.");
return address;
}
QDebug operator<<(QDebug debug, TunnelProxyServerConnection *serverConnection)

View File

@ -48,17 +48,27 @@ public:
QUuid serverUuid() const;
QString serverName() const;
QList<TunnelProxyClientConnection *> clientConnections() const;
void registerClientConnection(TunnelProxyClientConnection *clientConnection);
void unregisterClientConnection(TunnelProxyClientConnection *clientConnection);
TunnelProxyClientConnection *getClientConnection(quint16 socketAddress);
signals:
private:
TransportClient *m_transportClient = nullptr;
QUuid m_serverUuid;
QString m_serverName;
quint16 m_connectionLimit = 100;
quint16 m_currentAddressCounter = 0;
QHash<QUuid, TunnelProxyClientConnection *> m_clientConnections;
QHash<quint16, TunnelProxyClientConnection *> m_clientConnectionsAddresses;
quint16 getFreeAddress();
};

View File

@ -4,6 +4,7 @@ TEMPLATE = lib
TARGET = nymea-remoteproxyclient
target.path = $$[QT_INSTALL_LIBS]
include(../common/common.pri)
include(libnymea-remoteproxyclient.pri)
installheaders.files = remoteproxyconnection.h

View File

@ -79,6 +79,20 @@ JsonReply *JsonRpcClient::callRegisterServer(const QUuid &serverUuid, const QStr
return reply;
}
JsonReply *JsonRpcClient::callRegisterClient(const QUuid &clientUuid, const QString &clientName, const QUuid &serverUuid)
{
QVariantMap params;
params.insert("clientUuid", clientUuid);
params.insert("clientName", clientName);
params.insert("serverUuid", serverUuid.toString());
JsonReply *reply = new JsonReply(m_commandId, "TunnelProxy", "RegisterClient", params, this);
qCDebug(dcRemoteProxyClientJsonRpc()) << "Calling" << QString("%1.%2").arg(reply->nameSpace()).arg(reply->method());
sendRequest(reply->requestMap());
m_replies.insert(m_commandId, reply);
return reply;
}
void JsonRpcClient::sendRequest(const QVariantMap &request)
{
QByteArray data = QJsonDocument::fromVariant(request).toJson(QJsonDocument::Compact);
@ -97,6 +111,8 @@ void JsonRpcClient::processDataPackage(const QByteArray &data)
QVariantMap dataMap = jsonDoc.toVariant().toMap();
qCDebug(dcRemoteProxyClientJsonRpcTraffic()) << "Data received" << qUtf8Printable(data);
// check if this is a reply to a request
int commandId = dataMap.value("id").toInt();
JsonReply *reply = m_replies.take(commandId);
@ -109,7 +125,7 @@ void JsonRpcClient::processDataPackage(const QByteArray &data)
}
reply->setResponse(dataMap);
reply->finished();
emit reply->finished();
return;
}
@ -126,6 +142,15 @@ void JsonRpcClient::processDataPackage(const QByteArray &data)
QString clientName = notificationParams.value("name").toString();
QString clientUuid = notificationParams.value("uuid").toString();
emit tunnelEstablished(clientName, clientUuid);
} else if (nameSpace == "TunnelProxy" && notificationName == "ClientConnected") {
QString clientName = notificationParams.value("clientName").toString();
QUuid clientUuid = notificationParams.value("clientUuid").toUuid();
QString clientPeerAddress = notificationParams.value("clientPeerAddress").toString();
quint16 socketAddress = static_cast<quint16>(notificationParams.value("socketAddress").toUInt());
emit tunnelProxyClientConnected(clientName, clientUuid, clientPeerAddress, socketAddress);
} else if (nameSpace == "TunnelProxy" && notificationName == "ClientDisconnected") {
quint16 socketAddress = static_cast<quint16>(notificationParams.value("socketAddress").toUInt());
emit tunnelProxyClientDisonnected(socketAddress);
}
}
}

View File

@ -55,6 +55,8 @@ public:
// Tunnel proxy
JsonReply *callRegisterServer(const QUuid &serverUuid, const QString &serverName);
JsonReply *callRegisterClient(const QUuid &clientUuid, const QString &clientName, const QUuid &serverUuid);
JsonReply *callDisconnectClient(quint16 socketAddress);
private:
ProxyConnection *m_connection = nullptr;
@ -69,6 +71,8 @@ private:
signals:
void tunnelEstablished(const QString clientName, const QString &clientUuid);
void tunnelProxyClientConnected(const QString &clientName, const QUuid &clientUuid, const QString &clientPeerAddress, quint16 socketAddress);
void tunnelProxyClientDisonnected(quint16 socketAddress);
public slots:
void processData(const QByteArray &data);

View File

@ -1,6 +1,298 @@
#include "tunnelproxyremoteconnection.h"
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2021, 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 Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the terms of the GNU
* Lesser General Public License as published by the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser 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
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
TunnelProxyRemoteConnection::TunnelProxyRemoteConnection(QObject *parent) : QObject(parent)
#include "tunnelproxyremoteconnection.h"
#include "proxyconnection.h"
#include "tcpsocketconnection.h"
#include "websocketconnection.h"
#include "proxyjsonrpcclient.h"
Q_LOGGING_CATEGORY(dcTunnelProxyRemoteConnection, "TunnelProxyRemoteConnection")
namespace remoteproxyclient {
TunnelProxyRemoteConnection::TunnelProxyRemoteConnection(const QUuid &clientUuid, const QString &clientName, QObject *parent) :
QObject(parent),
m_clientUuid(clientUuid),
m_clientName(clientName)
{
}
TunnelProxyRemoteConnection::TunnelProxyRemoteConnection(const QUuid &clientUuid, const QString &clientName, ConnectionType connectionType, QObject *parent) :
QObject(parent),
m_clientUuid(clientUuid),
m_clientName(clientName),
m_connectionType(connectionType)
{
}
TunnelProxyRemoteConnection::~TunnelProxyRemoteConnection()
{
}
bool TunnelProxyRemoteConnection::remoteConnected() const
{
return m_remoteConnected;
}
QAbstractSocket::SocketError TunnelProxyRemoteConnection::error() const
{
return m_error;
}
void TunnelProxyRemoteConnection::ignoreSslErrors()
{
m_connection->ignoreSslErrors();
}
void TunnelProxyRemoteConnection::ignoreSslErrors(const QList<QSslError> &errors)
{
m_connection->ignoreSslErrors(errors);
}
QString TunnelProxyRemoteConnection::remoteProxyServer() const
{
return m_remoteProxyServer;
}
QString TunnelProxyRemoteConnection::remoteProxyServerName() const
{
return m_remoteProxyServerName;
}
QString TunnelProxyRemoteConnection::remoteProxyServerVersion() const
{
return m_remoteProxyServerVersion;
}
QString TunnelProxyRemoteConnection::remoteProxyApiVersion() const
{
return m_remoteProxyApiVersion;
}
bool TunnelProxyRemoteConnection::connectServer(const QUrl &url, const QUuid &serverUuid)
{
m_serverUrl = url;
m_serverUuid = serverUuid;
m_error = QAbstractSocket::UnknownSocketError;
cleanUp();
switch (m_connectionType) {
case ConnectionTypeWebSocket:
qCDebug(dcTunnelProxyRemoteConnection()) << "Creating a web socket connection to" << url.toString();
m_connection = qobject_cast<ProxyConnection *>(new WebSocketConnection(this));
break;
case ConnectionTypeTcpSocket:
qCDebug(dcTunnelProxyRemoteConnection()) << "Creating a TCP socket connection to" << url.toString();
m_connection = qobject_cast<ProxyConnection *>(new TcpSocketConnection(this));
break;
}
connect(m_connection, &ProxyConnection::connectedChanged, this, &TunnelProxyRemoteConnection::onConnectionChanged);
connect(m_connection, &ProxyConnection::dataReceived, this, &TunnelProxyRemoteConnection::onConnectionDataAvailable);
connect(m_connection, &ProxyConnection::errorOccured, this, &TunnelProxyRemoteConnection::onConnectionSocketError);
connect(m_connection, &ProxyConnection::stateChanged, this, &TunnelProxyRemoteConnection::onConnectionStateChanged);
connect(m_connection, &ProxyConnection::sslErrors, this, &TunnelProxyRemoteConnection::sslErrors);
m_jsonClient = new JsonRpcClient(m_connection, this);
qCDebug(dcTunnelProxyRemoteConnection()) << "Connecting to" << m_serverUrl.toString();
m_connection->connectServer(m_serverUrl);
return true;
}
void TunnelProxyRemoteConnection::disconnectServer()
{
if (m_connection) {
qCDebug(dcTunnelProxyRemoteConnection()) << "Disconnecting from" << m_connection->serverUrl().toString();
m_connection->disconnectServer();
}
}
void TunnelProxyRemoteConnection::onConnectionChanged(bool connected)
{
if (connected) {
qCDebug(dcTunnelProxyRemoteConnection()) << "Connected to remote proxy server.";
setState(StateConnected);
setState(StateInitializing);
JsonReply *reply = m_jsonClient->callHello();
connect(reply, &JsonReply::finished, this, &TunnelProxyRemoteConnection::onHelloFinished);
} else {
qCDebug(dcTunnelProxyRemoteConnection()) << "Disconnected from remote proxy server.";
setState(StateDisconnected);
cleanUp();
}
}
void TunnelProxyRemoteConnection::onConnectionDataAvailable(const QByteArray &data)
{
if (m_state != StateRemoteConnected) {
m_jsonClient->processData(data);
return;
}
emit dataReady(data);
}
void TunnelProxyRemoteConnection::onConnectionSocketError(QAbstractSocket::SocketError error)
{
setError(error);
}
void TunnelProxyRemoteConnection::onConnectionStateChanged(QAbstractSocket::SocketState state)
{
// Process the actuall socket state
switch (state) {
case QAbstractSocket::UnconnectedState:
setState(StateDisconnected);
break;
case QAbstractSocket::HostLookupState:
setState(StateHostLookup);
break;
case QAbstractSocket::ConnectingState:
setState(StateConnecting);
break;
case QAbstractSocket::ConnectedState:
setState(StateConnected);
break;
case QAbstractSocket::ClosingState:
setState(StateDiconnecting);
break;
case QAbstractSocket::BoundState:
case QAbstractSocket::ListeningState:
break;
}
}
void TunnelProxyRemoteConnection::onHelloFinished()
{
JsonReply *reply = static_cast<JsonReply *>(sender());
reply->deleteLater();
QVariantMap response = reply->response();
qCDebug(dcTunnelProxyRemoteConnection()) << "Hello response ready" << reply->commandId() << response;
if (response.value("status").toString() != "success") {
qCWarning(dcTunnelProxyRemoteConnection()) << "Could not get initial information from proxy server.";
m_connection->disconnectServer();
return;
}
QVariantMap responseParams = response.value("params").toMap();
m_remoteProxyServer = responseParams.value("server").toString();
m_remoteProxyServerName = responseParams.value("name").toString();
m_remoteProxyServerVersion = responseParams.value("version").toString();
m_remoteProxyApiVersion = responseParams.value("apiVersion").toString();
setState(StateRegister);
JsonReply *registerReply = m_jsonClient->callRegisterClient(m_clientUuid, m_clientName, m_serverUuid);
connect(registerReply, &JsonReply::finished, this, &TunnelProxyRemoteConnection::onClientRegistrationFinished);
}
void TunnelProxyRemoteConnection::onClientRegistrationFinished()
{
JsonReply *reply = static_cast<JsonReply *>(sender());
reply->deleteLater();
QVariantMap response = reply->response();
qCDebug(dcTunnelProxyRemoteConnection()) << "RegisterClient response ready" << reply->commandId() << response;
if (response.value("status").toString() != "success") {
qCWarning(dcTunnelProxyRemoteConnection()) << "JSON RPC error. Failed to register as tunnel client on the remote proxy server.";
m_connection->disconnectServer();
return;
}
QVariantMap responseParams = response.value("params").toMap();
if (responseParams.value("tunnelProxyError").toString() != "TunnelProxyErrorNoError") {
qCWarning(dcTunnelProxyRemoteConnection()) << "Failed to register as tunnel client on the remote proxy server:" << responseParams.value("tunnelProxyError").toString();
m_connection->disconnectServer();
return;
}
qCDebug(dcTunnelProxyRemoteConnection()) << "Registered successfully as tunnel client on the remote proxy server.";
setState(StateRemoteConnected);
}
void TunnelProxyRemoteConnection::setState(State state)
{
if (m_state == state)
return;
m_state = state;
qCDebug(dcTunnelProxyRemoteConnection()) << "State changed" << m_state;
emit stateChanged(m_state);
setRemoteConnected(m_state == StateRemoteConnected);
}
void TunnelProxyRemoteConnection::setRemoteConnected(bool remoteConnected)
{
if (m_remoteConnected == remoteConnected)
return;
m_remoteConnected = remoteConnected;
emit remoteConnectedChanged(m_remoteConnected);
}
void TunnelProxyRemoteConnection::setError(QAbstractSocket::SocketError error)
{
if (m_error == error)
return;
m_error = error;
qCWarning(dcTunnelProxyRemoteConnection()) << "Error occured" << m_error;
emit errorOccured(m_error);
}
void TunnelProxyRemoteConnection::cleanUp()
{
if (m_jsonClient) {
m_jsonClient->deleteLater();
m_jsonClient = nullptr;
}
if (m_connection) {
m_connection->deleteLater();
m_connection = nullptr;
}
m_remoteProxyServer.clear();
m_remoteProxyServerName.clear();
m_remoteProxyServerVersion.clear();
m_remoteProxyApiVersion.clear();
setState(StateDisconnected);
}
}

View File

@ -1,16 +1,139 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2021, 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 Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the terms of the GNU
* Lesser General Public License as published by the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser 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 TUNNELPROXYREMOTECONNECTION_H
#define TUNNELPROXYREMOTECONNECTION_H
#include <QUuid>
#include <QObject>
#include <QLoggingCategory>
#include "proxyconnection.h"
Q_DECLARE_LOGGING_CATEGORY(dcTunnelProxyRemoteConnection)
namespace remoteproxyclient {
class JsonRpcClient;
class TunnelProxyRemoteConnection : public QObject
{
Q_OBJECT
public:
explicit TunnelProxyRemoteConnection(QObject *parent = nullptr);
enum State {
StateConnecting,
StateHostLookup,
StateConnected,
StateInitializing,
StateRegister,
StateRemoteConnected,
StateDiconnecting,
StateDisconnected,
};
Q_ENUM(State)
enum ConnectionType {
ConnectionTypeWebSocket,
ConnectionTypeTcpSocket
};
Q_ENUM(ConnectionType)
explicit TunnelProxyRemoteConnection(const QUuid &clientUuid, const QString &clientName, QObject *parent = nullptr);
explicit TunnelProxyRemoteConnection(const QUuid &clientUuid, const QString &clientName, ConnectionType connectionType, QObject *parent = nullptr);
~TunnelProxyRemoteConnection();
bool remoteConnected() const;
QAbstractSocket::SocketError error() const;
void ignoreSslErrors();
void ignoreSslErrors(const QList<QSslError> &errors);
QString remoteProxyServer() const;
QString remoteProxyServerName() const;
QString remoteProxyServerVersion() const;
QString remoteProxyApiVersion() const;
public slots:
bool connectServer(const QUrl &url, const QUuid &serverUuid);
void disconnectServer();
signals:
void stateChanged(TunnelProxyRemoteConnection::State state);
void errorOccured(QAbstractSocket::SocketError error);
void sslErrors(const QList<QSslError> &errors);
void remoteConnectedChanged(bool remoteConnected);
void dataReady(const QByteArray &data);
private slots:
void onConnectionChanged(bool connected);
void onConnectionDataAvailable(const QByteArray &data);
void onConnectionSocketError(QAbstractSocket::SocketError error);
void onConnectionStateChanged(QAbstractSocket::SocketState state);
// Initialization calls
void onHelloFinished();
void onClientRegistrationFinished();
private:
// This server information
QUuid m_clientUuid;
QString m_clientName;
ConnectionType m_connectionType = ConnectionTypeTcpSocket;
QUuid m_serverUuid;
bool m_remoteConnected = false;
// Remote proxy server information
QString m_remoteProxyServer;
QString m_remoteProxyServerName;
QString m_remoteProxyServerVersion;
QString m_remoteProxyApiVersion;
QUrl m_serverUrl;
QAbstractSocket::SocketError m_error = QAbstractSocket::UnknownSocketError;
State m_state = StateDisconnected;
ProxyConnection *m_connection = nullptr;
JsonRpcClient *m_jsonClient = nullptr;
void setState(State state);
void setRemoteConnected(bool remoteConnected);
void setError(QAbstractSocket::SocketError error);
void cleanUp();
};
}
#endif // TUNNELPROXYREMOTECONNECTION_H

View File

@ -26,12 +26,63 @@
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "tunnelproxysocket.h"
#include "proxyconnection.h"
#include "../common/slipdataprocessor.h"
namespace remoteproxyclient {
TunnelProxySocket::TunnelProxySocket(QObject *parent) : QObject(parent)
TunnelProxySocket::TunnelProxySocket(ProxyConnection *connection, const QString &clientName, const QUuid &clientUuid, const QHostAddress &clientPeerAddress, quint16 socketAddress, QObject *parent) :
QObject(parent),
m_connection(connection),
m_clientName(clientName),
m_clientUuid(clientUuid),
m_clientPeerAddress(clientPeerAddress),
m_socketAddress(socketAddress)
{
}
QUuid TunnelProxySocket::clientUuid() const
{
return m_clientUuid;
}
QString TunnelProxySocket::clientName() const
{
return m_clientName;
}
QHostAddress TunnelProxySocket::clientPeerAddress() const
{
return m_clientPeerAddress;
}
quint16 TunnelProxySocket::socketAddress() const
{
return m_socketAddress;
}
void TunnelProxySocket::writeData(const QByteArray &data)
{
SlipDataProcessor::Frame frame;
frame.socketAddress = m_socketAddress;
frame.data = data;
m_connection->sendData(SlipDataProcessor::serializeData(SlipDataProcessor::buildFrame(frame)));
}
void TunnelProxySocket::disconnectSocket()
{
}
QDebug operator<<(QDebug debug, TunnelProxySocket *tunnelProxySocket)
{
debug.nospace() << "TunnelProxySocket(";
debug.nospace() << tunnelProxySocket->clientName() << ", ";
debug.nospace() << tunnelProxySocket->clientUuid().toString() << ", ";
debug.nospace() << tunnelProxySocket->clientPeerAddress().toString() << ", ";
debug.nospace() << tunnelProxySocket->socketAddress() << ")";
return debug.space();
}
}

View File

@ -28,20 +28,48 @@
#ifndef TUNNELPROXYSOCKET_H
#define TUNNELPROXYSOCKET_H
#include <QUuid>
#include <QObject>
#include <QHostAddress>
namespace remoteproxyclient {
class ProxyConnection;
class TunnelProxySocket : public QObject
{
Q_OBJECT
friend class TunnelProxySocketServer;
public:
explicit TunnelProxySocket(QObject *parent = nullptr);
QUuid clientUuid() const;
QString clientName() const;
QHostAddress clientPeerAddress() const;
quint16 socketAddress() const;
void writeData(const QByteArray &data);
void disconnectSocket();
signals:
void dataReceived(const QByteArray &data);
void disconnected();
private:
explicit TunnelProxySocket(ProxyConnection *connection, const QString &clientName, const QUuid &clientUuid, const QHostAddress &clientPeerAddress, quint16 socketAddress, QObject *parent = nullptr);
~TunnelProxySocket() = default;
ProxyConnection *m_connection = nullptr;
QString m_clientName;
QUuid m_clientUuid;
QHostAddress m_clientPeerAddress;
quint16 m_socketAddress = 0xFFFF;
};
QDebug operator<<(QDebug debug, TunnelProxySocket *tunnelProxySocket);
}
#endif // TUNNELPROXYSOCKET_H

View File

@ -30,8 +30,10 @@
#include "tcpsocketconnection.h"
#include "websocketconnection.h"
#include "proxyjsonrpcclient.h"
#include "../common/slipdataprocessor.h"
Q_LOGGING_CATEGORY(dcTunnelProxySocketServer, "dcTunnelProxySocketServer")
Q_LOGGING_CATEGORY(dcTunnelProxySocketServerTraffic, "dcTunnelProxySocketServerTraffic")
namespace remoteproxyclient {
@ -67,10 +69,14 @@ QAbstractSocket::SocketError TunnelProxySocketServer::error() const
return m_error;
}
TunnelProxySocketServer::Error TunnelProxySocketServer::serverError() const
{
return m_serverError;
}
void TunnelProxySocketServer::ignoreSslErrors()
{
m_connection->ignoreSslErrors();
}
void TunnelProxySocketServer::ignoreSslErrors(const QList<QSslError> &errors)
@ -78,6 +84,32 @@ void TunnelProxySocketServer::ignoreSslErrors(const QList<QSslError> &errors)
m_connection->ignoreSslErrors(errors);
}
QUrl TunnelProxySocketServer::serverUrl() const
{
return m_serverUrl;
}
QString TunnelProxySocketServer::remoteProxyServer() const
{
return m_remoteProxyServer;
}
QString TunnelProxySocketServer::remoteProxyServerName() const
{
return m_remoteProxyServerName;
}
QString TunnelProxySocketServer::remoteProxyServerVersion() const
{
return m_remoteProxyServerVersion;
}
QString TunnelProxySocketServer::remoteProxyApiVersion() const
{
return m_remoteProxyApiVersion;
}
void TunnelProxySocketServer::startServer(const QUrl &serverUrl)
{
m_serverUrl = serverUrl;
@ -103,7 +135,8 @@ void TunnelProxySocketServer::startServer(const QUrl &serverUrl)
connect(m_connection, &ProxyConnection::sslErrors, this, &TunnelProxySocketServer::sslErrors);
m_jsonClient = new JsonRpcClient(m_connection, this);
// connect(m_jsonClient, &JsonRpcClient::tunnelEstablished, this, &RemoteProxyConnection::onTunnelEstablished);
connect(m_jsonClient, &JsonRpcClient::tunnelProxyClientConnected, this, &TunnelProxySocketServer::onTunnelProxyClientConnected);
connect(m_jsonClient, &JsonRpcClient::tunnelProxyClientDisonnected, this, &TunnelProxySocketServer::onTunnelProxyClientDisconnected);
qCDebug(dcTunnelProxySocketServer()) << "Connecting to" << m_serverUrl.toString();
m_connection->connectServer(m_serverUrl);
@ -111,7 +144,10 @@ void TunnelProxySocketServer::startServer(const QUrl &serverUrl)
void TunnelProxySocketServer::stopServer()
{
// Close the server connection and all related sockets
if (m_connection) {
qCDebug(dcTunnelProxySocketServer()) << "Disconnecting from" << m_connection->serverUrl().toString();
m_connection->disconnectServer();
}
}
void TunnelProxySocketServer::onConnectionChanged(bool connected)
@ -119,6 +155,7 @@ void TunnelProxySocketServer::onConnectionChanged(bool connected)
if (connected) {
qCDebug(dcTunnelProxySocketServer()) << "Connected to remote proxy server.";
setState(StateConnected);
m_serverError = TunnelProxySocketServer::ErrorNoError;
setState(StateInitializing);
JsonReply *reply = m_jsonClient->callHello();
@ -126,18 +163,53 @@ void TunnelProxySocketServer::onConnectionChanged(bool connected)
} else {
qCDebug(dcTunnelProxySocketServer()) << "Disconnected from remote proxy server.";
setState(StateDisconnected);
setServerError(ErrorNotConnected);
cleanUp();
}
}
void TunnelProxySocketServer::onConnectionDataAvailable(const QByteArray &data)
{
qCDebug(dcTunnelProxySocketServerTraffic()) << "Data received" << qUtf8Printable(data);
if (m_state != StateRunning) {
m_jsonClient->processData(data);
m_dataBuffer.clear();
return;
}
// Parse SLIP
// Parse SLIP frame
for (int i = 0; i < data.length(); i++) {
quint8 byte = static_cast<quint8>(data.at(i));
if (byte == SlipDataProcessor::ProtocolByteEnd) {
// If there is no data...continue since it might be a starting END byte
if (m_dataBuffer.isEmpty())
continue;
QByteArray frameData = SlipDataProcessor::deserializeData(m_dataBuffer);
if (frameData.isNull()) {
qCWarning(dcTunnelProxySocketServerTraffic()) << "Received inconsistant SLIP encoded message. Ignoring data...";
} else {
SlipDataProcessor::Frame frame = SlipDataProcessor::parseFrame(frameData);
qCDebug(dcTunnelProxySocketServerTraffic()) << "Frame received" << frame.socketAddress << qUtf8Printable(frame.data);
if (frame.socketAddress == 0x0000) {
m_jsonClient->processData(frame.data);
} else {
// Find the socket and emit the data received signal
TunnelProxySocket *tunnlProxySocket = m_tunnelProxySockets.value(frame.socketAddress);
if (!tunnlProxySocket) {
qCWarning(dcTunnelProxySocketServer()) << "Received data from unknown tunnel proxy client with address" << frame.socketAddress << "...ignoring the data";
} else {
emit tunnlProxySocket->dataReceived(frame.data);
}
}
}
m_dataBuffer.clear();
} else {
m_dataBuffer.append(data.at(i));
}
}
}
void TunnelProxySocketServer::onConnectionSocketError(QAbstractSocket::SocketError error)
@ -181,6 +253,7 @@ void TunnelProxySocketServer::onHelloFinished()
if (response.value("status").toString() != "success") {
qCWarning(dcTunnelProxySocketServer()) << "Could not get initial information from proxy server.";
m_connection->disconnectServer();
setServerError(ErrorConnectionError);
return;
}
@ -207,6 +280,7 @@ void TunnelProxySocketServer::onServerRegistrationFinished()
if (response.value("status").toString() != "success") {
qCWarning(dcTunnelProxySocketServer()) << "JSON RPC error. Failed to register as tunnel server on the remote proxy server.";
m_connection->disconnectServer();
setServerError(ErrorConnectionError);
return;
}
@ -214,11 +288,34 @@ void TunnelProxySocketServer::onServerRegistrationFinished()
if (responseParams.value("tunnelProxyError").toString() != "TunnelProxyErrorNoError") {
qCWarning(dcTunnelProxySocketServer()) << "Failed to register as tunnel server on the remote proxy server:" << responseParams.value("tunnelProxyError").toString();
m_connection->disconnectServer();
setServerError(ErrorConnectionError);
return;
}
qCDebug(dcTunnelProxySocketServer()) << "Registered successfully as tunnel server on the remote proxy server.";
setState(StateRunning);
m_serverError = ErrorNoError;
}
void TunnelProxySocketServer::onTunnelProxyClientConnected(const QString &clientName, const QUuid &clientUuid, const QString &clientPeerAddress, quint16 socketAddress)
{
TunnelProxySocket *tunnelProxySocket = new TunnelProxySocket(m_connection, clientName, clientUuid, QHostAddress(clientPeerAddress), socketAddress, this);
qCDebug(dcTunnelProxySocketServer()) << "--> New client connected" << tunnelProxySocket;
m_tunnelProxySockets.insert(socketAddress, tunnelProxySocket);
emit clientConnected(tunnelProxySocket);
}
void TunnelProxySocketServer::onTunnelProxyClientDisconnected(quint16 socketAddress)
{
TunnelProxySocket *tunnelProxySocket = m_tunnelProxySockets.take(socketAddress);
if (!tunnelProxySocket) {
qCWarning(dcTunnelProxySocketServer()) << "Unknown client disconnected" << socketAddress << ". Ignoring the event.";
return;
}
qCDebug(dcTunnelProxySocketServer()) << "--> Client disconnected" << tunnelProxySocket;
emit tunnelProxySocket->disconnected();
tunnelProxySocket->deleteLater();
}
void TunnelProxySocketServer::setState(State state)
@ -253,10 +350,21 @@ void TunnelProxySocketServer::setError(QAbstractSocket::SocketError error)
emit errorOccured(m_error);
}
void TunnelProxySocketServer::setServerError(Error error)
{
if (m_serverError == error)
return;
qCDebug(dcTunnelProxySocketServer()) << "Server error occured" << error;
m_serverError = error;
emit serverErrorOccured(m_serverError);
}
void TunnelProxySocketServer::cleanUp()
{
// TODO: cleanup client connections
foreach (quint16 socketAddress, m_tunnelProxySockets.keys()) {
onTunnelProxyClientDisconnected(socketAddress);
}
if (m_jsonClient) {
m_jsonClient->deleteLater();
@ -268,8 +376,10 @@ void TunnelProxySocketServer::cleanUp()
m_connection = nullptr;
}
// m_serverName = QString();
// m_serverUuid = QUuid();
m_remoteProxyServer.clear();
m_remoteProxyServerName.clear();
m_remoteProxyServerVersion.clear();
m_remoteProxyApiVersion.clear();
setState(StateDisconnected);
}

View File

@ -32,14 +32,14 @@
#include <QObject>
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(dcTunnelProxySocketServer)
#include "proxyconnection.h"
#include "tunnelproxysocket.h"
Q_DECLARE_LOGGING_CATEGORY(dcTunnelProxySocketServer)
Q_DECLARE_LOGGING_CATEGORY(dcTunnelProxySocketServerTraffic)
namespace remoteproxyclient {
class ProxyConnection;
class JsonRpcClient;
class TunnelProxySocketServer : public QObject
@ -64,6 +64,15 @@ public:
};
Q_ENUM(ConnectionType)
enum Error {
ErrorNoError,
ErrorConnectionError,
ErrorInvalidAddress,
ErrorNotConnected,
ErrorNotRegistered
};
Q_ENUM(Error)
explicit TunnelProxySocketServer(const QUuid &serverUuid, const QString &serverName, QObject *parent = nullptr);
explicit TunnelProxySocketServer(const QUuid &serverUuid, const QString &serverName, ConnectionType connectionType, QObject *parent = nullptr);
~TunnelProxySocketServer();
@ -71,21 +80,32 @@ public:
bool running() const;
QAbstractSocket::SocketError error() const;
Error serverError() const;
void ignoreSslErrors();
void ignoreSslErrors(const QList<QSslError> &errors);
QUrl serverUrl() const;
QString remoteProxyServer() const;
QString remoteProxyServerName() const;
QString remoteProxyServerVersion() const;
QString remoteProxyApiVersion() const;
public slots:
void startServer(const QUrl &serverUrl);
void stopServer();
signals:
void runningChanged(bool running);
void stateChanged(TunnelProxySocketServer::State state);
void errorOccured(QAbstractSocket::SocketError error);
void serverErrorOccured(Error error);
void sslErrors(const QList<QSslError> &errors);
void runningChanged(bool running);
void clientConnected(TunnelProxySocket *tunnelProxySocket);
void clientDisconnected(TunnelProxySocket *tunnelProxySocket);
private slots:
void onConnectionChanged(bool connected);
@ -98,6 +118,10 @@ private slots:
void onHelloFinished();
void onServerRegistrationFinished();
// Client notifications
void onTunnelProxyClientConnected(const QString &clientName, const QUuid &clientUuid, const QString &clientPeerAddress, quint16 socketAddress);
void onTunnelProxyClientDisconnected(quint16 socketAddress);
private:
// This server information
QUuid m_serverUuid;
@ -113,14 +137,20 @@ private:
bool m_running = false;
QUrl m_serverUrl;
QAbstractSocket::SocketError m_error = QAbstractSocket::UnknownSocketError;
Error m_serverError = ErrorNoError;
State m_state = StateDisconnected;
ProxyConnection *m_connection = nullptr;
JsonRpcClient *m_jsonClient = nullptr;
QHash<quint16, TunnelProxySocket *> m_tunnelProxySockets;
QByteArray m_dataBuffer;
void setState(State state);
void setRunning(bool running);
void setError(QAbstractSocket::SocketError error);
void setServerError(Error error);
void cleanUp();
};

View File

@ -4,11 +4,11 @@ TEMPLATE=subdirs
SUBDIRS += server client libnymea-remoteproxy libnymea-remoteproxyclient
!disabletests {
SUBDIRS+=tests
SUBDIRS += tests
}
!disablemonitor {
SUBDIRS+=monitor
SUBDIRS += monitor
}
server.depends = libnymea-remoteproxy
@ -26,8 +26,14 @@ message("----------------------------------------------------------")
message("JSON-RPC API version $${API_VERSION_MAJOR}.$${API_VERSION_MINOR}")
message("Qt version:" $$[QT_VERSION])
coverage { message("Building with coverage report") }
ccache { message("Building with ccache support") }
coverage {
message("Building with coverage report")
}
ccache {
message("Building with ccache support")
}
disablemonitor {
message("Building without the monitor")
}

View File

@ -33,6 +33,7 @@
// Client
#include "tunnelproxy/tunnelproxysocketserver.h"
#include "tunnelproxy/tunnelproxyremoteconnection.h"
#include <QMetaType>
#include <QSignalSpy>
@ -296,6 +297,8 @@ void RemoteProxyTestsTunnelProxy::registerClient()
QVERIFY(!response.isEmpty());
QVERIFY(response.value("status").toString() == "success");
QVERIFY(response.value("params").toMap().contains("tunnelProxyError"));
QVERIFY(response.value("params").toMap().contains("slipEnabled"));
//QVERIFY(response.value("params").toMap().value("slipEnabled").toBool()));
verifyTunnelProxyError(response, expectedServerError);
}
@ -358,27 +361,24 @@ void RemoteProxyTestsTunnelProxy::registerServerDuplicated()
QVERIFY(!response.isEmpty());
QVERIFY(response.value("status").toString() == "success");
QVERIFY(response.value("params").toMap().contains("tunnelProxyError"));
QVERIFY(response.value("params").toMap().contains("slipEnabled"));
QVERIFY(response.value("params").toMap().value("slipEnabled").toBool());
verifyTunnelProxyError(response);
// SLIP Should be enabled now
// Try to register again with the same uuid on the same socket
result = invokeTcpSocketTunnelProxyApiCallPersistant("TunnelProxy.RegisterServer", params, socket);
result = invokeTcpSocketTunnelProxyApiCallPersistant("TunnelProxy.RegisterServer", params, true, socket);
response = result.first.toMap();
QVERIFY(response.value("status").toString() == "success");
QVERIFY(response.value("params").toMap().contains("tunnelProxyError"));
verifyTunnelProxyError(response, TunnelProxyServer::TunnelProxyErrorAlreadyRegistered);
// Try to register an invalid server uuid
QVariantMap paramsInvalidUuid;
paramsInvalidUuid.insert("serverName", serverName);
paramsInvalidUuid.insert("serverUuid", QUuid().toString());
result = invokeTcpSocketTunnelProxyApiCallPersistant("TunnelProxy.RegisterServer", paramsInvalidUuid, socket);
response = result.first.toMap();
QVERIFY(response.value("status").toString() == "success");
QVERIFY(response.value("params").toMap().contains("tunnelProxyError"));
verifyTunnelProxyError(response, TunnelProxyServer::TunnelProxyErrorInvalidUuid);
QSignalSpy disconnectedTcpSpy(socket, &QSslSocket::disconnected);
QVERIFY(disconnectedTcpSpy.wait());
QVERIFY(disconnectedTcpSpy.count() == 1);
// Close the tcp socket
socket->close();
delete socket;
QTest::qWait(100);
@ -394,18 +394,16 @@ void RemoteProxyTestsTunnelProxy::registerServerDuplicated()
verifyTunnelProxyError(response);
// Try to register again with the same uuid on the same socket
resultWebSocket = invokeWebSocketTunnelProxyApiCallPersistant("TunnelProxy.RegisterServer", params, webSocket);
resultWebSocket = invokeWebSocketTunnelProxyApiCallPersistant("TunnelProxy.RegisterServer", params, true, webSocket);
response = resultWebSocket.first.toMap();
QVERIFY(response.value("status").toString() == "success");
QVERIFY(response.value("params").toMap().contains("tunnelProxyError"));
verifyTunnelProxyError(response, TunnelProxyServer::TunnelProxyErrorAlreadyRegistered);
// Try to register an invalid server uuid
resultWebSocket = invokeWebSocketTunnelProxyApiCallPersistant("TunnelProxy.RegisterServer", paramsInvalidUuid, webSocket);
response = resultWebSocket.first.toMap();
QVERIFY(response.value("status").toString() == "success");
QVERIFY(response.value("params").toMap().contains("tunnelProxyError"));
verifyTunnelProxyError(response, TunnelProxyServer::TunnelProxyErrorInvalidUuid);
QSignalSpy disconnectedWebSocketSpy(webSocket, &QWebSocket::disconnected);
QVERIFY(disconnectedWebSocketSpy.wait());
QVERIFY(disconnectedWebSocketSpy.count() == 1);
webSocket->close();
delete webSocket;
@ -470,7 +468,6 @@ void RemoteProxyTestsTunnelProxy::registerClientDuplicated()
QVERIFY(response.value("params").toMap().contains("tunnelProxyError"));
verifyTunnelProxyError(response, TunnelProxyServer::TunnelProxyErrorAlreadyRegistered);
// CleanUp
if (clientSocketTcp) {
// Close the tcp socket
@ -501,8 +498,10 @@ void RemoteProxyTestsTunnelProxy::crossRegisterServerClient()
// Start the server
startServer();
resetDebugCategories();
resetDebugCategories();
addDebugCategory("TunnelProxyServer.debug=true");
addDebugCategory("TunnelProxyServerTraffic.debug=true");
addDebugCategory("JsonRpcTraffic.debug=true");
// Create the server and keep it up
@ -512,7 +511,7 @@ void RemoteProxyTestsTunnelProxy::crossRegisterServerClient()
serverParams.insert("serverName", serverName);
serverParams.insert("serverUuid", serverUuid.toString());
// TCP socket
// TCP server
QPair<QVariant, QSslSocket *> serverResult = invokeTcpSocketTunnelProxyApiCallPersistant("TunnelProxy.RegisterServer", serverParams);
QVariantMap response = serverResult.first.toMap();
QSslSocket *serverSocket = serverResult.second;
@ -521,6 +520,7 @@ void RemoteProxyTestsTunnelProxy::crossRegisterServerClient()
QVERIFY(response.value("params").toMap().contains("tunnelProxyError"));
verifyTunnelProxyError(response);
// SLIP is enabled now
// Now try to register as client
QVariantMap clientParams;
@ -528,13 +528,17 @@ void RemoteProxyTestsTunnelProxy::crossRegisterServerClient()
clientParams.insert("clientUuid", QUuid::createUuid().toString());
clientParams.insert("serverUuid", serverUuid.toString());
QPair<QVariant, QSslSocket *> result = invokeTcpSocketTunnelProxyApiCallPersistant("TunnelProxy.RegisterClient", clientParams, serverSocket);
QPair<QVariant, QSslSocket *> result = invokeTcpSocketTunnelProxyApiCallPersistant("TunnelProxy.RegisterClient", clientParams, true, serverSocket);
response = result.first.toMap();
QVERIFY(!response.isEmpty());
QVERIFY(response.value("status").toString() == "success");
QVERIFY(response.value("params").toMap().contains("tunnelProxyError"));
verifyTunnelProxyError(response, TunnelProxyServer::TunnelProxyErrorAlreadyRegistered);
QSignalSpy disconnectedSpy(serverSocket, &QSslSocket::disconnected);
QVERIFY(disconnectedSpy.wait());
QVERIFY(disconnectedSpy.count() == 1);
serverSocket->close();
delete serverSocket;
@ -551,8 +555,79 @@ void RemoteProxyTestsTunnelProxy::testTunnelProxyServer()
resetDebugCategories();
addDebugCategory("TunnelProxyServer.debug=true");
addDebugCategory("TunnelProxyServerTraffic.debug=true");
addDebugCategory("JsonRpcTraffic.debug=true");
addDebugCategory("TunnelProxySocketServer.debug=true");
addDebugCategory("TunnelProxyRemoteConnection.debug=true");
addDebugCategory("RemoteProxyClientJsonRpcTraffic.debug=true");
// Tunnel proxy socket server
QString serverName = "SuperDuper server name";
QUuid serverUuid = QUuid::createUuid();
TunnelProxySocketServer *tunnelProxyServer = new TunnelProxySocketServer(serverUuid, serverName, this);
connect(tunnelProxyServer, &TunnelProxySocketServer::sslErrors, this, [=](const QList<QSslError> &errors){
tunnelProxyServer->ignoreSslErrors(errors);
});
tunnelProxyServer->startServer(m_serverUrlTunnelProxyTcp);
QSignalSpy serverRunningSpy(tunnelProxyServer, &TunnelProxySocketServer::runningChanged);
serverRunningSpy.wait();
QVERIFY(serverRunningSpy.count() == 1);
QList<QVariant> arguments = serverRunningSpy.takeFirst();
QVERIFY(arguments.at(0).toBool() == true);
QVERIFY(tunnelProxyServer->running());
// Tunnel proxy client connection
QString clientName = "Awesome client name";
QUuid clientUuid = QUuid::createUuid();
TunnelProxyRemoteConnection *clientConnection = new TunnelProxyRemoteConnection(clientUuid, clientName, this);
connect(clientConnection, &TunnelProxyRemoteConnection::sslErrors, this, [=](const QList<QSslError> &errors){
clientConnection->ignoreSslErrors(errors);
});
clientConnection->connectServer(m_serverUrlTunnelProxyTcp, serverUuid);
QSignalSpy clientConnectedSpy(tunnelProxyServer, &TunnelProxySocketServer::clientConnected);
QVERIFY(clientConnectedSpy.wait());
QVERIFY(clientConnectedSpy.count() == 1);
arguments = clientConnectedSpy.takeFirst();
TunnelProxySocket *tunnelProxySocket = arguments.at(0).value<TunnelProxySocket *>();
QVERIFY(tunnelProxySocket->clientName() == clientName);
QVERIFY(tunnelProxySocket->clientUuid() == clientUuid);
// Stop the server connection and verify the client gets disconnected
tunnelProxyServer->stopServer();
QSignalSpy disconnectedSpy(clientConnection, &TunnelProxyRemoteConnection::remoteConnectedChanged);
QVERIFY(disconnectedSpy.wait());
QVERIFY(disconnectedSpy.count() == 1);
arguments = disconnectedSpy.takeFirst();
QVERIFY(arguments.at(0).toBool() == false);
QVERIFY(!clientConnection->remoteConnected());
resetDebugCategories();
// Clean up
stopServer();
}
void RemoteProxyTestsTunnelProxy::testTunnelProxyClient()
{
// Start the server
startServer();
resetDebugCategories();
addDebugCategory("TunnelProxyServer.debug=true");
addDebugCategory("TunnelProxyServerTraffic.debug=true");
addDebugCategory("JsonRpcTraffic.debug=true");
addDebugCategory("TunnelProxySocketServer.debug=true");
addDebugCategory("TunnelProxyRemoteConnection.debug=true");
addDebugCategory("RemoteProxyClientJsonRpcTraffic.debug=true");
// Tunnel proxy socket server
QString serverName = "SuperDuper server name";
@ -574,6 +649,24 @@ void RemoteProxyTestsTunnelProxy::testTunnelProxyServer()
// Tunnel proxy client connection
QString clientName = "Awesome client name";
QUuid clientUuid = QUuid::createUuid();
TunnelProxyRemoteConnection *clientConnection = new TunnelProxyRemoteConnection(clientUuid, clientName, this);
connect(clientConnection, &TunnelProxyRemoteConnection::sslErrors, this, [=](const QList<QSslError> &errors){
clientConnection->ignoreSslErrors(errors);
});
clientConnection->connectServer(m_serverUrlTunnelProxyTcp, serverUuid);
QSignalSpy clientRemoteConnectedSpy(clientConnection, &TunnelProxyRemoteConnection::remoteConnectedChanged);
QVERIFY(clientRemoteConnectedSpy.wait());
QVERIFY(clientRemoteConnectedSpy.count() == 1);
arguments = clientRemoteConnectedSpy.takeFirst();
QVERIFY(arguments.at(0).toBool() == true);
QVERIFY(clientConnection->remoteConnected());
// Verify server socket connected

View File

@ -66,6 +66,7 @@ private slots:
// Client classes
void testTunnelProxyServer();
void testTunnelProxyClient();
};

View File

@ -31,6 +31,8 @@
#include "loggingcategories.h"
#include "remoteproxyconnection.h"
#include "../common/slipdataprocessor.h"
#include <QSslError>
#include <QMetaType>
#include <QSignalSpy>
@ -656,7 +658,7 @@ QVariant BaseTest::injectTcpSocketTunnelProxyData(const QByteArray &data)
return jsonDoc.toVariant();
}
QPair<QVariant, QSslSocket *> BaseTest::invokeTcpSocketTunnelProxyApiCallPersistant(const QString &method, const QVariantMap params, QSslSocket *existingSocket)
QPair<QVariant, QSslSocket *> BaseTest::invokeTcpSocketTunnelProxyApiCallPersistant(const QString &method, const QVariantMap params, bool slipEnabled, QSslSocket *existingSocket)
{
QSslSocket *socket = nullptr;
if (!existingSocket) {
@ -680,30 +682,66 @@ QPair<QVariant, QSslSocket *> BaseTest::invokeTcpSocketTunnelProxyApiCallPersist
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);
socket->write(jsonDoc.toJson(QJsonDocument::Compact) + '\n');
if (slipEnabled) {
SlipDataProcessor::Frame frame;
frame.socketAddress = 0x0000;
frame.data = payload;
socket->write(SlipDataProcessor::serializeData(SlipDataProcessor::buildFrame(frame)));
} else {
socket->write(payload);
}
// FIXME: check why it waits the full time here
dataSpy.wait(500);
if (dataSpy.count() != 1) {
if (dataSpy.count() < 1) {
qWarning() << "No data received";
return QPair<QVariant, QSslSocket *>(QVariant(), socket);
}
QByteArray data = socket->readAll();
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;
// 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);
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, 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();
qWarning() << "JSON parser error" << error.errorString() << qUtf8Printable(data);
return QPair<QVariant, QSslSocket *>(QVariant(), socket);
}
@ -718,7 +756,7 @@ QPair<QVariant, QSslSocket *> BaseTest::invokeTcpSocketTunnelProxyApiCallPersist
return QPair<QVariant, QSslSocket *>(QVariant(), socket);
}
QPair<QVariant, QWebSocket *> BaseTest::invokeWebSocketTunnelProxyApiCallPersistant(const QString &method, const QVariantMap params, QWebSocket *existingSocket)
QPair<QVariant, QWebSocket *> BaseTest::invokeWebSocketTunnelProxyApiCallPersistant(const QString &method, const QVariantMap params, bool slipEnabled, QWebSocket *existingSocket)
{
QWebSocket *socket = nullptr;
if (!existingSocket) {
@ -740,16 +778,55 @@ QPair<QVariant, QWebSocket *> BaseTest::invokeWebSocketTunnelProxyApiCallPersist
request.insert("method", method);
request.insert("params", params);
QJsonDocument jsonDoc = QJsonDocument::fromVariant(request);
QByteArray payload = jsonDoc.toJson(QJsonDocument::Compact) + '\n';
QSignalSpy dataSpy(socket, SIGNAL(textMessageReceived(QString)));
socket->sendTextMessage(QString(jsonDoc.toJson(QJsonDocument::Compact)));
if (slipEnabled) {
SlipDataProcessor::Frame frame;
frame.socketAddress = 0x0000;
frame.data = payload;
socket->sendTextMessage(QString(SlipDataProcessor::serializeData(SlipDataProcessor::buildFrame(frame))));
} else {
socket->sendTextMessage(QString(payload));
}
dataSpy.wait();
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 QPair<QVariant, QWebSocket *>(QVariant(), socket);
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
@ -771,6 +848,7 @@ QPair<QVariant, QWebSocket *> BaseTest::invokeWebSocketTunnelProxyApiCallPersist
}
}
qWarning() << "No websocket data received...";
m_commandCounter++;
return QPair<QVariant, QWebSocket *>(QVariant(), socket);
}

View File

@ -97,8 +97,8 @@ protected:
QVariant invokeTcpSocketTunnelProxyApiCall(const QString &method, const QVariantMap params = QVariantMap());
QVariant injectTcpSocketTunnelProxyData(const QByteArray &data);
QPair<QVariant, QSslSocket *> invokeTcpSocketTunnelProxyApiCallPersistant(const QString &method, const QVariantMap params = QVariantMap(), QSslSocket *existingSocket = nullptr);
QPair<QVariant, QWebSocket *> invokeWebSocketTunnelProxyApiCallPersistant(const QString &method, const QVariantMap params = QVariantMap(), QWebSocket *existingSocket = nullptr);
QPair<QVariant, QSslSocket *> invokeTcpSocketTunnelProxyApiCallPersistant(const QString &method, const QVariantMap params = QVariantMap(), bool slipEnabled = false, QSslSocket *existingSocket = nullptr);
QPair<QVariant, QWebSocket *> invokeWebSocketTunnelProxyApiCallPersistant(const QString &method, const QVariantMap params = QVariantMap(), bool slipEnabled = false, QWebSocket *existingSocket = nullptr);
bool createRemoteConnection(const QString &token, const QString &nonce, QObject *parent);

View File

@ -1,5 +1,7 @@
RESOURCES += ../resources/resources.qrc
include(../common/common.pri)
CONFIG += testcase
QT += testlib