diff --git a/common/common.pri b/common/common.pri new file mode 100644 index 0000000..f1eddb5 --- /dev/null +++ b/common/common.pri @@ -0,0 +1,7 @@ +INCLUDEPATH += $$PWD + +HEADERS += \ + $$PWD/slipdataprocessor.h + +SOURCES += \ + $$PWD/slipdataprocessor.cpp diff --git a/common/slipdataprocessor.cpp b/common/slipdataprocessor.cpp new file mode 100644 index 0000000..c02709d --- /dev/null +++ b/common/slipdataprocessor.cpp @@ -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 . +* +* 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 + +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(data.at(i)); + + if (escaped) { + if (byte == ProtocolByteTransposedEnd) { + deserializedData.append(static_cast(ProtocolByteEnd)); + } else if (byte == ProtocolByteTransposedEsc) { + deserializedData.append(static_cast(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(byte)); + } + } + + return deserializedData; +} + +QByteArray SlipDataProcessor::serializeData(const QByteArray &data) +{ + QByteArray serializedData; + QDataStream stream(&serializedData, QIODevice::WriteOnly); +// stream << static_cast(ProtocolByteEnd); + + for (int i = 0; i < data.length(); i++) { + quint8 byte = static_cast(data.at(i)); + switch (byte) { + case ProtocolByteEnd: + stream << static_cast(ProtocolByteEsc); + stream << static_cast(ProtocolByteTransposedEnd); + break; + case ProtocolByteEsc: + stream << static_cast(ProtocolByteEsc); + stream << static_cast(ProtocolByteTransposedEsc); + break; + default: + stream << byte; + break; + } + } + + // Add the protocol end byte + stream << static_cast(ProtocolByteEnd); + + return serializedData; +} + +SlipDataProcessor::Frame SlipDataProcessor::parseFrame(const QByteArray &data) +{ + Frame frame; + frame.socketAddress = static_cast(static_cast(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; +} diff --git a/common/slipdataprocessor.h b/common/slipdataprocessor.h new file mode 100644 index 0000000..9e848f8 --- /dev/null +++ b/common/slipdataprocessor.h @@ -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 . +* +* 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 +#include + +// 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 diff --git a/docs/remote-connection-basic-flow.png b/docs/remote-connection-basic-flow.png index 5048002..dceb02d 100644 Binary files a/docs/remote-connection-basic-flow.png and b/docs/remote-connection-basic-flow.png differ diff --git a/docs/remote-connection-basic-flow.svg b/docs/remote-connection-basic-flow.svg index 1d5a26c..f3bf1ea 100644 --- a/docs/remote-connection-basic-flow.svg +++ b/docs/remote-connection-basic-flow.svg @@ -1 +1 @@ -title%20Remote%20tunnel%20proxy%0A%0Anymea-%3Eproxy%3A%20TunnelProxy.RegisterServer(serverUuid%2C%20serverName)%0A%0Anote%20over%20proxy%3A%20Register%20the%20server%20using%20the%20uuid%0A%0Anymea%3C-proxy%3A%20TunnelProxyErrorNoError%0A%0Anote%20over%20nymea%2C%20proxy%3A%20Protocol%20from%20now%20on%20SLIP%5CnThe%20proxy%20is%20client%200x0000%0A%0Anote%20over%20nymea%2C%20proxy%3A%20SLIP%20encoded%20data%3A%202%20Bytes%20address%20%2B%20data%0A%0Aproxy%3C-client%3A%20TunnelProxy.RegisterClient(clientUuid%2C%20clientName%2C%20serverUuid)%0A%0Anote%20over%20proxy%3A%20Search%20server%20with%20given%20uuid%0A%0Anote%20over%20proxy%3A%20Server%3A%20Assign%20address%20for%20this%20client%20socket%20(0x0001)%0A%0Aproxy-%3Enymea%3A%20SLIP%3A0x0000%3A%20ProxyTunnel.ClientConnected%20(address%3A%200x0001)%0A%0Aproxy%3C-nymea%3A%20SLIP%3A0x0000%3A%20ProxyTunnel.AcceptClient%20(address%3A%200x0001)%0A%0Aproxy-%3Enymea%3A%20TunnelProxyErrorNoError%0A%0Aproxy-%3Eclient%3A%20ProxyTunnel.ConfirmClient(uuid%2C%20name%2C%20serverUuid)%0A%0Anote%20over%20client%3A%20Connected%5CnAny%20incomming%20and%20outgoing%20data%20will%5Cnbe%20from%20the%20connected%20nymea%20instance%5Cnuntil%20disconnected.%0A%0Anote%20over%20nymea%2C%20client%3A%20Connected%3A%20The%20client%20can%20now%20communicate%20with%20nymea%20directly.%5CnThe%20proxy%20will%20not%20interpret%20any%20data%20from%20the%20client%2C%20and%20from%20the%20server%20only%5Cnthe%20transmission%20frame%20(SLIP%20%2B%202%20bytes%20of%20address)%0A%0A%0A%0Aproxy%3C-client%3A%20%22data%22%0A%0Anymea%3C-proxy%3A%20SLIP%3A0x0001%20%22data%22%0A%0Anymea-%3Eproxy%3A%20SLIP%3A0x0001%20%22data%22%0A%0Aproxy-%3Eclient%3A%20%22data%22%0ARemote tunnel proxynymeaproxyclientTunnelProxy.RegisterServer(serverUuid, serverName)Register the server using the uuidTunnelProxyErrorNoErrorProtocol from now on SLIPThe proxy is client 0x0000SLIP encoded data: 2 Bytes address + dataTunnelProxy.RegisterClient(clientUuid, clientName, serverUuid)Search server with given uuidServer: Assign address for this client socket (0x0001)SLIP:0x0000: ProxyTunnel.ClientConnected (address: 0x0001)SLIP:0x0000: ProxyTunnel.AcceptClient (address: 0x0001)TunnelProxyErrorNoErrorProxyTunnel.ConfirmClient(uuid, name, serverUuid)ConnectedAny incomming and outgoing data willbe from the connected nymea instanceuntil disconnected.Connected: The client can now communicate with nymea directly.The proxy will not interpret any data from the client, and from the server onlythe transmission frame (SLIP + 2 bytes of address)"data"SLIP:0x0001 "data"SLIP:0x0001 "data""data" \ No newline at end of file +title%20Remote%20tunnel%20proxy%0A%0Anymea-%3Eproxy%3A%20TunnelProxy.RegisterServer(serverUuid%2C%20serverName)%0A%0Anote%20over%20proxy%3A%20Register%20the%20server%20using%20the%20uuid%0A%0Anymea%3C-proxy%3A%20TunnelProxyErrorNoError%0A%0Anote%20over%20nymea%2C%20proxy%3A%20Protocol%20from%20now%20on%20SLIP%5CnThe%20proxy%20is%20client%200x0000%0A%0Anote%20over%20nymea%2C%20proxy%3A%20SLIP%20encoded%20data%3A%202%20Bytes%20address%20%2B%20data%0A%0Aproxy%3C-client%3A%20TunnelProxy.RegisterClient(clientUuid%2C%20clientName%2C%20serverUuid)%0A%0Anote%20over%20proxy%3A%20Search%20server%20with%20given%20uuid%0A%0Anote%20over%20proxy%3A%20Server%3A%20Assign%20address%20for%20this%20client%20socket%20(0x0001)%0A%0Aproxy-%3Enymea%3A%20SLIP%3A0x0000%3A%20ProxyTunnel.ClientConnected%20(address%3A%200x0001)%0A%0A%0Aproxy-%3Eclient%3A%20TunnelProxyErrorNoError%0A%0Anote%20over%20client%3A%20Connected%5CnAny%20incomming%20and%20outgoing%20data%20will%5Cnbe%20from%20the%20connected%20nymea%20instance%5Cnuntil%20disconnected.%0A%0Anote%20over%20nymea%2C%20client%3A%20Connected%3A%20The%20client%20can%20now%20communicate%20with%20nymea%20directly.%5CnThe%20proxy%20will%20not%20interpret%20any%20data%20from%20the%20client%2C%20and%20from%20the%20server%20only%5Cnthe%20transmission%20frame%20(SLIP%20%2B%202%20bytes%20of%20address)%0A%0A%0A%0Aproxy%3C-client%3A%20%22request%20data%22%0A%0Anymea%3C-proxy%3A%20SLIP%3A0x0001%20%22request%20data%22%0A%0Anymea-%3Eproxy%3A%20SLIP%3A0x0001%20%22response%20data%22%0A%0Aproxy-%3Eclient%3A%20%22response%20data%22%0A%0Anymea-%3Eproxy%3A%20SLIP%3A0x0001%20%22notification%20data%22%0A%0Aproxy-%3Eclient%3A%20%22notification%20data%22%0ARemote tunnel proxynymeaproxyclientTunnelProxy.RegisterServer(serverUuid, serverName)Register the server using the uuidTunnelProxyErrorNoErrorProtocol from now on SLIPThe proxy is client 0x0000SLIP encoded data: 2 Bytes address + dataTunnelProxy.RegisterClient(clientUuid, clientName, serverUuid)Search server with given uuidServer: Assign address for this client socket (0x0001)SLIP:0x0000: ProxyTunnel.ClientConnected (address: 0x0001)TunnelProxyErrorNoErrorConnectedAny incomming and outgoing data willbe from the connected nymea instanceuntil disconnected.Connected: The client can now communicate with nymea directly.The proxy will not interpret any data from the client, and from the server onlythe transmission frame (SLIP + 2 bytes of address)"request data"SLIP:0x0001 "request data"SLIP:0x0001 "response data""response data"SLIP:0x0001 "notification data""notification data" \ No newline at end of file diff --git a/docs/remote-connection-basic-flow.txt b/docs/remote-connection-basic-flow.txt index 29b93a8..0b80577 100644 --- a/docs/remote-connection-basic-flow.txt +++ b/docs/remote-connection-basic-flow.txt @@ -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" diff --git a/libnymea-remoteproxy/jsonrpc/jsonhandler.cpp b/libnymea-remoteproxy/jsonrpc/jsonhandler.cpp index fc33f35..8b9dc88 100644 --- a/libnymea-remoteproxy/jsonrpc/jsonhandler.cpp +++ b/libnymea-remoteproxy/jsonrpc/jsonhandler.cpp @@ -101,7 +101,7 @@ QPair 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 ¶ms) { - 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 ¶ms 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); diff --git a/libnymea-remoteproxy/jsonrpc/tunnelproxyhandler.cpp b/libnymea-remoteproxy/jsonrpc/tunnelproxyhandler.cpp index 277397d..c5456d8 100644 --- a/libnymea-remoteproxy/jsonrpc/tunnelproxyhandler.cpp +++ b/libnymea-remoteproxy/jsonrpc/tunnelproxyhandler.cpp @@ -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 ¶ms, Transpo QVariantMap response; response.insert("tunnelProxyError", JsonTypes::tunnelProxyErrorToString(error)); + response.insert("slipEnabled", error == TunnelProxyServer::TunnelProxyErrorNoError); return createReply("RegisterServer", response); } +JsonReply *TunnelProxyHandler::DisconnectClient(const QVariantMap ¶ms, TransportClient *transportClient) +{ + qCDebug(dcJsonRpc()) << name() << "disconnect client requested" << params << transportClient; + quint16 socketAddress = static_cast(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 ¶ms, TransportClient *transportClient) { qCDebug(dcJsonRpc()) << name() << "register client" << params << transportClient; @@ -122,9 +145,10 @@ JsonReply *TunnelProxyHandler::RegisterClient(const QVariantMap ¶ms, Transpo QVariantMap response; response.insert("tunnelProxyError", JsonTypes::tunnelProxyErrorToString(error)); - return createReply("RegisterServer", response); + return createReply("RegisterClient", response); } + //JsonReply *TunnelProxyHandler::RemoveClient(const QVariantMap ¶ms, TransportClient *transportClient) //{ diff --git a/libnymea-remoteproxy/jsonrpc/tunnelproxyhandler.h b/libnymea-remoteproxy/jsonrpc/tunnelproxyhandler.h index 0265e21..4fd1e62 100644 --- a/libnymea-remoteproxy/jsonrpc/tunnelproxyhandler.h +++ b/libnymea-remoteproxy/jsonrpc/tunnelproxyhandler.h @@ -46,9 +46,9 @@ public: QString name() const override; Q_INVOKABLE JsonReply *RegisterServer(const QVariantMap ¶ms, TransportClient *transportClient); + Q_INVOKABLE JsonReply *DisconnectClient(const QVariantMap ¶ms, TransportClient *transportClient); Q_INVOKABLE JsonReply *RegisterClient(const QVariantMap ¶ms, TransportClient *transportClient); -// Q_INVOKABLE JsonReply *RemoveClient(const QVariantMap ¶ms, TransportClient *transportClient); signals: void ClientConnected(const QVariantMap ¶ms, TransportClient *transportClient); diff --git a/libnymea-remoteproxy/libnymea-remoteproxy.pro b/libnymea-remoteproxy/libnymea-remoteproxy.pro index 7ebe9f4..35d7905 100644 --- a/libnymea-remoteproxy/libnymea-remoteproxy.pro +++ b/libnymea-remoteproxy/libnymea-remoteproxy.pro @@ -1,4 +1,5 @@ include(../nymea-remoteproxy.pri) +include(../common/common.pri) TEMPLATE = lib TARGET = nymea-remoteproxy diff --git a/libnymea-remoteproxy/proxy/proxyclient.cpp b/libnymea-remoteproxy/proxy/proxyclient.cpp index 7f170a3..1189dd6 100644 --- a/libnymea-remoteproxy/proxy/proxyclient.cpp +++ b/libnymea-remoteproxy/proxy/proxyclient.cpp @@ -130,16 +130,16 @@ QList ProxyClient::processData(const QByteArray &data) QList 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; diff --git a/libnymea-remoteproxy/server/jsonrpcserver.cpp b/libnymea-remoteproxy/server/jsonrpcserver.cpp index c1b6c5e..8f92ee3 100644 --- a/libnymea-remoteproxy/server/jsonrpcserver.cpp +++ b/libnymea-remoteproxy/server/jsonrpcserver.cpp @@ -29,6 +29,8 @@ #include "jsonrpcserver.h" #include "loggingcategories.h" #include "jsonrpc/jsontypes.h" +#include "transportclient.h" +#include "slipdataprocessor.h" #include #include @@ -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 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 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:" <sendData(SlipDataProcessor::serializeData(SlipDataProcessor::buildFrame(frame))); + } else { + qCDebug(dcJsonRpcTraffic()) << "Sending notification:" << data; + transportClient->sendData(data); + } } } diff --git a/libnymea-remoteproxy/server/jsonrpcserver.h b/libnymea-remoteproxy/server/jsonrpcserver.h index ca84c1b..c294084 100644 --- a/libnymea-remoteproxy/server/jsonrpcserver.h +++ b/libnymea-remoteproxy/server/jsonrpcserver.h @@ -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); diff --git a/libnymea-remoteproxy/server/tcpsocketserver.cpp b/libnymea-remoteproxy/server/tcpsocketserver.cpp index aef4c95..762c069 100644 --- a/libnymea-remoteproxy/server/tcpsocketserver.cpp +++ b/libnymea-remoteproxy/server/tcpsocketserver.cpp @@ -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(); } diff --git a/libnymea-remoteproxy/server/transportclient.cpp b/libnymea-remoteproxy/server/transportclient.cpp index 0e108ac..58ca3b3 100644 --- a/libnymea-remoteproxy/server/transportclient.cpp +++ b/libnymea-remoteproxy/server/transportclient.cpp @@ -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() diff --git a/libnymea-remoteproxy/server/transportclient.h b/libnymea-remoteproxy/server/transportclient.h index a43e746..caddee0 100644 --- a/libnymea-remoteproxy/server/transportclient.h +++ b/libnymea-remoteproxy/server/transportclient.h @@ -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; diff --git a/libnymea-remoteproxy/tunnelproxy/tunnelproxyclient.cpp b/libnymea-remoteproxy/tunnelproxy/tunnelproxyclient.cpp index ce46ba4..b6f7519 100644 --- a/libnymea-remoteproxy/tunnelproxy/tunnelproxyclient.cpp +++ b/libnymea-remoteproxy/tunnelproxy/tunnelproxyclient.cpp @@ -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 TunnelProxyClient::processData(const QByteArray &data) { // TODO: unescape if this data is for the json handler QList 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(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(); } } diff --git a/libnymea-remoteproxy/tunnelproxy/tunnelproxyclient.h b/libnymea-remoteproxy/tunnelproxy/tunnelproxyclient.h index ede50de..832be0a 100644 --- a/libnymea-remoteproxy/tunnelproxy/tunnelproxyclient.h +++ b/libnymea-remoteproxy/tunnelproxy/tunnelproxyclient.h @@ -23,9 +23,6 @@ public: Type type() const; void setType(Type type); - bool slipEnabled() const; - void setSlipEnabled(bool slipEnabled); - // Json server methods QList processData(const QByteArray &data) override; @@ -34,7 +31,6 @@ signals: private: Type m_type = TypeNone; - bool m_slipEnabled = false; }; diff --git a/libnymea-remoteproxy/tunnelproxy/tunnelproxyclientconnection.cpp b/libnymea-remoteproxy/tunnelproxy/tunnelproxyclientconnection.cpp index 3279be1..667f265 100644 --- a/libnymea-remoteproxy/tunnelproxy/tunnelproxyclientconnection.cpp +++ b/libnymea-remoteproxy/tunnelproxy/tunnelproxyclientconnection.cpp @@ -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("; diff --git a/libnymea-remoteproxy/tunnelproxy/tunnelproxyclientconnection.h b/libnymea-remoteproxy/tunnelproxy/tunnelproxyclientconnection.h index b52b121..fbbb986 100644 --- a/libnymea-remoteproxy/tunnelproxy/tunnelproxyclientconnection.h +++ b/libnymea-remoteproxy/tunnelproxy/tunnelproxyclientconnection.h @@ -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); diff --git a/libnymea-remoteproxy/tunnelproxy/tunnelproxyserver.cpp b/libnymea-remoteproxy/tunnelproxy/tunnelproxyserver.cpp index 0924ae9..e56f2af 100644 --- a/libnymea-remoteproxy/tunnelproxy/tunnelproxyserver.cpp +++ b/libnymea-remoteproxy/tunnelproxy/tunnelproxyserver.cpp @@ -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 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); } - } } diff --git a/libnymea-remoteproxy/tunnelproxy/tunnelproxyserver.h b/libnymea-remoteproxy/tunnelproxy/tunnelproxyserver.h index 585d229..937b370 100644 --- a/libnymea-remoteproxy/tunnelproxy/tunnelproxyserver.h +++ b/libnymea-remoteproxy/tunnelproxy/tunnelproxyserver.h @@ -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(); diff --git a/libnymea-remoteproxy/tunnelproxy/tunnelproxyserverconnection.cpp b/libnymea-remoteproxy/tunnelproxy/tunnelproxyserverconnection.cpp index 4451977..83b4dd8 100644 --- a/libnymea-remoteproxy/tunnelproxy/tunnelproxyserverconnection.cpp +++ b/libnymea-remoteproxy/tunnelproxy/tunnelproxyserverconnection.cpp @@ -55,14 +55,44 @@ QString TunnelProxyServerConnection::serverName() const return m_serverName; } +QList 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) diff --git a/libnymea-remoteproxy/tunnelproxy/tunnelproxyserverconnection.h b/libnymea-remoteproxy/tunnelproxy/tunnelproxyserverconnection.h index 3fdaf71..471eb5a 100644 --- a/libnymea-remoteproxy/tunnelproxy/tunnelproxyserverconnection.h +++ b/libnymea-remoteproxy/tunnelproxy/tunnelproxyserverconnection.h @@ -48,17 +48,27 @@ public: QUuid serverUuid() const; QString serverName() const; + QList 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 m_clientConnections; + QHash m_clientConnectionsAddresses; + + quint16 getFreeAddress(); }; diff --git a/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pro b/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pro index 0541c0f..3c500d6 100644 --- a/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pro +++ b/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pro @@ -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 diff --git a/libnymea-remoteproxyclient/proxyjsonrpcclient.cpp b/libnymea-remoteproxyclient/proxyjsonrpcclient.cpp index c2e04db..be9f7da 100644 --- a/libnymea-remoteproxyclient/proxyjsonrpcclient.cpp +++ b/libnymea-remoteproxyclient/proxyjsonrpcclient.cpp @@ -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(notificationParams.value("socketAddress").toUInt()); + emit tunnelProxyClientConnected(clientName, clientUuid, clientPeerAddress, socketAddress); + } else if (nameSpace == "TunnelProxy" && notificationName == "ClientDisconnected") { + quint16 socketAddress = static_cast(notificationParams.value("socketAddress").toUInt()); + emit tunnelProxyClientDisonnected(socketAddress); } } } diff --git a/libnymea-remoteproxyclient/proxyjsonrpcclient.h b/libnymea-remoteproxyclient/proxyjsonrpcclient.h index b3cb0bf..fb44b36 100644 --- a/libnymea-remoteproxyclient/proxyjsonrpcclient.h +++ b/libnymea-remoteproxyclient/proxyjsonrpcclient.h @@ -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); diff --git a/libnymea-remoteproxyclient/tunnelproxy/tunnelproxyremoteconnection.cpp b/libnymea-remoteproxyclient/tunnelproxy/tunnelproxyremoteconnection.cpp index 7a2e44b..cb0b0dd 100644 --- a/libnymea-remoteproxyclient/tunnelproxy/tunnelproxyremoteconnection.cpp +++ b/libnymea-remoteproxyclient/tunnelproxy/tunnelproxyremoteconnection.cpp @@ -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 . +* +* 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 &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(new WebSocketConnection(this)); + break; + case ConnectionTypeTcpSocket: + qCDebug(dcTunnelProxyRemoteConnection()) << "Creating a TCP socket connection to" << url.toString(); + m_connection = qobject_cast(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(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(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); +} + +} diff --git a/libnymea-remoteproxyclient/tunnelproxy/tunnelproxyremoteconnection.h b/libnymea-remoteproxyclient/tunnelproxy/tunnelproxyremoteconnection.h index c649e74..b4cd1c4 100644 --- a/libnymea-remoteproxyclient/tunnelproxy/tunnelproxyremoteconnection.h +++ b/libnymea-remoteproxyclient/tunnelproxy/tunnelproxyremoteconnection.h @@ -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 . +* +* 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 #include +#include + +#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 &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 &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 diff --git a/libnymea-remoteproxyclient/tunnelproxy/tunnelproxysocket.cpp b/libnymea-remoteproxyclient/tunnelproxy/tunnelproxysocket.cpp index b033688..169d059 100644 --- a/libnymea-remoteproxyclient/tunnelproxy/tunnelproxysocket.cpp +++ b/libnymea-remoteproxyclient/tunnelproxy/tunnelproxysocket.cpp @@ -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(); +} + } diff --git a/libnymea-remoteproxyclient/tunnelproxy/tunnelproxysocket.h b/libnymea-remoteproxyclient/tunnelproxy/tunnelproxysocket.h index bd259f9..bb3b6a7 100644 --- a/libnymea-remoteproxyclient/tunnelproxy/tunnelproxysocket.h +++ b/libnymea-remoteproxyclient/tunnelproxy/tunnelproxysocket.h @@ -28,20 +28,48 @@ #ifndef TUNNELPROXYSOCKET_H #define TUNNELPROXYSOCKET_H +#include #include +#include 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 diff --git a/libnymea-remoteproxyclient/tunnelproxy/tunnelproxysocketserver.cpp b/libnymea-remoteproxyclient/tunnelproxy/tunnelproxysocketserver.cpp index 67576c2..616eea6 100644 --- a/libnymea-remoteproxyclient/tunnelproxy/tunnelproxysocketserver.cpp +++ b/libnymea-remoteproxyclient/tunnelproxy/tunnelproxysocketserver.cpp @@ -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 &errors) @@ -78,6 +84,32 @@ void TunnelProxySocketServer::ignoreSslErrors(const QList &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(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); } diff --git a/libnymea-remoteproxyclient/tunnelproxy/tunnelproxysocketserver.h b/libnymea-remoteproxyclient/tunnelproxy/tunnelproxysocketserver.h index 15b4926..7a20bf2 100644 --- a/libnymea-remoteproxyclient/tunnelproxy/tunnelproxysocketserver.h +++ b/libnymea-remoteproxyclient/tunnelproxy/tunnelproxysocketserver.h @@ -32,14 +32,14 @@ #include #include -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 &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 &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 m_tunnelProxySockets; + + QByteArray m_dataBuffer; + void setState(State state); void setRunning(bool running); void setError(QAbstractSocket::SocketError error); + void setServerError(Error error); void cleanUp(); }; diff --git a/nymea-remoteproxy.pro b/nymea-remoteproxy.pro index 2e38692..bdd60cf 100644 --- a/nymea-remoteproxy.pro +++ b/nymea-remoteproxy.pro @@ -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") } diff --git a/tests/test-tunnelproxy/remoteproxyteststunnelproxy.cpp b/tests/test-tunnelproxy/remoteproxyteststunnelproxy.cpp index f8217bf..0ff10c6 100644 --- a/tests/test-tunnelproxy/remoteproxyteststunnelproxy.cpp +++ b/tests/test-tunnelproxy/remoteproxyteststunnelproxy.cpp @@ -33,6 +33,7 @@ // Client #include "tunnelproxy/tunnelproxysocketserver.h" +#include "tunnelproxy/tunnelproxyremoteconnection.h" #include #include @@ -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 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 result = invokeTcpSocketTunnelProxyApiCallPersistant("TunnelProxy.RegisterClient", clientParams, serverSocket); + QPair 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 &errors){ + tunnelProxyServer->ignoreSslErrors(errors); + }); + + tunnelProxyServer->startServer(m_serverUrlTunnelProxyTcp); + + QSignalSpy serverRunningSpy(tunnelProxyServer, &TunnelProxySocketServer::runningChanged); + serverRunningSpy.wait(); + QVERIFY(serverRunningSpy.count() == 1); + QList 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 &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(); + 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 &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 diff --git a/tests/test-tunnelproxy/remoteproxyteststunnelproxy.h b/tests/test-tunnelproxy/remoteproxyteststunnelproxy.h index c4d40cb..8c0ca6b 100644 --- a/tests/test-tunnelproxy/remoteproxyteststunnelproxy.h +++ b/tests/test-tunnelproxy/remoteproxyteststunnelproxy.h @@ -66,6 +66,7 @@ private slots: // Client classes void testTunnelProxyServer(); + void testTunnelProxyClient(); }; diff --git a/tests/testbase/basetest.cpp b/tests/testbase/basetest.cpp index cd55fef..e625ef7 100644 --- a/tests/testbase/basetest.cpp +++ b/tests/testbase/basetest.cpp @@ -31,6 +31,8 @@ #include "loggingcategories.h" #include "remoteproxyconnection.h" +#include "../common/slipdataprocessor.h" + #include #include #include @@ -656,7 +658,7 @@ QVariant BaseTest::injectTcpSocketTunnelProxyData(const QByteArray &data) return jsonDoc.toVariant(); } -QPair BaseTest::invokeTcpSocketTunnelProxyApiCallPersistant(const QString &method, const QVariantMap params, QSslSocket *existingSocket) +QPair BaseTest::invokeTcpSocketTunnelProxyApiCallPersistant(const QString &method, const QVariantMap params, bool slipEnabled, QSslSocket *existingSocket) { QSslSocket *socket = nullptr; if (!existingSocket) { @@ -680,30 +682,66 @@ QPair 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(), 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(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(), 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(), 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(), 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(), socket); } @@ -718,7 +756,7 @@ QPair BaseTest::invokeTcpSocketTunnelProxyApiCallPersist return QPair(QVariant(), socket); } -QPair BaseTest::invokeWebSocketTunnelProxyApiCallPersistant(const QString &method, const QVariantMap params, QWebSocket *existingSocket) +QPair BaseTest::invokeWebSocketTunnelProxyApiCallPersistant(const QString &method, const QVariantMap params, bool slipEnabled, QWebSocket *existingSocket) { QWebSocket *socket = nullptr; if (!existingSocket) { @@ -740,16 +778,55 @@ QPair 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(), 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(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(), 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(), socket); + } } // Make sure the response it a valid JSON string @@ -771,6 +848,7 @@ QPair BaseTest::invokeWebSocketTunnelProxyApiCallPersist } } + qWarning() << "No websocket data received..."; m_commandCounter++; return QPair(QVariant(), socket); } diff --git a/tests/testbase/basetest.h b/tests/testbase/basetest.h index 5d321e2..57cebbd 100644 --- a/tests/testbase/basetest.h +++ b/tests/testbase/basetest.h @@ -97,8 +97,8 @@ protected: QVariant invokeTcpSocketTunnelProxyApiCall(const QString &method, const QVariantMap params = QVariantMap()); QVariant injectTcpSocketTunnelProxyData(const QByteArray &data); - QPair invokeTcpSocketTunnelProxyApiCallPersistant(const QString &method, const QVariantMap params = QVariantMap(), QSslSocket *existingSocket = nullptr); - QPair invokeWebSocketTunnelProxyApiCallPersistant(const QString &method, const QVariantMap params = QVariantMap(), QWebSocket *existingSocket = nullptr); + QPair invokeTcpSocketTunnelProxyApiCallPersistant(const QString &method, const QVariantMap params = QVariantMap(), bool slipEnabled = false, QSslSocket *existingSocket = nullptr); + QPair invokeWebSocketTunnelProxyApiCallPersistant(const QString &method, const QVariantMap params = QVariantMap(), bool slipEnabled = false, QWebSocket *existingSocket = nullptr); bool createRemoteConnection(const QString &token, const QString &nonce, QObject *parent); diff --git a/tests/testbase/testbase.pri b/tests/testbase/testbase.pri index 723b56d..f5e30df 100644 --- a/tests/testbase/testbase.pri +++ b/tests/testbase/testbase.pri @@ -1,5 +1,7 @@ RESOURCES += ../resources/resources.qrc +include(../common/common.pri) + CONFIG += testcase QT += testlib