mirror of
				https://github.com/hyperion-project/hyperion.ng.git
				synced 2025-03-01 10:33:28 +00:00 
			
		
		
		
	Refactor WebSocket
This commit is contained in:
		@@ -1,3 +1,4 @@
 | 
			
		||||
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS WebSockets REQUIRED)
 | 
			
		||||
 | 
			
		||||
file(GLOB_RECURSE webFiles RELATIVE ${CMAKE_BINARY_DIR}  ${CMAKE_SOURCE_DIR}/assets/webconfig/*)
 | 
			
		||||
file(RELATIVE_PATH webConfigPath ${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/assets/webconfig)
 | 
			
		||||
@@ -28,13 +29,13 @@ add_library(webserver
 | 
			
		||||
	${CMAKE_SOURCE_DIR}/libsrc/webserver/StaticFileServing.cpp
 | 
			
		||||
	${CMAKE_SOURCE_DIR}/libsrc/webserver/WebJsonRpc.h
 | 
			
		||||
	${CMAKE_SOURCE_DIR}/libsrc/webserver/WebJsonRpc.cpp
 | 
			
		||||
	${CMAKE_SOURCE_DIR}/libsrc/webserver/WebSocketClient.h
 | 
			
		||||
	${CMAKE_SOURCE_DIR}/libsrc/webserver/WebSocketClient.cpp
 | 
			
		||||
	${CMAKE_SOURCE_DIR}/libsrc/webserver/WebSocketUtils.h
 | 
			
		||||
	${CMAKE_SOURCE_DIR}/libsrc/webserver/WebSocketJsonHandler.h
 | 
			
		||||
	${CMAKE_SOURCE_DIR}/libsrc/webserver/WebSocketJsonHandler.cpp
 | 
			
		||||
	${CMAKE_BINARY_DIR}/WebConfig.qrc
 | 
			
		||||
)
 | 
			
		||||
 )
 | 
			
		||||
 | 
			
		||||
target_link_libraries(webserver
 | 
			
		||||
	Qt${QT_VERSION_MAJOR}::WebSockets
 | 
			
		||||
	hyperion
 | 
			
		||||
	hyperion-utils
 | 
			
		||||
	hyperion-api
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
#include "QtHttpReply.h"
 | 
			
		||||
#include "QtHttpServer.h"
 | 
			
		||||
#include "QtHttpHeader.h"
 | 
			
		||||
#include "WebSocketClient.h"
 | 
			
		||||
#include "WebSocketJsonHandler.h"
 | 
			
		||||
#include "WebJsonRpc.h"
 | 
			
		||||
 | 
			
		||||
#include <QCryptographicHash>
 | 
			
		||||
@@ -29,6 +29,8 @@ QtHttpClientWrapper::QtHttpClientWrapper (QTcpSocket * sock, const bool& localCo
 | 
			
		||||
	, m_webJsonRpc     (nullptr)
 | 
			
		||||
{
 | 
			
		||||
	connect (m_sockClient, &QTcpSocket::readyRead, this, &QtHttpClientWrapper::onClientDataReceived);
 | 
			
		||||
	connect(&m_websocketServer, &QWebSocketServer::newConnection,
 | 
			
		||||
		this, &QtHttpClientWrapper::onNewWebSocketConnection);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QString QtHttpClientWrapper::getGuid (void)
 | 
			
		||||
@@ -50,6 +52,11 @@ void QtHttpClientWrapper::onClientDataReceived (void)
 | 
			
		||||
{
 | 
			
		||||
	if (m_sockClient != Q_NULLPTR)
 | 
			
		||||
	{
 | 
			
		||||
		if (!m_sockClient->isTransactionStarted())
 | 
			
		||||
		{
 | 
			
		||||
			m_sockClient->startTransaction();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		while (m_sockClient->bytesAvailable () != 0)
 | 
			
		||||
		{
 | 
			
		||||
			QByteArray line = m_sockClient->readLine ();
 | 
			
		||||
@@ -162,22 +169,25 @@ void QtHttpClientWrapper::onClientDataReceived (void)
 | 
			
		||||
			{
 | 
			
		||||
			case RequestParsed: // a valid request has ben fully parsed
 | 
			
		||||
			{
 | 
			
		||||
				// Catch websocket header "Upgrade"
 | 
			
		||||
				if(m_currentRequest->getHeader(QtHttpHeader::Upgrade).toLower() == "websocket")
 | 
			
		||||
				{
 | 
			
		||||
				const auto& upgradeValue = m_currentRequest->getHeader(QtHttpHeader::Upgrade).toLower();
 | 
			
		||||
				if (upgradeValue.compare(QByteArrayLiteral("websocket"), Qt::CaseInsensitive) == 0) {
 | 
			
		||||
 | 
			
		||||
					qDebug() << "WebSocket upgrade detected, passing to QWebSocketServer";
 | 
			
		||||
 | 
			
		||||
					if(m_websocketClient == Q_NULLPTR)
 | 
			
		||||
					{
 | 
			
		||||
						// disconnect this slot from socket for further requests
 | 
			
		||||
						disconnect(m_sockClient, &QTcpSocket::readyRead, this, &QtHttpClientWrapper::onClientDataReceived);
 | 
			
		||||
						// disabling packet bunching
 | 
			
		||||
						m_sockClient->setSocketOption(QAbstractSocket::LowDelayOption, 1);
 | 
			
		||||
						m_sockClient->setSocketOption(QAbstractSocket::KeepAliveOption, 1);
 | 
			
		||||
						m_websocketClient = new WebSocketClient(m_currentRequest, m_sockClient, m_localConnection, this);
 | 
			
		||||
						m_sockClient->rollbackTransaction();
 | 
			
		||||
						m_websocketServer.handleConnection(m_sockClient);
 | 
			
		||||
						emit m_sockClient->readyRead();
 | 
			
		||||
						return;
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				m_sockClient->commitTransaction();
 | 
			
		||||
				// add  post data to request and catch /jsonrpc subroute url
 | 
			
		||||
				if ( m_currentRequest->getCommand() == "POST")
 | 
			
		||||
				{
 | 
			
		||||
@@ -227,6 +237,8 @@ void QtHttpClientWrapper::onClientDataReceived (void)
 | 
			
		||||
			case ParsingError: // there was an error durin one of parsing steps
 | 
			
		||||
			{
 | 
			
		||||
				m_sockClient->readAll (); // clear remaining buffer to ignore content
 | 
			
		||||
				m_sockClient->commitTransaction();
 | 
			
		||||
 | 
			
		||||
				QtHttpReply reply (m_serverHandle);
 | 
			
		||||
				reply.setStatusCode (QtHttpReply::BadRequest);
 | 
			
		||||
				reply.appendRawData (QByteArrayLiteral ("<h1>Bad Request (HTTP parsing error) !</h1>"));
 | 
			
		||||
@@ -365,3 +377,19 @@ void QtHttpClientWrapper::closeConnection()
 | 
			
		||||
	}
 | 
			
		||||
	m_sockClient->close ();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void QtHttpClientWrapper::onNewWebSocketConnection() {
 | 
			
		||||
 | 
			
		||||
	// Handle the pending connection
 | 
			
		||||
	QWebSocket* webSocket = m_websocketServer.nextPendingConnection();
 | 
			
		||||
	if (webSocket) {
 | 
			
		||||
		qDebug() << "New WebSocket connection established";
 | 
			
		||||
 | 
			
		||||
		// Manage the WebSocketJsonHandler for this connection
 | 
			
		||||
		WebSocketJsonHandler* handler = new WebSocketJsonHandler(webSocket);
 | 
			
		||||
		connect(webSocket, &QWebSocket::disconnected, handler, &QObject::deleteLater);
 | 
			
		||||
	}
 | 
			
		||||
	else {
 | 
			
		||||
		qWarning() << "No pending WebSocket connection!";
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,8 @@
 | 
			
		||||
 | 
			
		||||
#include <QObject>
 | 
			
		||||
#include <QString>
 | 
			
		||||
#include <QWebSocketServer>
 | 
			
		||||
#include <QCoreApplication>
 | 
			
		||||
 | 
			
		||||
class QTcpSocket;
 | 
			
		||||
 | 
			
		||||
@@ -39,8 +41,17 @@ public:
 | 
			
		||||
	///
 | 
			
		||||
	void closeConnection();
 | 
			
		||||
 | 
			
		||||
	QWebSocketServer m_websocketServer{
 | 
			
		||||
	QCoreApplication::applicationName() + QLatin1Char('/') + QCoreApplication::applicationVersion(),
 | 
			
		||||
	QWebSocketServer::NonSecureMode
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
	void newWebSocketConnection();
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
	void onClientDataReceived (void);
 | 
			
		||||
	void onNewWebSocketConnection();
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
	ParsingStatus sendReplyToClient (QtHttpReply * reply);
 | 
			
		||||
 
 | 
			
		||||
@@ -40,7 +40,7 @@ QtHttpServer::QtHttpServer (QObject * parent)
 | 
			
		||||
	, m_netOrigin  (NetOrigin::getInstance())
 | 
			
		||||
{
 | 
			
		||||
	m_sockServer = new QtHttpServerWrapper (this);
 | 
			
		||||
	connect (m_sockServer, &QtHttpServerWrapper::newConnection, this, &QtHttpServer::onClientConnected);
 | 
			
		||||
	connect (m_sockServer, &QtHttpServerWrapper::newConnection, this, &QtHttpServer::onClientConnected, Qt::UniqueConnection);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void QtHttpServer::start (quint16 port)
 | 
			
		||||
@@ -81,27 +81,27 @@ void QtHttpServer::onClientConnected (void)
 | 
			
		||||
	{
 | 
			
		||||
		if (QTcpSocket * sock = m_sockServer->nextPendingConnection ())
 | 
			
		||||
		{
 | 
			
		||||
			if(m_netOrigin->accessAllowed(sock->peerAddress(), sock->localAddress()))
 | 
			
		||||
			if (m_netOrigin->accessAllowed(sock->peerAddress(), sock->localAddress()))
 | 
			
		||||
			{
 | 
			
		||||
				connect (sock, &QTcpSocket::disconnected, this, &QtHttpServer::onClientDisconnected);
 | 
			
		||||
				connect(sock, &QTcpSocket::disconnected, this, &QtHttpServer::onClientDisconnected);
 | 
			
		||||
 | 
			
		||||
				if (m_useSsl)
 | 
			
		||||
				{
 | 
			
		||||
					if (QSslSocket * ssl = qobject_cast<QSslSocket *> (sock))
 | 
			
		||||
					if (QSslSocket* ssl = qobject_cast<QSslSocket*> (sock))
 | 
			
		||||
					{
 | 
			
		||||
						connect (ssl, SslErrorSignal (&QSslSocket::sslErrors), this, &QtHttpServer::onClientSslErrors);
 | 
			
		||||
						connect (ssl, &QSslSocket::encrypted,                  this, &QtHttpServer::onClientSslEncrypted);
 | 
			
		||||
						connect (ssl, &QSslSocket::peerVerifyError,            this, &QtHttpServer::onClientSslPeerVerifyError);
 | 
			
		||||
						connect (ssl, &QSslSocket::modeChanged,                this, &QtHttpServer::onClientSslModeChanged);
 | 
			
		||||
						ssl->setLocalCertificateChain (m_sslCerts);
 | 
			
		||||
						ssl->setPrivateKey (m_sslKey);
 | 
			
		||||
						ssl->setPeerVerifyMode (QSslSocket::AutoVerifyPeer);
 | 
			
		||||
						ssl->startServerEncryption ();
 | 
			
		||||
						connect(ssl, SslErrorSignal(&QSslSocket::sslErrors), this, &QtHttpServer::onClientSslErrors);
 | 
			
		||||
						connect(ssl, &QSslSocket::encrypted, this, &QtHttpServer::onClientSslEncrypted);
 | 
			
		||||
						connect(ssl, &QSslSocket::peerVerifyError, this, &QtHttpServer::onClientSslPeerVerifyError);
 | 
			
		||||
						connect(ssl, &QSslSocket::modeChanged, this, &QtHttpServer::onClientSslModeChanged);
 | 
			
		||||
						ssl->setLocalCertificateChain(m_sslCerts);
 | 
			
		||||
						ssl->setPrivateKey(m_sslKey);
 | 
			
		||||
						ssl->setPeerVerifyMode(QSslSocket::AutoVerifyPeer);
 | 
			
		||||
						ssl->startServerEncryption();
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				QtHttpClientWrapper * wrapper = new QtHttpClientWrapper (sock, m_netOrigin->isLocalAddress(sock->peerAddress(), sock->localAddress()), this);
 | 
			
		||||
				m_socksClientsHash.insert (sock, wrapper);
 | 
			
		||||
				QtHttpClientWrapper* wrapper = new QtHttpClientWrapper(sock, m_netOrigin->isLocalAddress(sock->peerAddress(), sock->localAddress()), this);
 | 
			
		||||
				m_socksClientsHash.insert(sock, wrapper);
 | 
			
		||||
				emit clientConnected (wrapper->getGuid ());
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
 
 | 
			
		||||
@@ -1,359 +0,0 @@
 | 
			
		||||
#include "WebSocketClient.h"
 | 
			
		||||
#include "QtHttpRequest.h"
 | 
			
		||||
#include "QtHttpHeader.h"
 | 
			
		||||
 | 
			
		||||
#include <hyperion/Hyperion.h>
 | 
			
		||||
#include <api/JsonAPI.h>
 | 
			
		||||
#include <api/JsonCallbacks.h>
 | 
			
		||||
 | 
			
		||||
#include <QTcpSocket>
 | 
			
		||||
#include <QtEndian>
 | 
			
		||||
#include <QCryptographicHash>
 | 
			
		||||
#include <QJsonObject>
 | 
			
		||||
 | 
			
		||||
WebSocketClient::WebSocketClient(QtHttpRequest* request, QTcpSocket* sock, bool localConnection, QObject* parent)
 | 
			
		||||
	: QObject(parent)
 | 
			
		||||
	, _socket(sock)
 | 
			
		||||
	, _log(Logger::getInstance("WEBSOCKET"))
 | 
			
		||||
{
 | 
			
		||||
	// connect socket; disconnect handled from QtHttpServer
 | 
			
		||||
	connect(_socket, &QTcpSocket::readyRead , this, &WebSocketClient::handleWebSocketFrame);
 | 
			
		||||
 | 
			
		||||
	// QtHttpRequest contains all headers for handshake
 | 
			
		||||
	QByteArray secWebSocketKey = request->getHeader(QtHttpHeader::SecWebSocketKey);
 | 
			
		||||
	const QString client = request->getClientInfo().clientAddress.toString();
 | 
			
		||||
 | 
			
		||||
	// Json processor
 | 
			
		||||
	_jsonAPI.reset(new JsonAPI(client, _log, localConnection, this));
 | 
			
		||||
	connect(_jsonAPI.get(), &JsonAPI::callbackReady, this, &WebSocketClient::sendMessage);
 | 
			
		||||
	connect(_jsonAPI.get(), &JsonAPI::forceClose, this,[this]() { this->sendClose(CLOSECODE::NORMAL); });
 | 
			
		||||
 | 
			
		||||
	connect(_jsonAPI->getCallBack().get(), &JsonCallbacks::callbackReady, this, &WebSocketClient::sendMessage);
 | 
			
		||||
 | 
			
		||||
	connect(this, &WebSocketClient::handleMessage, _jsonAPI.get(), &JsonAPI::handleMessage);
 | 
			
		||||
 | 
			
		||||
	Debug(_log, "New connection from %s", QSTRING_CSTR(client));
 | 
			
		||||
 | 
			
		||||
	// do handshake
 | 
			
		||||
	secWebSocketKey += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
 | 
			
		||||
	QByteArray hash = QCryptographicHash::hash(secWebSocketKey, QCryptographicHash::Sha1).toBase64();
 | 
			
		||||
 | 
			
		||||
	QString data
 | 
			
		||||
		= QString("HTTP/1.1 101 Switching Protocols\r\n")
 | 
			
		||||
		+ QString("Upgrade: websocket\r\n")
 | 
			
		||||
		+ QString("Connection: Upgrade\r\n")
 | 
			
		||||
		+ QString("Sec-WebSocket-Accept: ")+QString(hash.data()) + "\r\n\r\n";
 | 
			
		||||
 | 
			
		||||
	_socket->write(QSTRING_CSTR(data), data.size());
 | 
			
		||||
	_socket->flush();
 | 
			
		||||
 | 
			
		||||
	// Init JsonAPI
 | 
			
		||||
	_jsonAPI->initialize();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WebSocketClient::handleWebSocketFrame()
 | 
			
		||||
{
 | 
			
		||||
	while (_socket->bytesAvailable())
 | 
			
		||||
	{
 | 
			
		||||
		// we are on no continious reading from socket from call before
 | 
			
		||||
		if (!_notEnoughData)
 | 
			
		||||
		{
 | 
			
		||||
			getWsFrameHeader(&_wsh);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if(_socket->bytesAvailable() < (qint64)_wsh.payloadLength)
 | 
			
		||||
		{
 | 
			
		||||
			_notEnoughData=true;
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		_notEnoughData = false;
 | 
			
		||||
 | 
			
		||||
		QByteArray buf = _socket->read(_wsh.payloadLength);
 | 
			
		||||
 | 
			
		||||
		if (OPCODE::invalid((OPCODE::value)_wsh.opCode))
 | 
			
		||||
		{
 | 
			
		||||
			sendClose(CLOSECODE::INV_TYPE, "invalid opcode");
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// check the type of data frame
 | 
			
		||||
		bool isContinuation=false;
 | 
			
		||||
 | 
			
		||||
		switch (_wsh.opCode)
 | 
			
		||||
		{
 | 
			
		||||
			case OPCODE::CONTINUATION:
 | 
			
		||||
				isContinuation = true;
 | 
			
		||||
				// no break here, just jump over to opcode text
 | 
			
		||||
 | 
			
		||||
			case OPCODE::BINARY:
 | 
			
		||||
			case OPCODE::TEXT:
 | 
			
		||||
			{
 | 
			
		||||
				// A fragmented message consists of a single frame with the FIN bit
 | 
			
		||||
				// clear and an opcode other than 0, followed by zero or more frames
 | 
			
		||||
				// with the FIN bit clear and the opcode set to 0, and terminated by
 | 
			
		||||
				// a single frame with the FIN bit set and an opcode of 0.
 | 
			
		||||
				//
 | 
			
		||||
				// Store frame type given by first frame
 | 
			
		||||
				if (_wsh.opCode != OPCODE::CONTINUATION )
 | 
			
		||||
				{
 | 
			
		||||
					_frameOpCode = _wsh.opCode;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// check for protocol violations
 | 
			
		||||
				if (_onContinuation && !isContinuation)
 | 
			
		||||
				{
 | 
			
		||||
					sendClose(CLOSECODE::VIOLATION, "protocol violation, somebody sends frames in between continued frames");
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (!_wsh.masked && _wsh.opCode == OPCODE::TEXT)
 | 
			
		||||
				{
 | 
			
		||||
					sendClose(CLOSECODE::VIOLATION, "protocol violation, unmasked text frames not allowed");
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// unmask data
 | 
			
		||||
				for (int i=0; i < buf.size(); i++)
 | 
			
		||||
				{
 | 
			
		||||
					buf[i] = buf[i] ^ _wsh.key[i % 4];
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				_onContinuation = !_wsh.fin || isContinuation;
 | 
			
		||||
 | 
			
		||||
				// frame contains text, extract it, append data if this is a continuation
 | 
			
		||||
				if (_wsh.fin && ! isContinuation) // one frame
 | 
			
		||||
				{
 | 
			
		||||
					_wsReceiveBuffer.clear();
 | 
			
		||||
				}
 | 
			
		||||
				_wsReceiveBuffer.append(buf);
 | 
			
		||||
 | 
			
		||||
				// this is the final frame, decode and handle data
 | 
			
		||||
				if (_wsh.fin)
 | 
			
		||||
				{
 | 
			
		||||
					_onContinuation = false;
 | 
			
		||||
 | 
			
		||||
					if (_frameOpCode == OPCODE::TEXT)
 | 
			
		||||
					{
 | 
			
		||||
						emit handleMessage(QString(_wsReceiveBuffer),"");
 | 
			
		||||
					}
 | 
			
		||||
					else
 | 
			
		||||
					{
 | 
			
		||||
						handleBinaryMessage(_wsReceiveBuffer);
 | 
			
		||||
					}
 | 
			
		||||
					_wsReceiveBuffer.clear();
 | 
			
		||||
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
			case OPCODE::CLOSE:
 | 
			
		||||
				{
 | 
			
		||||
					sendClose(CLOSECODE::NORMAL);
 | 
			
		||||
				}
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
			case OPCODE::PING:
 | 
			
		||||
				{
 | 
			
		||||
					// ping received, send pong
 | 
			
		||||
					quint8 pong[] = {OPCODE::PONG, 0};
 | 
			
		||||
					_socket->write((const char*)pong, 2);
 | 
			
		||||
					_socket->flush();
 | 
			
		||||
				}
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
			case OPCODE::PONG:
 | 
			
		||||
				{
 | 
			
		||||
					Error(_log, "pong received, protocol violation!");
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
			default:
 | 
			
		||||
				Warning(_log, "strange %d\n%s\n",  _wsh.opCode, QSTRING_CSTR(QString(buf)));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WebSocketClient::getWsFrameHeader(WebSocketHeader* header)
 | 
			
		||||
{
 | 
			
		||||
	char fin_rsv_opcode, mask_length;
 | 
			
		||||
	_socket->getChar(&fin_rsv_opcode);
 | 
			
		||||
	_socket->getChar(&mask_length);
 | 
			
		||||
 | 
			
		||||
	header->fin    = (fin_rsv_opcode & BHB0_FIN) == BHB0_FIN;
 | 
			
		||||
	header->opCode = fin_rsv_opcode  & BHB0_OPCODE;
 | 
			
		||||
	header->masked = (mask_length & BHB1_MASK) == BHB1_MASK;
 | 
			
		||||
	header->payloadLength = mask_length  & BHB1_PAYLOAD;
 | 
			
		||||
 | 
			
		||||
	// get size of payload
 | 
			
		||||
	switch (header->payloadLength)
 | 
			
		||||
	{
 | 
			
		||||
		case payload_size_code_16bit:
 | 
			
		||||
		{
 | 
			
		||||
			QByteArray buf = _socket->read(2);
 | 
			
		||||
			header->payloadLength = ((buf.at(0) << 8) & 0xFF00) | (buf.at(1) & 0xFF);
 | 
			
		||||
		}
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
		case payload_size_code_64bit:
 | 
			
		||||
		{
 | 
			
		||||
			QByteArray buf = _socket->read(8);
 | 
			
		||||
			header->payloadLength = 0;
 | 
			
		||||
			for (uint i=0; i < 8; i++)
 | 
			
		||||
			{
 | 
			
		||||
				header->payloadLength |= ((quint64)(buf.at(i) & 0xFF)) << (8*(7-i));
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// if the data is masked we need to get the key for unmasking
 | 
			
		||||
	if (header->masked)
 | 
			
		||||
	{
 | 
			
		||||
		_socket->read(header->key, 4);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// See http://tools.ietf.org/html/rfc6455#section-5.2 for more information
 | 
			
		||||
void WebSocketClient::sendClose(int status, const QString& reason)
 | 
			
		||||
{
 | 
			
		||||
	Debug(_log, "Send close to %s: %d %s", QSTRING_CSTR(_socket->peerAddress().toString()), status, QSTRING_CSTR(reason));
 | 
			
		||||
	ErrorIf(!reason.isEmpty(), _log, "%s", QSTRING_CSTR(reason));
 | 
			
		||||
	_receiveBuffer.clear();
 | 
			
		||||
	QByteArray sendBuffer;
 | 
			
		||||
 | 
			
		||||
	sendBuffer.append(136+(status-1000));
 | 
			
		||||
	int length = reason.size();
 | 
			
		||||
	if(length >= 126)
 | 
			
		||||
	{
 | 
			
		||||
		sendBuffer.append( (length > 0xffff) ? 127 : 126);
 | 
			
		||||
		int num_bytes = (length > 0xffff) ? 8 : 2;
 | 
			
		||||
 | 
			
		||||
		for(int c = num_bytes - 1; c != -1; c--)
 | 
			
		||||
		{
 | 
			
		||||
			sendBuffer.append( quint8((static_cast<unsigned long long>(length) >> (8 * c)) % 256));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		sendBuffer.append(quint8(length));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sendBuffer.append(reason.toUtf8());
 | 
			
		||||
 | 
			
		||||
	_socket->write(sendBuffer);
 | 
			
		||||
	_socket->flush();
 | 
			
		||||
	_socket->close();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void WebSocketClient::handleBinaryMessage(QByteArray &data)
 | 
			
		||||
{
 | 
			
		||||
	unsigned imgSize    = data.size() - 4;
 | 
			
		||||
	unsigned width      = ((data.at(2) << 8) & 0xFF00) | (data.at(3) & 0xFF);
 | 
			
		||||
	unsigned height     =  imgSize / width;
 | 
			
		||||
 | 
			
		||||
	if ( imgSize % width > 0 )
 | 
			
		||||
	{
 | 
			
		||||
		Error(_log, "data size is not multiple of width");
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Image<ColorRgb> image;
 | 
			
		||||
	image.resize(width, height);
 | 
			
		||||
 | 
			
		||||
	memcpy(image.memptr(), data.data()+4, imgSize);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
qint64 WebSocketClient::sendMessage(QJsonObject obj)
 | 
			
		||||
{
 | 
			
		||||
	QJsonDocument writer(obj);
 | 
			
		||||
	QByteArray data = writer.toJson(QJsonDocument::Compact) + "\n";
 | 
			
		||||
 | 
			
		||||
	if (!_socket || (_socket->state() != QAbstractSocket::ConnectedState)) return 0;
 | 
			
		||||
 | 
			
		||||
	qint64 payloadWritten = 0;
 | 
			
		||||
	quint32 payloadSize   = data.size();
 | 
			
		||||
	const char * payload  = data.data();
 | 
			
		||||
 | 
			
		||||
	qint32 numFrames = payloadSize / FRAME_SIZE_IN_BYTES + ((quint64(payloadSize) % FRAME_SIZE_IN_BYTES) > 0 ? 1 : 0);
 | 
			
		||||
 | 
			
		||||
	for (int i = 0; i < numFrames; ++i)
 | 
			
		||||
	{
 | 
			
		||||
		const bool isLastFrame = (i == (numFrames - 1));
 | 
			
		||||
 | 
			
		||||
		quint64 position  = i * FRAME_SIZE_IN_BYTES;
 | 
			
		||||
		quint32 frameSize = (payloadSize-position >= FRAME_SIZE_IN_BYTES) ? FRAME_SIZE_IN_BYTES : (payloadSize-position);
 | 
			
		||||
 | 
			
		||||
		QByteArray buf = makeFrameHeader((i == 0) ? OPCODE::TEXT : OPCODE::CONTINUATION, frameSize, isLastFrame);
 | 
			
		||||
		sendMessage_Raw(buf);
 | 
			
		||||
 | 
			
		||||
		qint64 written = sendMessage_Raw(payload+position,frameSize);
 | 
			
		||||
		if (written > 0)
 | 
			
		||||
		{
 | 
			
		||||
			payloadWritten += written;
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			_socket->flush();
 | 
			
		||||
			Error(_log, "Error writing bytes to socket: %s", QSTRING_CSTR(_socket->errorString()));
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (payloadSize != payloadWritten)
 | 
			
		||||
	{
 | 
			
		||||
		Error(_log, "Error writing bytes to socket %d bytes from %d written", payloadWritten, payloadSize);
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
	return payloadWritten;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
qint64 WebSocketClient::sendMessage_Raw(const char* data, quint64 size)
 | 
			
		||||
{
 | 
			
		||||
	return _socket->write(data, size);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
qint64 WebSocketClient::sendMessage_Raw(QByteArray &data)
 | 
			
		||||
{
 | 
			
		||||
	return _socket->write(data.data(), data.size());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
QByteArray WebSocketClient::makeFrameHeader(quint8 opCode, quint64 payloadLength, bool lastFrame)
 | 
			
		||||
{
 | 
			
		||||
	QByteArray header;
 | 
			
		||||
 | 
			
		||||
	if (payloadLength <= 0x7FFFFFFFFFFFFFFFULL)
 | 
			
		||||
	{
 | 
			
		||||
		//FIN, RSV1-3, opcode (RSV-1, RSV-2 and RSV-3 are zero)
 | 
			
		||||
		quint8 byte = static_cast<quint8>((opCode & 0x0F) | (lastFrame ? 0x80 : 0x00));
 | 
			
		||||
		header.append(static_cast<char>(byte));
 | 
			
		||||
 | 
			
		||||
		byte = 0x00;
 | 
			
		||||
		if (payloadLength <= 125)
 | 
			
		||||
		{
 | 
			
		||||
			byte |= static_cast<quint8>(payloadLength);
 | 
			
		||||
			header.append(static_cast<char>(byte));
 | 
			
		||||
		}
 | 
			
		||||
		else if (payloadLength <= 0xFFFFU)
 | 
			
		||||
		{
 | 
			
		||||
			byte |= 126;
 | 
			
		||||
			header.append(static_cast<char>(byte));
 | 
			
		||||
			quint16 swapped = qToBigEndian<quint16>(static_cast<quint16>(payloadLength));
 | 
			
		||||
			header.append(static_cast<const char *>(static_cast<const void *>(&swapped)), 2);
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			byte |= 127;
 | 
			
		||||
			header.append(static_cast<char>(byte));
 | 
			
		||||
			quint64 swapped = qToBigEndian<quint64>(payloadLength);
 | 
			
		||||
			header.append(static_cast<const char *>(static_cast<const void *>(&swapped)), 8);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		Error(_log, "Payload too big!");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return header;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,82 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <utils/Logger.h>
 | 
			
		||||
#include "WebSocketUtils.h"
 | 
			
		||||
#include <api/JsonAPI.h>
 | 
			
		||||
 | 
			
		||||
#include <QScopedPointer>
 | 
			
		||||
 | 
			
		||||
class QTcpSocket;
 | 
			
		||||
 | 
			
		||||
class QtHttpRequest;
 | 
			
		||||
class Hyperion;
 | 
			
		||||
class JsonAPI;
 | 
			
		||||
 | 
			
		||||
class WebSocketClient : public QObject
 | 
			
		||||
{
 | 
			
		||||
	Q_OBJECT
 | 
			
		||||
public:
 | 
			
		||||
	WebSocketClient(QtHttpRequest* request, QTcpSocket* sock, bool localConnection, QObject* parent);
 | 
			
		||||
 | 
			
		||||
	struct WebSocketHeader
 | 
			
		||||
	{
 | 
			
		||||
		bool          fin;
 | 
			
		||||
		quint8        opCode;
 | 
			
		||||
		bool          masked;
 | 
			
		||||
		quint64       payloadLength;
 | 
			
		||||
		char          key[4];
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	QTcpSocket* _socket;
 | 
			
		||||
	Logger* _log;
 | 
			
		||||
	Hyperion* _hyperion;
 | 
			
		||||
	QScopedPointer<JsonAPI> _jsonAPI;
 | 
			
		||||
 | 
			
		||||
	void getWsFrameHeader(WebSocketHeader* header);
 | 
			
		||||
	void sendClose(int status, const QString& reason = "");
 | 
			
		||||
	void handleBinaryMessage(QByteArray &data);
 | 
			
		||||
	qint64 sendMessage_Raw(const char* data, quint64 size);
 | 
			
		||||
	qint64 sendMessage_Raw(QByteArray &data);
 | 
			
		||||
	QByteArray makeFrameHeader(quint8 opCode, quint64 payloadLength, bool lastFrame);
 | 
			
		||||
 | 
			
		||||
	/// The buffer used for reading data from the socket
 | 
			
		||||
	QByteArray _receiveBuffer;
 | 
			
		||||
 | 
			
		||||
	/// buffer for websockets multi frame receive
 | 
			
		||||
	QByteArray _wsReceiveBuffer;
 | 
			
		||||
	quint8 _maskKey[4];
 | 
			
		||||
 | 
			
		||||
	bool _onContinuation = false;
 | 
			
		||||
 | 
			
		||||
	// true when data is missing for parsing
 | 
			
		||||
	bool _notEnoughData = false;
 | 
			
		||||
 | 
			
		||||
	// websocket header store
 | 
			
		||||
	WebSocketHeader _wsh;
 | 
			
		||||
 | 
			
		||||
	//opCode of first frame (in case of fragmented frames)
 | 
			
		||||
	quint8 _frameOpCode;
 | 
			
		||||
 | 
			
		||||
	// masks for fields in the basic header
 | 
			
		||||
	static uint8_t const BHB0_OPCODE = 0x0F;
 | 
			
		||||
	static uint8_t const BHB0_RSV3   = 0x10;
 | 
			
		||||
	static uint8_t const BHB0_RSV2   = 0x20;
 | 
			
		||||
	static uint8_t const BHB0_RSV1   = 0x40;
 | 
			
		||||
	static uint8_t const BHB0_FIN    = 0x80;
 | 
			
		||||
 | 
			
		||||
	static uint8_t const BHB1_PAYLOAD = 0x7F;
 | 
			
		||||
	static uint8_t const BHB1_MASK    = 0x80;
 | 
			
		||||
 | 
			
		||||
	static uint8_t const payload_size_code_16bit = 0x7E; // 126
 | 
			
		||||
	static uint8_t const payload_size_code_64bit = 0x7F; // 127
 | 
			
		||||
 | 
			
		||||
	static const quint64 FRAME_SIZE_IN_BYTES = 512 * 512 * 2;  //maximum size of a frame when sending a message
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
	void handleWebSocketFrame();
 | 
			
		||||
	qint64 sendMessage(QJsonObject obj);
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
	void handleMessage(const QString &message, const QString &httpAuthHeader);
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										49
									
								
								libsrc/webserver/WebSocketJsonHandler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								libsrc/webserver/WebSocketJsonHandler.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
			
		||||
#include "WebSocketJsonHandler.h"
 | 
			
		||||
 | 
			
		||||
#include <api/JsonAPI.h>
 | 
			
		||||
#include <api/JsonCallbacks.h>
 | 
			
		||||
#include <utils/JsonUtils.h>
 | 
			
		||||
#include <utils/NetOrigin.h>
 | 
			
		||||
 | 
			
		||||
WebSocketJsonHandler::WebSocketJsonHandler(QWebSocket* websocket, QObject* parent)
 | 
			
		||||
	: QObject(parent)
 | 
			
		||||
	, _websocket(websocket)
 | 
			
		||||
	, _log(Logger::getInstance("WEBSOCKET"))
 | 
			
		||||
{
 | 
			
		||||
	connect(_websocket, &QWebSocket::textMessageReceived, this, &WebSocketJsonHandler::onTextMessageReceived);
 | 
			
		||||
	connect(_websocket, &QWebSocket::disconnected, this, &WebSocketJsonHandler::onDisconnected);
 | 
			
		||||
 | 
			
		||||
	const QString client = _websocket->peerAddress().toString();
 | 
			
		||||
	Debug(_log, "New WebSocket connection from %s", QSTRING_CSTR(client));
 | 
			
		||||
 | 
			
		||||
	bool localConnection = NetOrigin::getInstance()->isLocalAddress(_websocket->peerAddress(), _websocket->localAddress());
 | 
			
		||||
 | 
			
		||||
	// Json processor
 | 
			
		||||
	_jsonAPI.reset(new JsonAPI(client, _log, localConnection, this));
 | 
			
		||||
 | 
			
		||||
	connect(_jsonAPI.get(), &JsonAPI::callbackReady, this, &WebSocketJsonHandler::sendMessage);
 | 
			
		||||
	connect(_jsonAPI->getCallBack().get(), &JsonCallbacks::callbackReady, this, &WebSocketJsonHandler::sendMessage);
 | 
			
		||||
 | 
			
		||||
	// Init JsonAPI
 | 
			
		||||
	_jsonAPI->initialize();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WebSocketJsonHandler::onTextMessageReceived(const QString& message)
 | 
			
		||||
{
 | 
			
		||||
	qDebug() << "WebSocket message received:" << message;
 | 
			
		||||
	_jsonAPI.get()->handleMessage(message);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
qint64 WebSocketJsonHandler::sendMessage(QJsonObject obj)
 | 
			
		||||
{
 | 
			
		||||
	QString const message = JsonUtils::jsonValueToQString(obj);
 | 
			
		||||
	qDebug() << "WebSocket send message: " << message;
 | 
			
		||||
	return _websocket->sendTextMessage(message);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WebSocketJsonHandler::onDisconnected()
 | 
			
		||||
{
 | 
			
		||||
	qDebug() << "WebSocket disconnected";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										30
									
								
								libsrc/webserver/WebSocketJsonHandler.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								libsrc/webserver/WebSocketJsonHandler.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
#ifndef WEBSOCKETJSONHANDLER_H
 | 
			
		||||
#define WEBSOCKETJSONHANDLER_H
 | 
			
		||||
 | 
			
		||||
#include <utils/Logger.h>
 | 
			
		||||
#include <api/JsonAPI.h>
 | 
			
		||||
 | 
			
		||||
#include <QObject>
 | 
			
		||||
#include <QWebSocket>
 | 
			
		||||
#include <QScopedPointer>
 | 
			
		||||
 | 
			
		||||
class WebSocketJsonHandler : public QObject
 | 
			
		||||
{
 | 
			
		||||
	Q_OBJECT
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	WebSocketJsonHandler(QWebSocket* websocket, QObject* parent = nullptr);
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
	void onTextMessageReceived(const QString& message);
 | 
			
		||||
	void onDisconnected();
 | 
			
		||||
	qint64 sendMessage(QJsonObject obj);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	QWebSocket* _websocket;
 | 
			
		||||
 | 
			
		||||
	Logger* _log;
 | 
			
		||||
	QScopedPointer<JsonAPI> _jsonAPI;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // WEBSOCKETJSONHANDLER_H
 | 
			
		||||
@@ -1,75 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
/// Constants and utility functions related to WebSocket opcodes
 | 
			
		||||
/**
 | 
			
		||||
 * WebSocket Opcodes are 4 bits. See RFC6455 section 5.2.
 | 
			
		||||
 */
 | 
			
		||||
namespace OPCODE
 | 
			
		||||
{
 | 
			
		||||
	enum value
 | 
			
		||||
	{
 | 
			
		||||
		CONTINUATION = 0x0,
 | 
			
		||||
		TEXT = 0x1,
 | 
			
		||||
		BINARY = 0x2,
 | 
			
		||||
		RSV3 = 0x3,
 | 
			
		||||
		RSV4 = 0x4,
 | 
			
		||||
		RSV5 = 0x5,
 | 
			
		||||
		RSV6 = 0x6,
 | 
			
		||||
		RSV7 = 0x7,
 | 
			
		||||
		CLOSE = 0x8,
 | 
			
		||||
		PING = 0x9,
 | 
			
		||||
		PONG = 0xA,
 | 
			
		||||
		CONTROL_RSVB = 0xB,
 | 
			
		||||
		CONTROL_RSVC = 0xC,
 | 
			
		||||
		CONTROL_RSVD = 0xD,
 | 
			
		||||
		CONTROL_RSVE = 0xE,
 | 
			
		||||
		CONTROL_RSVF = 0xF
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	/// Check if an opcode is reserved
 | 
			
		||||
	/**
 | 
			
		||||
	 * @param v The opcode to test.
 | 
			
		||||
	 * @return Whether or not the opcode is reserved.
 | 
			
		||||
	 */
 | 
			
		||||
	inline bool reserved(value v)
 | 
			
		||||
	{
 | 
			
		||||
		return (v >= RSV3 && v <= RSV7) || (v >= CONTROL_RSVB && v <= CONTROL_RSVF);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/// Check if an opcode is invalid
 | 
			
		||||
	/**
 | 
			
		||||
	 * Invalid opcodes are negative or require greater than 4 bits to store.
 | 
			
		||||
	 *
 | 
			
		||||
	 * @param v The opcode to test.
 | 
			
		||||
	 * @return Whether or not the opcode is invalid.
 | 
			
		||||
	 */
 | 
			
		||||
	inline bool invalid(value v)
 | 
			
		||||
	{
 | 
			
		||||
		return (v > 0xF || v < 0);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/// Check if an opcode is for a control frame
 | 
			
		||||
	/**
 | 
			
		||||
	 * @param v The opcode to test.
 | 
			
		||||
	 * @return Whether or not the opcode is a control opcode.
 | 
			
		||||
	*/
 | 
			
		||||
	inline bool is_control(value v)
 | 
			
		||||
	{
 | 
			
		||||
		return v >= 0x8;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace CLOSECODE
 | 
			
		||||
{
 | 
			
		||||
	enum value
 | 
			
		||||
	{
 | 
			
		||||
		NORMAL    = 1000,
 | 
			
		||||
		AWAY      = 1001,
 | 
			
		||||
		TERM      = 1002,
 | 
			
		||||
		INV_TYPE  = 1003,
 | 
			
		||||
		INV_DATA  = 1007,
 | 
			
		||||
		VIOLATION = 1008,
 | 
			
		||||
		BIG_MSG   = 1009,
 | 
			
		||||
		UNEXPECTED= 1011
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user