Backwards compatibility ensured

This commit is contained in:
Paulchen-Panther
2019-01-27 13:41:21 +01:00
parent 1f132bcfa9
commit a412c34e68
18 changed files with 164 additions and 46 deletions

View File

@@ -0,0 +1,15 @@
{
"type":"object",
"required":true,
"properties":{
"command": {
"type" : "string",
"required" : true,
"enum" : ["clearall"]
},
"tan" : {
"type" : "integer"
}
},
"additionalProperties": false
}

View File

@@ -24,7 +24,7 @@
"type": "string",
"minLength" : 4,
"maxLength" : 20,
"required": true
"required": false
},
"color": {
"type": "array",

View File

@@ -24,7 +24,7 @@
"type": "string",
"minLength" : 4,
"maxLength" : 20,
"required": true
"required": false
},
"effect": {
"type": "object",

View File

@@ -0,0 +1,12 @@
{
"type":"object",
"required":true,
"properties":{
"command": {
"title" : "This schema is used to ensure backward compatibility with hyperion classic",
"type" : "string",
"required" : false
}
},
"additionalProperties": true
}

View File

@@ -5,7 +5,7 @@
"command": {
"type" : "string",
"required" : true,
"enum" : ["color", "image", "effect", "create-effect", "delete-effect", "serverinfo", "clear", "clearall", "adjustment", "sourceselect", "config", "componentstate", "ledcolors", "logging", "processing", "sysinfo", "videomode"]
"enum" : ["color", "image", "effect", "create-effect", "delete-effect", "serverinfo", "clear", "clearall", "adjustment", "sourceselect", "config", "componentstate", "ledcolors", "logging", "processing", "sysinfo", "videomode", "transform", "correction" , "temperature"]
}
}
}

View File

@@ -6,6 +6,7 @@
<file alias="schema-serverinfo">JSONRPC_schema/schema-serverinfo.json</file>
<file alias="schema-sysinfo">JSONRPC_schema/schema-sysinfo.json</file>
<file alias="schema-clear">JSONRPC_schema/schema-clear.json</file>
<file alias="schema-clearall">JSONRPC_schema/schema-clearall.json</file>
<file alias="schema-adjustment">JSONRPC_schema/schema-adjustment.json</file>
<file alias="schema-effect">JSONRPC_schema/schema-effect.json</file>
<file alias="schema-create-effect">JSONRPC_schema/schema-create-effect.json</file>
@@ -17,5 +18,9 @@
<file alias="schema-logging">JSONRPC_schema/schema-logging.json</file>
<file alias="schema-processing">JSONRPC_schema/schema-processing.json</file>
<file alias="schema-videomode">JSONRPC_schema/schema-videomode.json</file>
<!-- The following schemas are derecated but used to ensure backward compatibility with hyperion Classic remote control-->
<file alias="schema-transform">JSONRPC_schema/schema-hyperion-classic.json</file>
<file alias="schema-correction">JSONRPC_schema/schema-hyperion-classic.json</file>
<file alias="schema-temperature">JSONRPC_schema/schema-hyperion-classic.json</file>
</qresource>
</RCC>

View File

