From a14a58dbd2dd3cf1e55ee8d68b5c4a1a5b662b92 Mon Sep 17 00:00:00 2001 From: Markus Sattler Date: Fri, 22 May 2015 20:35:51 +0200 Subject: [PATCH] message browser to client working --- src/WebSockets.cpp | 202 +++++++++++++++++++++++++++++++++++++++ src/WebSockets.h | 54 +++++++++++ src/WebSocketsServer.cpp | 61 +++++------- src/WebSocketsServer.h | 46 ++------- tests/webSocket.html | 15 +-- 5 files changed, 293 insertions(+), 85 deletions(-) create mode 100644 src/WebSockets.cpp diff --git a/src/WebSockets.cpp b/src/WebSockets.cpp new file mode 100644 index 0000000..03db50a --- /dev/null +++ b/src/WebSockets.cpp @@ -0,0 +1,202 @@ +/** + * @file WebSockets.cpp + * @date 20.05.2015 + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the WebSockets for Arduino. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "WebSockets.h" + +/** + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-------+-+-------------+-------------------------------+ + |F|R|R|R| opcode|M| Payload len | Extended payload length | + |I|S|S|S| (4) |A| (7) | (16/64) | + |N|V|V|V| |S| | (if payload len==126/127) | + | |1|2|3| |K| | | + +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + + | Extended payload length continued, if payload len == 127 | + + - - - - - - - - - - - - - - - +-------------------------------+ + | |Masking-key, if MASK set to 1 | + +-------------------------------+-------------------------------+ + | Masking-key (continued) | Payload Data | + +-------------------------------- - - - - - - - - - - - - - - - + + : Payload Data continued ... : + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + | Payload Data continued ... | + +---------------------------------------------------------------+ + * + */ + +typedef enum { + WSop_continuation = 0x00, ///< %x0 denotes a continuation frame + WSop_text = 0x01, ///< %x1 denotes a text frame + WSop_binary = 0x02, ///< %x2 denotes a binary frame + ///< %x3-7 are reserved for further non-control frames + WSop_close = 0x08, ///< %x8 denotes a connection close + WSop_ping = 0x09, ///< %x9 denotes a ping + WSop_pong = 0x0A ///< %xA denotes a pong + ///< %xB-F are reserved for further control frames +} WSopcode_t; + + + +/** + * handle the WebSocket stream + * @param client WSclient_t * ptr to the client struct + */ +void WebSockets::handleWebsocket(WSclient_t * client) { + + uint8_t buffer[8] = { 0 }; + + bool fin; + bool rsv1; + bool rsv2; + bool rsv3; + WSopcode_t opCode; + bool mask; + size_t payloadLen; + + uint8_t maskKey[4]; + + uint8_t * payload = NULL; + + DEBUG_WEBSOCKETS("[WS-Server][%d][handleWebsocket] ------- read massage frame -------\n", client->num); + + if(!readWait(client, buffer, 2)) { + //timeout + clientDisconnect(client); + return; + } + + // split first 2 bytes in the data + fin = ((buffer[0] >> 7) & 0x01); + rsv1 = ((buffer[0] >> 6) & 0x01); + rsv2 = ((buffer[0] >> 5) & 0x01); + rsv3 = ((buffer[0] >> 4) & 0x01); + opCode = (WSopcode_t)(buffer[0] & 0x0F); + + mask = ((buffer[1] >> 7) & 0x01); + payloadLen = (WSopcode_t)(buffer[1] & 0x7F); + + if(payloadLen == 126) { + if(!readWait(client, buffer, 2)) { + //timeout + clientDisconnect(client); + return; + } + payloadLen = buffer[0] << 8 | buffer[1]; + } else if(payloadLen == 127) { + // read 64bit inteager as Lenght + if(!readWait(client, buffer, 8)) { + //timeout + clientDisconnect(client); + return; + } + + if(buffer[0] != 0 || buffer[1] != 0 || buffer[2] != 0 || buffer[3] != 0) { + // really to big! + payloadLen = 0xFFFFFFFF; + } else { + payloadLen = buffer[4] << 24 | buffer[5] << 16 | buffer[6] << 8 | buffer[7]; + } + } + + DEBUG_WEBSOCKETS("[WS-Server][%d][handleWebsocket] fin: %u rsv1: %u rsv2: %u rsv3 %u opCode: %u\n", client->num, fin, rsv1, rsv2, rsv3, opCode); + DEBUG_WEBSOCKETS("[WS-Server][%d][handleWebsocket] mask: %u payloadLen: %u\n", client->num, mask, payloadLen); + + if(payloadLen > WEBSOCKETS_MAX_DATA_SIZE) { + DEBUG_WEBSOCKETS("[WS-Server][%d][handleWebsocket] payload to big! (%u)\n", client->num, payloadLen); + clientDisconnect(client); + return; + } + + if(mask) { + client->tcp.read(maskKey, 4); + } + + if(payloadLen > 0) { + // if text data we need one more + payload = (uint8_t *) malloc(payloadLen+1); + + if(!payload) { + DEBUG_WEBSOCKETS("[WS-Server][%d][handleWebsocket] to less memory to handle payload %d!\n", client->num, payloadLen); + clientDisconnect(client); + return; + } + + if(!readWait(client, payload, payloadLen)) { + DEBUG_WEBSOCKETS("[WS-Server][%d][handleWebsocket] missing data!\n", client->num); + clientDisconnect(client); + return; + } + + if(mask) { + //decode XOR + for (size_t i = 0; i < payloadLen; i++) { + payload[i] = (payload[i] ^ maskKey[i % 4]); + } + } + + if(opCode == WSop_text) { + payload[payloadLen] = 0x00; + DEBUG_WEBSOCKETS("[WS-Server][%d][handleWebsocket] Text: %s\n", client->num, payload); + } else { + clientDisconnect(client); + } + } + +} + +bool WebSockets::readWait(WSclient_t * client, uint8_t *out, size_t n) { + unsigned long t = millis(); + size_t len; + + while(n > 0) { + if(!client->tcp.connected()) { + DEBUG_WEBSOCKETS("[readWait] Receive not connected - 1!\n"); + return false; + } + while(!client->tcp.available()) { + if(!client->tcp.connected()) { + DEBUG_WEBSOCKETS("[readWait] Receive not connected - 2!\n"); + return false; + } + if((millis() - t) > WEBSOCKETS_TCP_TIMEOUT) { + DEBUG_WEBSOCKETS("[readWait] Receive TIMEOUT!\n"); + return false; + } + delay(0); + } + + len = client->tcp.read((uint8_t*) out, n); + if(len) { + t = millis(); + out += len; + n -= len; + //DEBUG_WEBSOCKETS("Receive %d left %d!\n", len, n); + } else { + //DEBUG_WEBSOCKETS("Receive %d left %d!\n", len, n); + } + delay(0); + } + return true; +} diff --git a/src/WebSockets.h b/src/WebSockets.h index 080728b..580ef4e 100644 --- a/src/WebSockets.h +++ b/src/WebSockets.h @@ -27,13 +27,67 @@ #include +#ifdef ESP8266 +#include +#else +#include +#ifndef UIPETHERNET_H +#include +#include +#endif +#endif + #define DEBUG_WEBSOCKETS(...) Serial1.printf( __VA_ARGS__ ); Serial1.flush() #ifndef DEBUG_WEBSOCKETS #define DEBUG_WEBSOCKETS(...) #endif +#define WEBSOCKETS_MAX_DATA_SIZE (15*1024) +#define WEBSOCKETS_TCP_TIMEOUT (1000) +typedef enum { + WSC_NOT_CONNECTED, + WSC_HEADER, + WSC_CONNECTED +} WSclientsStatus_t; +typedef struct { + uint8_t num; ///< connection number + + WSclientsStatus_t status; +#ifdef ESP8266 + WiFiClient tcp; +#else +#ifdef UIPETHERNET_H + UIPClient tcp; +#else + EthernetClient tcp; +#endif +#endif + String cUrl; ///< http url + + bool cIsUpgrade; ///< Connection == Upgrade + bool cIsWebsocket; ///< Upgrade == websocket + + String cKey; ///< client Sec-WebSocket-Key + String cProtocol; ///< client Sec-WebSocket-Protocol + String cExtensions; ///< client Sec-WebSocket-Extensions + int cVersion; ///< client Sec-WebSocket-Version + + String sKey; ///< server Sec-WebSocket-Key + +} WSclient_t; + +class WebSockets { + protected: + + void handleWebsocket(WSclient_t * client); + + virtual void clientDisconnect(WSclient_t * client); + virtual bool clientIsConnected(WSclient_t * client); + + bool readWait(WSclient_t * client, uint8_t *out, size_t n); +}; #endif /* WEBSOCKETS_H_ */ diff --git a/src/WebSocketsServer.cpp b/src/WebSocketsServer.cpp index 212a891..feb5936 100644 --- a/src/WebSocketsServer.cpp +++ b/src/WebSocketsServer.cpp @@ -35,12 +35,13 @@ WebSocketsServer::WebSocketsServer(uint16_t port) { _port = port; _server = new WiFiServer(port); } + WebSocketsServer::~WebSocketsServer() { // todo how to close server? } void WebSocketsServer::begin(void) { - WSclients_t * client; + WSclient_t * client; // init client storage for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) { @@ -76,9 +77,9 @@ void WebSocketsServer::loop(void) { /** * Disconnect an client - * @param num uint8_t index of _clients array + * @param client WSclients_t * ptr to the client struct */ -void WebSocketsServer::clientDisconnect(WSclients_t * client) { +void WebSocketsServer::clientDisconnect(WSclient_t * client) { if(client->tcp) { client->tcp.stop(); @@ -101,10 +102,10 @@ void WebSocketsServer::clientDisconnect(WSclients_t * client) { /** * get client state - * @param num uint8_t index of _clients array + * @param client WSclients_t * ptr to the client struct * @return true = conneted */ -bool WebSocketsServer::clientIsConnected(WSclients_t * client) { +bool WebSocketsServer::clientIsConnected(WSclient_t * client) { if(client->status != WSC_NOT_CONNECTED && client->tcp.connected()) { return true; @@ -121,7 +122,7 @@ bool WebSocketsServer::clientIsConnected(WSclients_t * client) { * Handle incomming Connection Request */ void WebSocketsServer::handleNewClients(void) { - WSclients_t * client; + WSclient_t * client; while(_server->hasClient()) { bool ok = false; // search free list entry for client @@ -135,7 +136,7 @@ void WebSocketsServer::handleNewClients(void) { client->tcp = _server->available(); client->tcp.setNoDelay(true); // set Timeout for readBytesUntil and readStringUntil - client->tcp.setTimeout(1000); + client->tcp.setTimeout(WEBSOCKETS_TCP_TIMEOUT); client->status = WSC_HEADER; IPAddress ip = client->tcp.remoteIP(); @@ -162,7 +163,7 @@ void WebSocketsServer::handleNewClients(void) { */ void WebSocketsServer::handleClientData(void) { - WSclients_t * client; + WSclient_t * client; for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) { client = &_clients[i]; if(clientIsConnected(client)) { @@ -174,6 +175,7 @@ void WebSocketsServer::handleClientData(void) { handleHeader(client); break; case WSC_CONNECTED: + WebSockets::handleWebsocket(client); break; default: clientDisconnect(client); @@ -185,30 +187,11 @@ void WebSocketsServer::handleClientData(void) { } } -/* - [WS-Server][0] new client from 192.168.2.23 - [WS-Server][0][handleHeader] RX: GET /test HTTP/1.1 - [WS-Server][0][handleHeader] RX: Host: 10.11.2.1:81 - [WS-Server][0][handleHeader] RX: Connection: Upgrade - [WS-Server][0][handleHeader] RX: Pragma: no-cache - [WS-Server][0][handleHeader] RX: Cache-Control: no-cache - [WS-Server][0][handleHeader] RX: Upgrade: websocket - [WS-Server][0][handleHeader] RX: Origin: null - [WS-Server][0][handleHeader] RX: Sec-WebSocket-Version: 13 - [WS-Server][0][handleHeader] RX: DNT: 1 - [WS-Server][0][handleHeader] RX: User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36 - [WS-Server][0][handleHeader] RX: Accept-Encoding: gzip, deflate, sdch - [WS-Server][0][handleHeader] RX: Accept-Language: de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4 - [WS-Server][0][handleHeader] RX: Sec-WebSocket-Key: FRddfIKSePnzAzKOqUGI/Q== - [WS-Server][0][handleHeader] RX: Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits - [WS-Server][0][handleHeader] RX: Sec-WebSocket-Protocol: esp8266, test - */ - /** - * handle the WebSocket headder reading - * @param num uint8_t index of _clients array + * handle the WebSocket header reading + * @param client WSclients_t * ptr to the client struct */ -void WebSocketsServer::handleHeader(WSclients_t * client) { +void WebSocketsServer::handleHeader(WSclient_t * client) { String headerLine = client->tcp.readStringUntil('\n'); headerLine.trim(); // remove \r @@ -268,7 +251,6 @@ void WebSocketsServer::handleHeader(WSclients_t * client) { DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] Websocket connection incomming.\n", client->num); - // generate Sec-WebSocket-Accept key uint8_t sha1HashBin[20] = {0}; sha1(client->cKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", &sha1HashBin[0]); @@ -295,11 +277,15 @@ void WebSocketsServer::handleHeader(WSclients_t * client) { "Sec-WebSocket-Version: 13\r\n" "Sec-WebSocket-Accept: "); client->tcp.write(client->sKey.c_str(), client->sKey.length()); - client->tcp.write("\r\n" - "Sec-WebSocket-Protocol: "); - client->tcp.write(client->cProtocol.c_str(), client->cProtocol.length()); // support any protocol for now - client->tcp.write("\r\n" - "\r\n"); + client->tcp.write("\r\n"); + + if(client->cProtocol.length() > 0) { + // todo add api to set Protocol of Server + client->tcp.write("Sec-WebSocket-Protocol: esp8266\r\n"); + } + + // header end + client->tcp.write("\r\n"); } else { DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] no Websocket connection close.\n", client->num); @@ -315,3 +301,6 @@ void WebSocketsServer::handleHeader(WSclients_t * client) { } } } + + + diff --git a/src/WebSocketsServer.h b/src/WebSocketsServer.h index 674660f..43a8289 100644 --- a/src/WebSocketsServer.h +++ b/src/WebSocketsServer.h @@ -37,44 +37,12 @@ #endif #endif - +#include "WebSockets.h" #define WEBSOCKETS_SERVER_CLIENT_MAX (5) -typedef enum { - WSC_NOT_CONNECTED, - WSC_HEADER, - WSC_CONNECTED -} WSclientsStatus_t; -typedef struct { - uint8_t num; ///< connection number - - WSclientsStatus_t status; -#ifdef ESP8266 - WiFiClient tcp; -#else -#ifdef UIPETHERNET_H - UIPClient tcp; -#else - EthernetClient tcp; -#endif -#endif - String cUrl; ///< http url - - bool cIsUpgrade; ///< Connection == Upgrade - bool cIsWebsocket; ///< Upgrade == websocket - - String cKey; ///< client Sec-WebSocket-Key - String cProtocol; ///< client Sec-WebSocket-Protocol - String cExtensions; ///< client Sec-WebSocket-Extensions - int cVersion; ///< client Sec-WebSocket-Version - - String sKey; ///< server Sec-WebSocket-Key - -} WSclients_t; - -class WebSocketsServer { +class WebSocketsServer: private WebSockets { public: WebSocketsServer(uint16_t port); ~WebSocketsServer(void); @@ -95,17 +63,17 @@ private: #endif #endif - WSclients_t _clients[WEBSOCKETS_SERVER_CLIENT_MAX]; + WSclient_t _clients[WEBSOCKETS_SERVER_CLIENT_MAX]; - void clientDisconnect(WSclients_t * client); - bool clientIsConnected(WSclients_t * client); + void clientDisconnect(WSclient_t * client); + bool clientIsConnected(WSclient_t * client); void handleNewClients(void); void handleClientData(void); - void handleHeader(WSclients_t * client); - + void handleHeader(WSclient_t * client); + void handleWebsocket(WSclient_t * client); }; diff --git a/tests/webSocket.html b/tests/webSocket.html index 452af13..baeaad0 100644 --- a/tests/webSocket.html +++ b/tests/webSocket.html @@ -2,26 +2,21 @@