nymea-remoteproxy/libnymea-remoteproxy/server/websocketserver.cpp

215 lines
7.6 KiB
C++

// 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 <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "websocketserver.h"
#include "loggingcategories.h"
#include <QCoreApplication>
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<QWebSocket *>(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<QWebSocket *>(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<QWebSocket *>(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<QWebSocket *>(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<quint16>(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;
}
}