// SPDX-License-Identifier: LGPL-3.0-or-later /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * nymea-remoteproxy * Tunnel proxy server for the nymea remote access * * Copyright (C) 2013 - 2024, nymea GmbH * Copyright (C) 2024 - 2025, chargebyte austria GmbH * * This file is part of nymea-remoteproxy. * * nymea-remoteproxy is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * nymea-remoteproxy is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with nymea-remoteproxy. If not, see . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "websocketserver.h" #include "loggingcategories.h" #include namespace remoteproxy { WebSocketServer::WebSocketServer(bool sslEnabled, const QSslConfiguration &sslConfiguration, QObject *parent) : TransportInterface(parent), m_sslEnabled(sslEnabled), m_sslConfiguration(sslConfiguration) { m_serverName = "WebSocket"; } WebSocketServer::~WebSocketServer() { stopServer(); } bool WebSocketServer::running() const { if (!m_server) return false; return m_server->isListening(); } QSslConfiguration WebSocketServer::sslConfiguration() const { return m_sslConfiguration; } void WebSocketServer::sendData(const QUuid &clientId, const QByteArray &data) { QWebSocket *client = nullptr; client = m_clientList.value(clientId); if (client) { qCDebug(dcWebSocketServerTraffic()) << "--> Sending data to client:" << data; client->sendTextMessage(data); } else { qCWarning(dcWebSocketServer()) << "Client" << clientId << "unknown to this transport"; } } void WebSocketServer::killClientConnection(const QUuid &clientId, const QString &killReason) { QWebSocket *client = m_clientList.value(clientId); if (!client) return; qCWarning(dcWebSocketServer()) << "Killing client connection" << clientId.toString() << "Reason:" << killReason; client->flush(); client->close(QWebSocketProtocol::CloseCodeBadOperation, killReason); } uint WebSocketServer::connectionsCount() const { return m_clientList.count(); } void WebSocketServer::onClientConnected() { // Got a new client connected QWebSocket *client = m_server->nextPendingConnection(); if (!client) { qCWarning(dcWebSocketServer()) << "Next pending connection disappeared. Doing nothing."; return; } // Check websocket version if (client->version() != QWebSocketProtocol::Version13) { qCWarning(dcWebSocketServer()) << "Client with invalid protocol version" << client->version() << ". Rejecting."; client->close(QWebSocketProtocol::CloseCodeProtocolError, QString("invalid protocol version: %1 != Supported Version 13").arg(client->version())); client->deleteLater(); return; } // Create new uuid for this connection QUuid clientId = QUuid::createUuid(); qCDebug(dcWebSocketServer()) << "New client connected:" << client << client->peerAddress().toString() << clientId.toString(); // Append the new client to the client list m_clientList.insert(clientId, client); connect(client, SIGNAL(binaryMessageReceived(QByteArray)), this, SLOT(onBinaryMessageReceived(QByteArray))); connect(client, SIGNAL(textMessageReceived(QString)), this, SLOT(onTextMessageReceived(QString))); connect(client, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onClientError(QAbstractSocket::SocketError))); connect(client, SIGNAL(disconnected()), this, SLOT(onClientDisconnected())); emit clientConnected(clientId, client->peerAddress()); } void WebSocketServer::onClientDisconnected() { QWebSocket *client = static_cast(sender()); QUuid clientId = m_clientList.key(client); qCDebug(dcWebSocketServer()) << "Client disconnected:" << client << client->peerAddress().toString() << clientId.toString() << client->closeReason(); // Manually close it in any case client->close(); m_clientList.take(clientId)->deleteLater(); emit clientDisconnected(clientId); } void WebSocketServer::onTextMessageReceived(const QString &message) { QWebSocket *client = static_cast(sender()); qCDebug(dcWebSocketServerTraffic()) << "Text message from" << client->peerAddress().toString() << ":" << message; emit dataAvailable(m_clientList.key(client), message.toUtf8()); } void WebSocketServer::onBinaryMessageReceived(const QByteArray &data) { QWebSocket *client = static_cast(sender()); qCWarning(dcWebSocketServerTraffic()) << "<-- Binary message from" << client->peerAddress().toString() << ":" << data; // Note: this is not expected, so close this client connection. client->close(QWebSocketProtocol::CloseCodeBadOperation, "Binary message not expected."); } void WebSocketServer::onClientError(QAbstractSocket::SocketError error) { QWebSocket *client = static_cast(sender()); qCWarning(dcWebSocketServer()) << "Client error occurred:" << client << client->peerAddress().toString() << error << client->errorString() << "Closing the socket."; // Note: on any error which can occurre, make sure the socket will be closed in any case client->close(); } void WebSocketServer::onAcceptError(QAbstractSocket::SocketError error) { qCWarning(dcWebSocketServer()) << "Server accept error occurred:" << error << m_server->errorString(); } void WebSocketServer::onServerError(QWebSocketProtocol::CloseCode closeCode) { qCWarning(dcWebSocketServer()) << "Server error occurred:" << closeCode << m_server->errorString(); } bool WebSocketServer::startServer() { if (m_sslEnabled) { m_server = new QWebSocketServer(QCoreApplication::applicationName(), QWebSocketServer::SecureMode, this); m_server->setSslConfiguration(sslConfiguration()); } else { m_server = new QWebSocketServer(QCoreApplication::applicationName(), QWebSocketServer::NonSecureMode, this); } connect (m_server, &QWebSocketServer::newConnection, this, &WebSocketServer::onClientConnected); connect (m_server, &QWebSocketServer::acceptError, this, &WebSocketServer::onAcceptError); connect (m_server, &QWebSocketServer::serverError, this, &WebSocketServer::onServerError); qCDebug(dcWebSocketServer()) << "Starting server" << m_server->serverName() << serverUrl().toString(); if (!m_server->listen(QHostAddress(m_serverUrl.host()), static_cast(serverUrl().port()))) { qCWarning(dcWebSocketServer()) << "Server" << m_server->serverName() << "could not listen on" << serverUrl().toString(); delete m_server; m_server = nullptr; return false; } qCDebug(dcWebSocketServer()) << "Server started successfully."; return true; } bool WebSocketServer::stopServer() { // Clean up client connections foreach (QWebSocket *client, m_clientList.values()) { client->close(QWebSocketProtocol::CloseCodeNormal, "Stop server"); client->flush(); client->abort(); } // Delete the server object if (!m_server) return true; qCDebug(dcWebSocketServer()) << "Stopping server" << m_server->serverName() << serverUrl().toString(); m_server->close(); delete m_server; m_server = nullptr; return true; } }