From 9428586195fe6e76bd22494f650f39371ff8adfb Mon Sep 17 00:00:00 2001 From: redPanther Date: Mon, 5 Dec 2016 11:10:44 +0100 Subject: [PATCH] Json write (#313) * always output latest version of config file to webui * fix permissions after default config export * tune code * set permissions for exported effects * use qt setperm instead of chmod update effects code style a bit * add fallback when config is not readable * ui: when sending config, convert to utf8 to save size and avoid jumbo frames (todo: minify it) jsonclient: add some constants for websocket frames (taken from https://github.com/zaphoyd/websocketpp/blob/master/websocketpp/frame.hpp) * webui: refactory of websocket connector sended json data is always convert to utf8 --- assets/webconfig/js/hyperion.js | 63 ++++++++++++------- assets/webconfig/js/ui_utils.js | 31 ++++++---- libsrc/jsonserver/JsonClientConnection.cpp | 33 +++++----- libsrc/jsonserver/JsonClientConnection.h | 70 +++++++++++++++++++++- 4 files changed, 149 insertions(+), 48 deletions(-) diff --git a/assets/webconfig/js/hyperion.js b/assets/webconfig/js/hyperion.js index 23fc5b7a..23f76463 100644 --- a/assets/webconfig/js/hyperion.js +++ b/assets/webconfig/js/hyperion.js @@ -116,7 +116,20 @@ function initWebSocket() } } +function sendToHyperion(command, subcommand, msg) +{ + if (typeof subcommand != 'undefined' && subcommand.length > 0) + subcommand = ',"subcommand":"'+subcommand+'"'; + else + subcommand = ""; + if (typeof msg != 'undefined' && msg.length > 0) + msg = ","+msg; + else + msg = ""; + + websocket.send(encode_utf8('{"command":"'+command+'", "tan":'+wsTan+subcommand+msg+'}')); +} // ----------------------------------------------------------- // wrapped server commands @@ -124,55 +137,55 @@ function initWebSocket() // also used for watchdog function requestServerInfo() { watchdog++; - websocket.send('{"command":"serverinfo", "tan":'+wsTan+'}'); + sendToHyperion("serverinfo"); } function requestServerConfigSchema() { - websocket.send('{"command":"config", "tan":'+wsTan+',"subcommand":"getschema"}'); + sendToHyperion("config","getschema"); } function requestServerConfig() { - websocket.send('{"command":"config", "tan":'+wsTan+',"subcommand":"getconfig"}'); + sendToHyperion("config", "getconfig"); } function requestServerConfigReload() { - websocket.send('{"command":"config", "tan":'+wsTan+',"subcommand":"reload"}'); + sendToHyperion("config", "reload"); } function requestLedColorsStart() { ledStreamActive=true; - websocket.send('{"command":"ledcolors", "tan":'+wsTan+',"subcommand":"ledstream-start"}'); + sendToHyperion("ledcolors", "ledstream-start"); } function requestLedColorsStop() { ledStreamActive=false; - websocket.send('{"command":"ledcolors", "tan":'+wsTan+',"subcommand":"ledstream-stop"}'); + sendToHyperion("ledcolors", "ledstream-stop"); } function requestPriorityClear() { - websocket.send('{"command":"clear", "tan":'+wsTan+', "priority":1}'); + sendToHyperion("clear", "", '"priority":1'); } function requestPlayEffect(effectName) { - websocket.send('{"command":"effect", "tan":'+wsTan+',"effect":{"name":"'+effectName+'"},"priority":1}'); + sendToHyperion("effect", "", '"effect":{"name":"'+effectName+'"},"priority":1'); } function requestSetColor(r,g,b) { - websocket.send('{"command":"color", "tan":'+wsTan+', "color":['+r+','+g+','+b+'], "priority":1}'); + sendToHyperion("color", "", '"color":['+r+','+g+','+b+'], "priority":1'); } function requestSetComponentState(comp, state){ - state_str = state?"true":"false"; - websocket.send('{"command":"componentstate", "tan":'+wsTan+',"componentstate":{"component":"'+comp+'","state":'+state_str+'}}'); + state_str = state ? "true" : "false"; + sendToHyperion("componentstate", "", '"componentstate":{"component":"'+comp+'","state":'+state_str+'}'); console.log(comp+' state: '+state_str); } function requestSetSource(prio) { if ( prio == "auto" ) - websocket.send('{"command":"sourceselect", "tan":'+wsTan+', "auto" : true}'); + sendToHyperion("sourceselect", "", '"auto":true'); else - websocket.send('{"command":"sourceselect", "tan":'+wsTan+', "priority" : '+prio+'}'); + sendToHyperion("sourceselect", "", '"priority":'+prio); } function requestWriteConfig(config) @@ -181,30 +194,36 @@ function requestWriteConfig(config) jQuery.each(config, function(i, val) { complete_config[i] = val; }); - websocket.send('{"command":"config","subcommand":"setconfig", "tan":'+wsTan+', "config":'+JSON.stringify(complete_config)+'}'); + + var config_str = JSON.stringify(complete_config); + console.log(config_str.length); + sendToHyperion("config","setconfig", '"config":'+config_str); } function requestWriteEffect(effectName,effectPy,effectArgs) { var cutArgs = effectArgs.slice(1, -1); - websocket.send('{"command":"create-effect","name":"'+effectName+'", "script":"'+effectPy+'", '+cutArgs+'}'); + sendToHyperion("create-effect", "", '"name":"'+effectName+'", "script":"'+effectPy+'", '+cutArgs); } function requestTestEffect(effectName,effectPy,effectArgs) { - websocket.send('{"command":"effect", "tan":'+wsTan+',"effect":{"name":"'+effectName+'", "args":'+effectArgs+'},"priority":1, "pythonScript":"'+effectPy+'"}'); + sendToHyperion("effect", "", '"effect":{"name":"'+effectName+'", "args":'+effectArgs+'},"priority":1, "pythonScript":"'+effectPy+'"}'); } -function requestDeleteEffect(effectName) { - websocket.send('{"command":"delete-effect", "tan":'+wsTan+',"name":"'+effectName+'"}'); +function requestDeleteEffect(effectName) +{ + sendToHyperion("delete-effect", "", '"name":"'+effectName+'"'); } -function requestLoggingStart() { +function requestLoggingStart() +{ loggingStreamActive=true; - websocket.send('{"command":"logging", "tan":'+wsTan+',"subcommand":"start"}'); + sendToHyperion("logging", "start"); } -function requestLoggingStop() { +function requestLoggingStop() +{ loggingStreamActive=false; - websocket.send('{"command":"logging", "tan":'+wsTan+',"subcommand":"stop"}'); + sendToHyperion("logging", "stop"); } diff --git a/assets/webconfig/js/ui_utils.js b/assets/webconfig/js/ui_utils.js index e21d7705..687442dd 100644 --- a/assets/webconfig/js/ui_utils.js +++ b/assets/webconfig/js/ui_utils.js @@ -120,30 +120,41 @@ function createJsonEditor(container,schema,setconfig) return editor; } -function createSelGroup(group){ +function createSelGroup(group) +{ var el = document.createElement('optgroup'); el.setAttribute('label', group); - return el + return el; } -function createSelOpt(opt){ +function createSelOpt(opt) +{ var el = document.createElement('option'); el.setAttribute('value', opt); el.innerHTML = opt; - return el + return el; } -function createSel(array, group){ - if (array.length != "0"){ - var el = createSelGroup(group); - for(var i=0; iwrite((const char*)close, 2); _socket->flush(); _socket->close(); } break; - case 0x09: + case OPCODE::PING: { // ping received, send pong - quint8 pong[] = {0x0A, 0}; + quint8 pong[] = {OPCODE::PONG, 0}; _socket->write((const char*)pong, 2); _socket->flush(); } break; } - } else + } + else { Error(_log, "Someone is sending very big messages over several frames... it's not supported yet"); quint8 close[] = {0x88, 0}; @@ -1165,7 +1168,7 @@ void JsonClientConnection::handleConfigSetCommand(const QJsonObject& message, co QJsonObject hyperionConfig = message["config"].toObject(); QJsonFactory::writeJson(QString::fromStdString(_hyperion->getConfigFileName()), hyperionConfig); - + sendSuccessReply(command, tan); } } else diff --git a/libsrc/jsonserver/JsonClientConnection.h b/libsrc/jsonserver/JsonClientConnection.h index f0b08676..4d272e5e 100644 --- a/libsrc/jsonserver/JsonClientConnection.h +++ b/libsrc/jsonserver/JsonClientConnection.h @@ -17,6 +17,63 @@ class ImageProcessor; + +/// 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; + } +} + + + /// /// The Connection object created by \a JsonServer when a new connection is establshed /// @@ -228,7 +285,6 @@ private: /// void forwardJsonMessage(const QJsonObject & message); -private: /// /// Check if a JSON messag is valid according to a given JSON schema /// @@ -270,4 +326,16 @@ private: QJsonObject _streaming_logging_reply; bool _streaming_logging_activated; + // 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 };