From bbc6fe389d07309057827125c9068903183f3a52 Mon Sep 17 00:00:00 2001 From: Gamadril Date: Sat, 25 Oct 2014 22:35:53 +0200 Subject: [PATCH 1/4] Added very basic WebSocket support to json server Former-commit-id: 5d62331eecdbd10287ba0520bbb06814bca0bca7 --- libsrc/jsonserver/JsonClientConnection.cpp | 150 ++++++++++++++++++++- libsrc/jsonserver/JsonClientConnection.h | 3 + 2 files changed, 152 insertions(+), 1 deletion(-) diff --git a/libsrc/jsonserver/JsonClientConnection.cpp b/libsrc/jsonserver/JsonClientConnection.cpp index d478a4d2..b82b51d7 100644 --- a/libsrc/jsonserver/JsonClientConnection.cpp +++ b/libsrc/jsonserver/JsonClientConnection.cpp @@ -10,6 +10,8 @@ // Qt includes #include #include +#include +#include // hyperion util includes #include @@ -27,6 +29,7 @@ JsonClientConnection::JsonClientConnection(QTcpSocket *socket, Hyperion * hyperi _hyperion(hyperion), _receiveBuffer() { + _webSocketHandshakeDone = false; // connect internal signals and slots connect(_socket, SIGNAL(disconnected()), this, SLOT(socketClosed())); connect(_socket, SIGNAL(readyRead()), this, SLOT(readData())); @@ -41,7 +44,126 @@ JsonClientConnection::~JsonClientConnection() void JsonClientConnection::readData() { _receiveBuffer += _socket->readAll(); + + if (_webSocketHandshakeDone) { // websocket mode, data frame + quint8 opCode = 0; + quint64 payloadLength = 0; + bool isMasked = false; + quint32 index = 0; + quint8 maskKey[4]; + + if ((_receiveBuffer.at(0) & 0x80) == 0x80) { // final bit + opCode = _receiveBuffer.at(0) & 0x0F; + isMasked = (_receiveBuffer.at(1) & 0x80) == 0x80; + payloadLength = _receiveBuffer.at(1) & 0x7F; + index = 2; + + switch (payloadLength) { + case 126: + payloadLength = ((_receiveBuffer.at(2) << 8) & 0xFF00) | (_receiveBuffer.at(3) & 0xFF); + index += 2; + break; + case 127: { + payloadLength = 0; + for (int i=0; i < 8; i++) { + payloadLength |= ((quint64)(_receiveBuffer.at(index+i) & 0xFF)) << (8*(7-i)); + } + index += 8; + } + break; + default: + break; + } + + if (isMasked) { // if the data is masked we need to get the key for unmasking + for (int i=0; i < 4; i++) { + maskKey[i] = _receiveBuffer.at(index + i); + } + index += 4; + } + + // check the type of data frame + switch (opCode) { + case 0x01: { // text + QByteArray result = _receiveBuffer.mid(index, payloadLength); + _receiveBuffer.clear(); + + // unmask data if necessary + if (isMasked) { + for (uint i=0 ; i < payloadLength; i++) { + result[i] = (result[i] ^ maskKey[i % 4]); + } + } + + handleMessage(QString(result).toStdString()); + } + break; + case 0x08: { // close + quint8 close[]={0x88, 0}; + _socket->write((const char*)close, 2); + _socket->flush(); + _socket->close(); + } + break; + case 0x09: { // ping, send pong + quint8 close[]={0x0A, 0}; + _socket->write((const char*)close, 2); + _socket->flush(); + } + break; + } + } else { + std::cout << "Someone is sending very big messages over several frames... it's not supported yet" << std::endl; + quint8 close[]={0x88, 0}; + _socket->write((const char*)close, 2); + _socket->flush(); + _socket->close(); + } + } else { // might be a handshake request or raw socket data + if(_receiveBuffer.contains("Upgrade: websocket")){ // http header, might not be a very reliable check... + std::cout << "Websocket handshake" << std::endl; + + // get the key to tprepare an answer + int start = _receiveBuffer.indexOf("Sec-WebSocket-Key") + 19; + std::string value(_receiveBuffer.mid(start, _receiveBuffer.indexOf("\r\n", start) - start).data()); + _receiveBuffer.clear(); + // must be always appended + value += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + + // generate sha1 hash + QByteArray hash = QCryptographicHash::hash(value.c_str(), QCryptographicHash::Sha1); + + // prepare an answer + std::ostringstream h; + h << "HTTP/1.1 101 Switching Protocols\r\n" << + "Upgrade: websocket\r\n" << + "Connection: Upgrade\r\n" << + "Sec-WebSocket-Accept: " << QString(hash.toBase64()).toStdString() << "\r\n\r\n"; + + _socket->write(h.str().c_str()); + _socket->flush(); + _webSocketHandshakeDone = true; // we are in WebSocket mode, data frames should follow next + } else { // raw socket data, handling as usual + int bytes = _receiveBuffer.indexOf('\n') + 1; + while(bytes > 0) + { + // create message string + std::string message(_receiveBuffer.data(), bytes); + + // remove message data from buffer + _receiveBuffer = _receiveBuffer.mid(bytes); + + // handle message + handleMessage(message); + + // try too look up '\n' again + bytes = _receiveBuffer.indexOf('\n') + 1; + } + } + } + + /* int bytes = _receiveBuffer.indexOf('\n') + 1; while(bytes > 0) { @@ -57,10 +179,12 @@ void JsonClientConnection::readData() // try too look up '\n' again bytes = _receiveBuffer.indexOf('\n') + 1; } + */ } void JsonClientConnection::socketClosed() { + _webSocketHandshakeDone = false; emit connectionClosed(this); } @@ -205,6 +329,9 @@ void JsonClientConnection::handleServerInfoCommand(const Json::Value &) Json::Value result; result["success"] = true; Json::Value & info = result["info"]; + + // add host name for remote clients + info["hostname"] = QHostInfo::localHostName().toStdString(); // collect priority information Json::Value & priorities = info["priorities"] = Json::Value(Json::arrayValue); @@ -362,7 +489,28 @@ void JsonClientConnection::sendMessage(const Json::Value &message) { Json::FastWriter writer; std::string serializedReply = writer.write(message); - _socket->write(serializedReply.data(), serializedReply.length()); + + if (!_webSocketHandshakeDone) { // raw tcp socket mode + _socket->write(serializedReply.data(), serializedReply.length()); + } else { // websocket mode + quint32 size = serializedReply.length(); + + // prepare data frame + QByteArray response; + response.append(0x81); + if (size > 125) { + response.append(0x7E); + response.append((size >> 8) & 0xFF); + response.append(size & 0xFF); + } else { + response.append(size); + } + + QByteArray data(serializedReply.c_str(), serializedReply.length()); + response.append(data); + + _socket->write(response.data(), response.length()); + } } void JsonClientConnection::sendSuccessReply() diff --git a/libsrc/jsonserver/JsonClientConnection.h b/libsrc/jsonserver/JsonClientConnection.h index dc80deba..b036e638 100644 --- a/libsrc/jsonserver/JsonClientConnection.h +++ b/libsrc/jsonserver/JsonClientConnection.h @@ -161,4 +161,7 @@ private: /// The buffer used for reading data from the socket QByteArray _receiveBuffer; + + /// used for WebSocket detection and connection handling + bool _webSocketHandshakeDone; }; From 6a9e75243d9bfa383381227549cbe37dea064abe Mon Sep 17 00:00:00 2001 From: Gamadril Date: Sun, 26 Oct 2014 10:55:40 +0100 Subject: [PATCH 2/4] Update JsonClientConnection.cpp small code cleanup Former-commit-id: 82029cd23f77b393b2e6688ed6e65424cb676452 --- libsrc/jsonserver/JsonClientConnection.cpp | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/libsrc/jsonserver/JsonClientConnection.cpp b/libsrc/jsonserver/JsonClientConnection.cpp index b82b51d7..d50ed951 100644 --- a/libsrc/jsonserver/JsonClientConnection.cpp +++ b/libsrc/jsonserver/JsonClientConnection.cpp @@ -65,7 +65,7 @@ void JsonClientConnection::readData() break; case 127: { payloadLength = 0; - for (int i=0; i < 8; i++) { + for (uint i=0; i < 8; i++) { payloadLength |= ((quint64)(_receiveBuffer.at(index+i) & 0xFF)) << (8*(7-i)); } index += 8; @@ -76,7 +76,7 @@ void JsonClientConnection::readData() } if (isMasked) { // if the data is masked we need to get the key for unmasking - for (int i=0; i < 4; i++) { + for (uint i=0; i < 4; i++) { maskKey[i] = _receiveBuffer.at(index + i); } index += 4; @@ -162,24 +162,6 @@ void JsonClientConnection::readData() } } } - - /* - int bytes = _receiveBuffer.indexOf('\n') + 1; - while(bytes > 0) - { - // create message string - std::string message(_receiveBuffer.data(), bytes); - - // remove message data from buffer - _receiveBuffer = _receiveBuffer.mid(bytes); - - // handle message - handleMessage(message); - - // try too look up '\n' again - bytes = _receiveBuffer.indexOf('\n') + 1; - } - */ } void JsonClientConnection::socketClosed() From 9168ee5098ccd5498181f5a500e8ca8020afb805 Mon Sep 17 00:00:00 2001 From: Gamadril Date: Sat, 8 Nov 2014 21:01:46 +0100 Subject: [PATCH 3/4] code refactoring Former-commit-id: 157f424e83c58fca4ed1e3283899cbbdfadcffe1 --- libsrc/jsonserver/JsonClientConnection.cpp | 253 ++++++++++++--------- libsrc/jsonserver/JsonClientConnection.h | 10 + 2 files changed, 156 insertions(+), 107 deletions(-) diff --git a/libsrc/jsonserver/JsonClientConnection.cpp b/libsrc/jsonserver/JsonClientConnection.cpp index d50ed951..87fb7c1f 100644 --- a/libsrc/jsonserver/JsonClientConnection.cpp +++ b/libsrc/jsonserver/JsonClientConnection.cpp @@ -27,9 +27,9 @@ JsonClientConnection::JsonClientConnection(QTcpSocket *socket, Hyperion * hyperi _socket(socket), _imageProcessor(ImageProcessorFactory::getInstance().newImageProcessor()), _hyperion(hyperion), - _receiveBuffer() + _receiveBuffer(), + _webSocketHandshakeDone(false) { - _webSocketHandshakeDone = false; // connect internal signals and slots connect(_socket, SIGNAL(disconnected()), this, SLOT(socketClosed())); connect(_socket, SIGNAL(readyRead()), this, SLOT(readData())); @@ -45,106 +45,19 @@ void JsonClientConnection::readData() { _receiveBuffer += _socket->readAll(); - if (_webSocketHandshakeDone) { // websocket mode, data frame - quint8 opCode = 0; - quint64 payloadLength = 0; - bool isMasked = false; - quint32 index = 0; - quint8 maskKey[4]; - - if ((_receiveBuffer.at(0) & 0x80) == 0x80) { // final bit - opCode = _receiveBuffer.at(0) & 0x0F; - isMasked = (_receiveBuffer.at(1) & 0x80) == 0x80; - payloadLength = _receiveBuffer.at(1) & 0x7F; - index = 2; - - switch (payloadLength) { - case 126: - payloadLength = ((_receiveBuffer.at(2) << 8) & 0xFF00) | (_receiveBuffer.at(3) & 0xFF); - index += 2; - break; - case 127: { - payloadLength = 0; - for (uint i=0; i < 8; i++) { - payloadLength |= ((quint64)(_receiveBuffer.at(index+i) & 0xFF)) << (8*(7-i)); - } - index += 8; - } - break; - default: - break; - } - - if (isMasked) { // if the data is masked we need to get the key for unmasking - for (uint i=0; i < 4; i++) { - maskKey[i] = _receiveBuffer.at(index + i); - } - index += 4; - } - - // check the type of data frame - switch (opCode) { - case 0x01: { // text - QByteArray result = _receiveBuffer.mid(index, payloadLength); - _receiveBuffer.clear(); - - // unmask data if necessary - if (isMasked) { - for (uint i=0 ; i < payloadLength; i++) { - result[i] = (result[i] ^ maskKey[i % 4]); - } - } - - handleMessage(QString(result).toStdString()); - } - break; - case 0x08: { // close - quint8 close[]={0x88, 0}; - _socket->write((const char*)close, 2); - _socket->flush(); - _socket->close(); - } - break; - case 0x09: { // ping, send pong - quint8 close[]={0x0A, 0}; - _socket->write((const char*)close, 2); - _socket->flush(); - } - break; - } - } else { - std::cout << "Someone is sending very big messages over several frames... it's not supported yet" << std::endl; - quint8 close[]={0x88, 0}; - _socket->write((const char*)close, 2); - _socket->flush(); - _socket->close(); - } - } else { // might be a handshake request or raw socket data - if(_receiveBuffer.contains("Upgrade: websocket")){ // http header, might not be a very reliable check... - std::cout << "Websocket handshake" << std::endl; - - // get the key to tprepare an answer - int start = _receiveBuffer.indexOf("Sec-WebSocket-Key") + 19; - std::string value(_receiveBuffer.mid(start, _receiveBuffer.indexOf("\r\n", start) - start).data()); - _receiveBuffer.clear(); - - // must be always appended - value += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - - // generate sha1 hash - QByteArray hash = QCryptographicHash::hash(value.c_str(), QCryptographicHash::Sha1); - - // prepare an answer - std::ostringstream h; - h << "HTTP/1.1 101 Switching Protocols\r\n" << - "Upgrade: websocket\r\n" << - "Connection: Upgrade\r\n" << - "Sec-WebSocket-Accept: " << QString(hash.toBase64()).toStdString() << "\r\n\r\n"; - - _socket->write(h.str().c_str()); - _socket->flush(); - _webSocketHandshakeDone = true; // we are in WebSocket mode, data frames should follow next - } else { // raw socket data, handling as usual + if (_webSocketHandshakeDone) + { + // websocket mode, data frame + handleWebSocketFrame(); + } else + { + // might be a handshake request or raw socket data + if(_receiveBuffer.contains("Upgrade: websocket")) + { + doWebSocketHandshake(); + } else + { + // raw socket data, handling as usual int bytes = _receiveBuffer.indexOf('\n') + 1; while(bytes > 0) { @@ -164,6 +77,128 @@ void JsonClientConnection::readData() } } +void JsonClientConnection::handleWebSocketFrame() +{ + if ((_receiveBuffer.at(0) & 0x80) == 0x80) + { + // final bit found, frame complete + quint8 * maskKey = NULL; + quint8 opCode = _receiveBuffer.at(0) & 0x0F; + bool isMasked = (_receiveBuffer.at(1) & 0x80) == 0x80; + quint64 payloadLength = _receiveBuffer.at(1) & 0x7F; + quint32 index = 2; + + switch (payloadLength) + { + case 126: + payloadLength = ((_receiveBuffer.at(2) << 8) & 0xFF00) | (_receiveBuffer.at(3) & 0xFF); + index += 2; + break; + case 127: + payloadLength = 0; + for (uint i=0; i < 8; i++) { + payloadLength |= ((quint64)(_receiveBuffer.at(index+i) & 0xFF)) << (8*(7-i)); + } + index += 8; + break; + default: + break; + } + + if (isMasked) + { + // if the data is masked we need to get the key for unmasking + maskKey = new quint8[4]; + for (uint i=0; i < 4; i++) + { + maskKey[i] = _receiveBuffer.at(index + i); + } + index += 4; + } + + // check the type of data frame + switch (opCode) + { + case 0x01: + { + // frame contains text, extract it + QByteArray result = _receiveBuffer.mid(index, payloadLength); + _receiveBuffer.clear(); + + // unmask data if necessary + if (isMasked) + { + for (uint i=0; i < payloadLength; i++) + { + result[i] = (result[i] ^ maskKey[i % 4]); + } + if (maskKey != NULL) + { + delete[] maskKey; + maskKey = NULL; + } + } + + handleMessage(QString(result).toStdString()); + } + break; + case 0x08: + { + // close request, confirm + quint8 close[] = {0x88, 0}; + _socket->write((const char*)close, 2); + _socket->flush(); + _socket->close(); + } + break; + case 0x09: + { + // ping received, send pong + quint8 pong[] = {0x0A, 0}; + _socket->write((const char*)pong, 2); + _socket->flush(); + } + break; + } + } else + { + std::cout << "Someone is sending very big messages over several frames... it's not supported yet" << std::endl; + quint8 close[] = {0x88, 0}; + _socket->write((const char*)close, 2); + _socket->flush(); + _socket->close(); + } +} + +void JsonClientConnection::doWebSocketHandshake() +{ + // http header, might not be a very reliable check... + std::cout << "Websocket handshake" << std::endl; + + // get the key to prepare an answer + int start = _receiveBuffer.indexOf("Sec-WebSocket-Key") + 19; + std::string value(_receiveBuffer.mid(start, _receiveBuffer.indexOf("\r\n", start) - start).data()); + _receiveBuffer.clear(); + + // must be always appended + value += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + + // generate sha1 hash + QByteArray hash = QCryptographicHash::hash(value.c_str(), QCryptographicHash::Sha1); + + // prepare an answer + std::ostringstream h; + h << "HTTP/1.1 101 Switching Protocols\r\n" << + "Upgrade: websocket\r\n" << + "Connection: Upgrade\r\n" << + "Sec-WebSocket-Accept: " << QString(hash.toBase64()).toStdString() << "\r\n\r\n"; + + _socket->write(h.str().c_str()); + _socket->flush(); + // we are in WebSocket mode, data frames should follow next + _webSocketHandshakeDone = true; +} + void JsonClientConnection::socketClosed() { _webSocketHandshakeDone = false; @@ -472,15 +507,20 @@ void JsonClientConnection::sendMessage(const Json::Value &message) Json::FastWriter writer; std::string serializedReply = writer.write(message); - if (!_webSocketHandshakeDone) { // raw tcp socket mode + if (!_webSocketHandshakeDone) + { + // raw tcp socket mode _socket->write(serializedReply.data(), serializedReply.length()); - } else { // websocket mode + } else + { + // websocket mode quint32 size = serializedReply.length(); // prepare data frame QByteArray response; response.append(0x81); - if (size > 125) { + if (size > 125) + { response.append(0x7E); response.append((size >> 8) & 0xFF); response.append(size & 0xFF); @@ -488,8 +528,7 @@ void JsonClientConnection::sendMessage(const Json::Value &message) response.append(size); } - QByteArray data(serializedReply.c_str(), serializedReply.length()); - response.append(data); + response.append(serializedReply.c_str(), serializedReply.length()); _socket->write(response.data(), response.length()); } diff --git a/libsrc/jsonserver/JsonClientConnection.h b/libsrc/jsonserver/JsonClientConnection.h index b036e638..6575388a 100644 --- a/libsrc/jsonserver/JsonClientConnection.h +++ b/libsrc/jsonserver/JsonClientConnection.h @@ -136,6 +136,16 @@ private: /// @param error String describing the error /// void sendErrorReply(const std::string & error); + + /// + /// Do handshake for a websocket connection + /// + void doWebSocketHandshake(); + + /// + /// Handle incoming websocket data frame + /// + void handleWebSocketFrame(); private: /// From 0ea9c87c1a0e2b219fbbb4ab78fc2644854961b2 Mon Sep 17 00:00:00 2001 From: "T. van der Zwan" Date: Sun, 9 Nov 2014 15:56:21 +0100 Subject: [PATCH 4/4] Updated RPi release Former-commit-id: e25fabd5625c4c260a81766503bd2f9c67080a36 --- deploy/hyperion.tar.gz.REMOVED.git-id | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/hyperion.tar.gz.REMOVED.git-id b/deploy/hyperion.tar.gz.REMOVED.git-id index e7c9bf76..874b3ca0 100644 --- a/deploy/hyperion.tar.gz.REMOVED.git-id +++ b/deploy/hyperion.tar.gz.REMOVED.git-id @@ -1 +1 @@ -e1ca70a63f1ce9b6975aab98b8eb6ae1a353df14 \ No newline at end of file +c2332ae026dcd9b5ededbbe2db493ae13e5208c5 \ No newline at end of file