@@ -13,6 +13,7 @@
#include <QBuffer>
#include <QByteArray>
#include <QDateTime>
#include <QHostInfo>
// hyperion includes
#include <utils/jsonschema/QJsonFactory.h>
@@ -104,6 +105,14 @@ void JsonAPI::handleMessage(const QString& messageString)
else if (command == "logging") handleLoggingCommand (message, command, tan);
else if (command == "processing") handleProcessingCommand (message, command, tan);
else if (command == "videomode") handleVideoModeCommand (message, command, tan);
// BEGIN | The following commands are derecated but used to ensure backward compatibility with hyperion Classic remote control
else if (command == "clearall") handleClearallCommand(message, command, tan);
else if (command == "transform" || command == "correction" || command == "temperature")
sendErrorReply("The command " + command + "is deprecated, please use the Hyperion Web Interface to configure");
// END
// handle not implemented commands
else handleNotImplemented ();
}
@@ -114,7 +123,7 @@ void JsonAPI::handleColorCommand(const QJsonObject& message, const QString& comm
// extract parameters
int priority = message["priority"].toInt();
int duration = message["duration"].toInt(-1);
QString origin = message["origin"].toString() + "@"+_peerAddress;
QString origin = message["origin"].toString("Empty") + "@"+_peerAddress;
std::vector<ColorRgb> colorData(_hyperion->getLedCount());
const QJsonArray & jsonColor = message["color"].toArray();
@@ -185,7 +194,7 @@ void JsonAPI::handleEffectCommand(const QJsonObject &message, const QString &com
int priority = message["priority"].toInt();
int duration = message["duration"].toInt(-1);
QString pythonScript = message["pythonScript"].toString();
QString origin = message["origin"].toString() + "@"+_peerAddress;
QString origin = message["origin"].toString("Empty") + "@"+_peerAddress;
const QJsonObject & effect = message["effect"].toObject();
const QString & effectName = effect["name"].toString();
const QString & data = message["imageData"].toString("").toUtf8();
@@ -463,6 +472,46 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject& message, const QString&
}
info["sessions"] = sessions;
// BEGIN | The following entries are derecated but used to ensure backward compatibility with hyperion Classic remote control
// TODO Output the real transformation information instead of default
// host name
info["hostname"] = QHostInfo::localHostName();
// transform information (default values)
QJsonArray transformArray;
for (const QString& transformId : _hyperion->getAdjustmentIds())
{
QJsonObject transform;
QJsonArray blacklevel, whitelevel, gamma, threshold;
transform["id"] = transformId;
transform["saturationGain"] = 1.0;
transform["valueGain"] = 1.0;
transform["saturationLGain"] = 1.0;
transform["luminanceGain"] = 1.0;
transform["luminanceMinimum"] = 0.0;
for (int i = 0; i < 3; i++ )
{
blacklevel.append(0.0);
whitelevel.append(1.0);
gamma.append(2.50);
threshold.append(0.0);
}
transform.insert("blacklevel", blacklevel);
transform.insert("whitelevel", whitelevel);
transform.insert("gamma", gamma);
transform.insert("threshold", threshold);
transformArray.append(transform);
}
info["transform"] = transformArray;
// END
sendSuccessDataReply(QJsonDocument(info), command, tan);
// AFTER we send the info, the client might want to subscribe to future updates
@@ -845,6 +894,16 @@ void JsonAPI::handleVideoModeCommand(const QJsonObject& message, const QString &
sendSuccessReply(command, tan);
}
void JsonAPI::handleClearallCommand(const QJsonObject& message, const QString& command, const int tan)
{
emit forwardJsonMessage(message);
// clear priority
_hyperion->clearall();
// send reply
sendSuccessReply(command, tan);
}
void JsonAPI::handleNotImplemented()
{

View File

@@ -6,9 +6,13 @@
#include <QTcpSocket>
#include <QHostAddress>
// websocket includes
#include "webserver/WebSocketClient.h"
JsonClientConnection::JsonClientConnection(QTcpSocket *socket)
: QObject()
, _socket(socket)
, _websocketClient(nullptr)
, _receiveBuffer()
, _log(Logger::getInstance("JSONCLIENTCONNECTION"))
{
@@ -23,21 +27,37 @@ JsonClientConnection::JsonClientConnection(QTcpSocket *socket)
void JsonClientConnection::readRequest()
{
_receiveBuffer += _socket->readAll();
// raw socket data, handling as usual
int bytes = _receiveBuffer.indexOf('\n') + 1;
while(bytes > 0)
// might be an old hyperion classic handshake request or raw socket data
if(_receiveBuffer.contains("Upgrade: websocket"))
{
// create message string
QString message(QByteArray(_receiveBuffer.data(), bytes));
if(_websocketClient == Q_NULLPTR)
{
// disconnect this slot from socket for further requests
disconnect(_socket, &QTcpSocket::readyRead, this, &JsonClientConnection::readRequest);
int start = _receiveBuffer.indexOf("Sec-WebSocket-Key") + 19;
QByteArray header(_receiveBuffer.mid(start, _receiveBuffer.indexOf("\r\n", start) - start).data());
_websocketClient = new WebSocketClient(header, _socket, this);
}
}
else
{
// raw socket data, handling as usual
int bytes = _receiveBuffer.indexOf('\n') + 1;
while(bytes > 0)
{
// create message string
QString message(QByteArray(_receiveBuffer.data(), bytes));
// remove message data from buffer
_receiveBuffer = _receiveBuffer.mid(bytes);
// remove message data from buffer
_receiveBuffer = _receiveBuffer.mid(bytes);
// handle message
_jsonAPI->handleMessage(message);
// handle message
_jsonAPI->handleMessage(message);
// try too look up '\n' again
bytes = _receiveBuffer.indexOf('\n') + 1;
// try too look up '\n' again
bytes = _receiveBuffer.indexOf('\n') + 1;
}
}
}

View File

@@ -10,9 +10,10 @@
class JsonAPI;
class QTcpSocket;
class WebSocketClient;
///
/// The Connection object created by \a JsonServer when a new connection is establshed
/// The Connection object created by \a JsonServer when a new connection is established
///
class JsonClientConnection : public QObject
{
@@ -41,6 +42,7 @@ private slots:
private:
QTcpSocket* _socket;
WebSocketClient* _websocketClient;
/// new instance of JsonAPI
JsonAPI * _jsonAPI;

View File

@@ -4,8 +4,8 @@
#include "QtHttpReply.h"
#include "QtHttpServer.h"
#include "QtHttpHeader.h"
#include "WebSocketClient.h"
#include "WebJsonRpc.h"
#include "webserver/WebSocketClient.h"
#include <QCryptographicHash>
#include <QTcpSocket>
@@ -120,7 +120,7 @@ void QtHttpClientWrapper::onClientDataReceived (void) {
{
// disconnect this slot from socket for further requests
disconnect(m_sockClient, &QTcpSocket::readyRead, this, &QtHttpClientWrapper::onClientDataReceived);
m_websocketClient = new WebSocketClient(m_currentRequest, m_sockClient, this);
m_websocketClient = new WebSocketClient(m_currentRequest->getHeader(QtHttpHeader::SecWebSocketKey), m_sockClient, this);
}
break;
}

View File

@@ -1,4 +1,4 @@
#include "WebSocketClient.h"
#include "webserver/WebSocketClient.h"
// hyperion includes
#include <hyperion/Hyperion.h>
@@ -7,25 +7,23 @@
#include <api/JsonAPI.h>
// qt includes
#include "QtHttpRequest.h"
#include "QtHttpHeader.h"
#include <QTcpSocket>
#include <QtEndian>
#include <QCryptographicHash>
#include <QJsonObject>
#include <QHostAddress>
WebSocketClient::WebSocketClient(QtHttpRequest* request, QTcpSocket* sock, QObject* parent)
WebSocketClient::WebSocketClient(QByteArray socketKey, QTcpSocket* sock, QObject* parent)
: QObject(parent)
, _socket(sock)
, _secWebSocketKey(socketKey)
, _log(Logger::getInstance("WEBSOCKET"))
, _hyperion(Hyperion::getInstance())
{
// 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();
const QString client = sock->peerAddress().toString();
// Json processor
_jsonAPI = new JsonAPI(client, _log, this);
@@ -34,8 +32,8 @@ WebSocketClient::WebSocketClient(QtHttpRequest* request, QTcpSocket* sock, QObje
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();
_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")
@@ -225,6 +223,7 @@ void WebSocketClient::sendClose(int status, QString reason)
_socket->close();
}
/*
void WebSocketClient::handleBinaryMessage(QByteArray &data)
{
//uint8_t priority = data.at(0);
@@ -243,9 +242,10 @@ void WebSocketClient::handleBinaryMessage(QByteArray &data)
image.resize(width, height);
memcpy(image.memptr(), data.data()+4, imgSize);
//_hyperion->registerInput();
//_hyperion->setInputImage(priority, image, duration_s*1000);
_hyperion->registerInput();
_hyperion->setInputImage(priority, image, duration_s*1000);
}
*/
qint64 WebSocketClient::sendMessage(QJsonObject obj)
{

View File

@@ -1,72 +0,0 @@
#pragma once
#include <utils/Logger.h>
#include "WebSocketUtils.h"
class QTcpSocket;
class QtHttpRequest;
class Hyperion;
class JsonAPI;
class WebSocketClient : public QObject {
Q_OBJECT
public:
WebSocketClient(QtHttpRequest* request, QTcpSocket* sock, QObject* parent);
struct WebSocketHeader
{
bool fin;
quint8 opCode;
bool masked;
quint64 payloadLength;
char key[4];
};
private:
QTcpSocket* _socket;
Logger* _log;
Hyperion* _hyperion;
JsonAPI* _jsonAPI;
void getWsFrameHeader(WebSocketHeader* header);
void sendClose(int status, 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;
// 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(void);
qint64 sendMessage(QJsonObject obj);
};

View File

@@ -1,68 +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
};
}