From f1e685fe5f3bccf21ee139b416c0a7743f020d83 Mon Sep 17 00:00:00 2001 From: slaff Date: Wed, 22 Nov 2017 10:31:11 +0100 Subject: [PATCH] Initial test code for improved sendPing and sendPong. (#1270) * Changed ping and pong to have automatic mask generation. If there is no payload the mask will be 0x0000. --- .../Http/Websocket/WebSocketConnection.cpp | 19 ++++-- .../Http/Websocket/WebSocketConnection.h | 8 ++- Sming/SmingCore/Network/WebsocketClient.cpp | 49 ++++++++++++-- Sming/SmingCore/Network/WebsocketClient.h | 65 +++++++++++++------ Sming/SmingCore/Network/WebsocketFrame.cpp | 17 +++++ Sming/SmingCore/Network/WebsocketFrame.h | 45 +++++++------ 6 files changed, 151 insertions(+), 52 deletions(-) diff --git a/Sming/SmingCore/Network/Http/Websocket/WebSocketConnection.cpp b/Sming/SmingCore/Network/Http/Websocket/WebSocketConnection.cpp index a8c72b781f..646e207b56 100644 --- a/Sming/SmingCore/Network/Http/Websocket/WebSocketConnection.cpp +++ b/Sming/SmingCore/Network/Http/Websocket/WebSocketConnection.cpp @@ -125,7 +125,9 @@ int WebSocketConnection::staticOnControlBegin(void* userData, ws_frame_type_t ty return -1; } - connection->controlFrameType = type; + connection->controlFrame.type = type; + connection->controlFrame.payload = NULL; + connection->controlFrame.payloadLegth = 0; if (type == WS_FRAME_CLOSE) { connection->close(); @@ -136,6 +138,14 @@ int WebSocketConnection::staticOnControlBegin(void* userData, ws_frame_type_t ty int WebSocketConnection::staticOnControlPayload(void* userData, const char *data, size_t length) { + WebSocketConnection *connection = (WebSocketConnection *)userData; + if (connection == NULL) { + return -1; + } + + connection->controlFrame.payload = (char *)data; + connection->controlFrame.payloadLegth = length; + return WS_OK; } @@ -146,9 +156,10 @@ int WebSocketConnection::staticOnControlEnd(void* userData) return -1; } - if(connection->controlFrameType == WS_FRAME_PING) { - // TODO: add control frame payload processing... - connection->send((const char* )NULL, 0, WS_PONG_FRAME); + if(connection->controlFrame.type == WS_FRAME_PING) { + connection->send((const char* )connection->controlFrame.payload, + connection->controlFrame.payloadLegth, + WS_PONG_FRAME); } return WS_OK; diff --git a/Sming/SmingCore/Network/Http/Websocket/WebSocketConnection.h b/Sming/SmingCore/Network/Http/Websocket/WebSocketConnection.h index dc90541c19..a8d32c23e6 100644 --- a/Sming/SmingCore/Network/Http/Websocket/WebSocketConnection.h +++ b/Sming/SmingCore/Network/Http/Websocket/WebSocketConnection.h @@ -30,6 +30,12 @@ enum WsConnectionState eWSCS_Closed }; +typedef struct { + ws_frame_type_t type; + char* payload; + size_t payloadLegth; +} WsFrameInfo; + class WebSocketConnection { public: @@ -84,7 +90,7 @@ class WebSocketConnection HttpServerConnection* connection = nullptr; ws_frame_type_t frameType = WS_FRAME_TEXT; - ws_frame_type_t controlFrameType = WS_FRAME_PING; + WsFrameInfo controlFrame; ws_parser_t parser; ws_parser_callbacks_t parserSettings; diff --git a/Sming/SmingCore/Network/WebsocketClient.cpp b/Sming/SmingCore/Network/WebsocketClient.cpp index 30166c8172..749304f34e 100644 --- a/Sming/SmingCore/Network/WebsocketClient.cpp +++ b/Sming/SmingCore/Network/WebsocketClient.cpp @@ -156,18 +156,16 @@ void WebsocketClient::onFinished(TcpClientState finishState) TcpClient::onFinished(finishState); } -void WebsocketClient::sendPing() +bool WebsocketClient::sendPing(const String& payload /* = "" */) { - uint8_t buf[2] = { 0x89, 0x00 }; debugf("Sending PING"); - send((char*) buf, 2, false); + return sendControlFrame(WSFrameType::ping, payload); } -void WebsocketClient::sendPong() +bool WebsocketClient::sendPong(const String& payload /* = "" */) { - uint8_t buf[2] = { 0x8A, 0x00 }; debugf("Sending PONG"); - send((char*) buf, 2, false); + return sendControlFrame(WSFrameType::pong, payload); } void WebsocketClient::disconnect() @@ -209,6 +207,43 @@ void WebsocketClient::sendMessage(const String& str) _sendFrame(WSFrameType::text, (uint8_t*) str.c_str(), str.length() + 1); } +bool WebsocketClient::sendControlFrame(WSFrameType frameType, const String& payload /* = "" */) +{ + if(payload.length() > 127) { + debugf("Maximum length of payload is 127 bytes"); + return false; + } + + uint32_t mask = 0; + int size = 2 + payload.length() + 4 * mask; + uint8_t* buf = new uint8_t[size]; + + // if we have payload, generate random mask for it + if(payload.length()) { + mask = ESP8266_DREG(0x20E44); // See: http://esp8266-re.foogod.com/wiki/Random_Number_Generator + } + + int pos = 0; + buf[pos++] = (uint8_t)frameType; + buf[pos++] = 0x00; + buf[pos] |= bit(7); + + if(payload.length()) { + buf[pos] += payload.length(); + } + + buf[++pos] = (mask >> 24) & 0xFF; + buf[++pos] = (mask >> 16) & 0xFF; + buf[++pos] = (mask >> 8) & 0xFF; + buf[++pos] = (mask >> 0) & 0xFF; + + WebsocketFrameClass::mask(payload, mask, (char *)(buf+pos+1)); + + send((char*) buf, size, false); + + return true; +} + err_t WebsocketClient::onReceive(pbuf* buf) { if (buf == NULL) @@ -280,7 +315,7 @@ err_t WebsocketClient::onReceive(pbuf* buf) case WSFrameType::ping: { debugf("Got ping ..."); - sendPong(); //Need to send Pong in response to Ping + sendPong(String((char*)wsFrame._payload, wsFrame._payloadLength)); //Need to send Pong in response to Ping break; } case WSFrameType::pong: diff --git a/Sming/SmingCore/Network/WebsocketClient.h b/Sming/SmingCore/Network/WebsocketClient.h index 7342332ec3..16ee642ad5 100644 --- a/Sming/SmingCore/Network/WebsocketClient.h +++ b/Sming/SmingCore/Network/WebsocketClient.h @@ -61,54 +61,79 @@ class WebsocketClient: protected TcpClient public: WebsocketClient(bool autoDestruct = false) :TcpClient(autoDestruct) {}; virtual ~WebsocketClient() {}; + + /** @brief Set handler for websocket text messages + * @param handler Delegate callback to be run when text message received + */ void setWebSocketMessageHandler(WebSocketClientMessageDelegate handler); - /** @brief Set handler for websocket text messages - * @param handler Delegate callback to be run when text message received - */ - void setWebSocketDisconnectedHandler(WebSocketClientDisconnectDelegate handler); + /** @brief Set handler for websocket disconnection event * @param handler Delegate callback to be run when websocket disconnects */ - void setWebSocketConnectedHandler(WebSocketClientConnectedDelegate handler); + void setWebSocketDisconnectedHandler(WebSocketClientDisconnectDelegate handler); + /** @brief Set handler for websocket connection event * @param handler Delegate callback to be run when websocket connects */ - void setWebSocketBinaryHandler(WebSocketClientBinaryDelegate handler); + void setWebSocketConnectedHandler(WebSocketClientConnectedDelegate handler); + /** @brief Set handler for websocket binary messages * @param handler Delegate callback to be run when binary message received */ - bool connect(String url, uint32_t sslOptions = 0); - /** @brief Connects websocket client to server + void setWebSocketBinaryHandler(WebSocketClientBinaryDelegate handler); + + /** @brief Connects websocket client to server * @param url URL address of websocket server * @param sslOptions Specify the SSL options to be used when calling websocket server over SSL */ - void sendPing(); + bool connect(String url, uint32_t sslOptions = 0); + /** @brief Send websocket ping to server + * + * @param String payload - maximum 255 bytes + * + * @retval bool true if the data can be send, false otherwise */ - void sendPong(); + bool sendPing(const String& payload = ""); + /** @brief Send websocket ping to server + * @param String& payload - maximum 255 bytes + * + * @retval bool true if the data can be send, false otherwise */ - void disconnect(); - /** @brief Disconnects websocket client from server + bool sendPong(const String& payload = ""); + + /** @brief Disconnects websocket client from server */ - void sendMessage(char* msg, uint16_t length); - /** @brief Send text message to websocket server + void disconnect(); + + /** @brief Send text message to websocket server * @param msg Pointer to NULL-terminated string buffer to be send to websocket server * @param length length of the NULL-terminated string buffer */ - void sendMessage(const String& str); - /** @brief Send text message to websocket server + void sendMessage(char* msg, uint16_t length); + + /** @brief Send text message to websocket server * @param C++ String to be send to websocket server */ - void sendBinary(uint8_t* msg, uint16_t length); - /** @brief Send binary message to websocket server + void sendMessage(const String& str); + + /** @brief Send binary message to websocket server * @param msg Pointer to binary-data buffer to be send to websocket server * @param length length of the binary-data buffer */ - wsMode getWSMode(); - /** @brief Get websocket client mode + void sendBinary(uint8_t* msg, uint16_t length); + + /** @brief Send control frame to websocket server + * @param payload C++ String to be send to websocket server + * + */ + bool sendControlFrame(WSFrameType frameType, const String& payload = ""); + + /** @brief Get websocket client mode * @retval Returnt websocket client mode */ + wsMode getWSMode(); #ifdef ENABLE_SSL using TcpClient::addSslOptions; diff --git a/Sming/SmingCore/Network/WebsocketFrame.cpp b/Sming/SmingCore/Network/WebsocketFrame.cpp index bb694ee873..e5538a8ef9 100644 --- a/Sming/SmingCore/Network/WebsocketFrame.cpp +++ b/Sming/SmingCore/Network/WebsocketFrame.cpp @@ -242,3 +242,20 @@ uint8_t WebsocketFrameClass::decodeFrame(uint8_t * buffer, size_t length) } return true; } + +int WebsocketFrameClass::mask(const String& payload, uint32_t key, char *data) +{ + uint8_t pool[4] = {0}; + int pos = 0; + pool[pos++] = (key >> 24) & 0xFF; + pool[pos++] = (key >> 16) & 0xFF; + pool[pos++] = (key >> 8) & 0xFF; + pool[pos++] = (key >> 0) & 0xFF; + + int i; + for (i = 0; i < payload.length(); i++) { + data[i] = (payload[i] ^ pool[i % 4]); + } + + return i; +} diff --git a/Sming/SmingCore/Network/WebsocketFrame.h b/Sming/SmingCore/Network/WebsocketFrame.h index 8265c4f542..1b9e6d1c23 100644 --- a/Sming/SmingCore/Network/WebsocketFrame.h +++ b/Sming/SmingCore/Network/WebsocketFrame.h @@ -54,29 +54,34 @@ class WebsocketFrameClass public: WebsocketFrameClass() {}; virtual ~WebsocketFrameClass(); + + /** @brief Encode given buffer to valid websocket frame + * @param frameType Websocket frame type text or binary + * @param payload Pointer to buffer to be encoded as websocket frame + * @param length Length of buffer to be encoded as websocket frame + * @param mask If true websocket frame will be masked (required for client->server communication) + * @param fin If true produce ordinary websocket frame, not continuation. Currently MUST be true. + * @param headerToPayload If true try to create single buffer message with header and payload, otherwise produce separate header and payload buffers + * @retval Return true on success, false on error + * + * @details if successfully executed, check whether _header is not nullptr and either use _header and _payload or just _payload as websocket frame + * + */ uint8_t encodeFrame(WSFrameType frameType, uint8_t * payload, size_t length, uint8_t mask, uint8_t fin, uint8_t headerToPayload = true); - /** @brief Encode given buffer to valid websocket frame - * @param frameType Websocket frame type text or binary - * @param payload Pointer to buffer to be encoded as websocket frame - * @param length Length of buffer to be encoded as websocket frame - * @param mask If true websocket frame will be masked (required for client->server communication) - * @param fin If true produce ordinary websocket frame, not continuation. Currently MUST be true. - * @param headerToPayload If true try to create single buffer message with header and payload, otherwise produce separate header and payload buffers - * @retval Return true on success, false on error - * - * @details if successfully executed, check whether _header is not nullptr and either use _header and _payload or just _payload as websocket frame - * - */ + /** @brief Decode given buffer containing websocket frame to payload + * @param buffer Pointer to buffer to be decoded as websocket frame + * @param length Length of buffer to be decoded as websocket frame + * @retval Return true on success, false on error + * + * @details if successfully executed, check _frameType to decide what to do with payload pointed by _payload + * + */ uint8_t decodeFrame(uint8_t * buffer, size_t length); - /** @brief Decode given buffer containing websocket frame to payload - * @param buffer Pointer to buffer to be decoded as websocket frame - * @param length Length of buffer to be decoded as websocket frame - * @retval Return true on success, false on error - * - * @details if successfully executed, check _frameType to decide what to do with payload pointed by _payload - * - */ + + + static int mask(const String& payload, uint32_t key, char *data); + protected: uint8_t* _payload = nullptr; // pointer to payload; in encode - will point to proper websocket frame payload, in decode - will point to websocket frame's decoded data size_t _payloadLength = 0;