diff --git a/examples/ESP32_AsyncFSBrowser/ESP32_AsyncFSBrowser.ino b/examples/ESP32_AsyncFSBrowser/ESP32_AsyncFSBrowser.ino index b514e29..4726664 100644 --- a/examples/ESP32_AsyncFSBrowser/ESP32_AsyncFSBrowser.ino +++ b/examples/ESP32_AsyncFSBrowser/ESP32_AsyncFSBrowser.ino @@ -110,6 +110,70 @@ class SPIFFSEditor: public AsyncWebHandler { // SKETCH BEGIN AsyncWebServer server(80); +AsyncWebSocket ws("/ws"); + +void onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){ + if(type == WS_EVT_CONNECT){ + os_printf("ws[%s][%u] connect\n", server->url(), client->id()); + client->printf("Hello Client %u :)", client->id()); + client->ping(); + } else if(type == WS_EVT_DISCONNECT){ + os_printf("ws[%s][%u] disconnect: %u\n", server->url(), client->id()); + } else if(type == WS_EVT_ERROR){ + os_printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data); + } else if(type == WS_EVT_PONG){ + os_printf("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len)?(char*)data:""); + } else if(type == WS_EVT_DATA){ + AwsFrameInfo * info = (AwsFrameInfo*)arg; + if(info->final && info->index == 0 && info->len == len){ + //the whole message is in a single frame and we got all of it's data + os_printf("ws[%s][%u] %s-message[%llu]: ", server->url(), client->id(), (info->opcode == WS_TEXT)?"text":"binary", info->len); + if(info->opcode == WS_TEXT){ + data[len] = 0; + os_printf("%s\n", (char*)data); + } else { + for(size_t i=0; i < info->len; i++){ + os_printf("%02x ", data[i]); + } + os_printf("\n"); + } + if(info->opcode == WS_TEXT) + client->text("I got your text message"); + else + client->binary("I got your binary message"); + } else { + //message is comprised of multiple frames or the frame is split into multiple packets + if(info->index == 0){ + if(info->num == 0) + os_printf("ws[%s][%u] %s-message start\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary"); + os_printf("ws[%s][%u] frame[%u] start[%llu]\n", server->url(), client->id(), info->num, info->len); + } + + os_printf("ws[%s][%u] frame[%u] %s[%llu - %llu]: ", server->url(), client->id(), info->num, (info->message_opcode == WS_TEXT)?"text":"binary", info->index, info->index + len); + if(info->message_opcode == WS_TEXT){ + data[len] = 0; + os_printf("%s\n", (char*)data); + } else { + for(size_t i=0; i < len; i++){ + os_printf("%02x ", data[i]); + } + os_printf("\n"); + } + + if((info->index + len) == info->len){ + os_printf("ws[%s][%u] frame[%u] end[%llu]\n", server->url(), client->id(), info->num, info->len); + if(info->final){ + os_printf("ws[%s][%u] %s-message end\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary"); + if(info->message_opcode == WS_TEXT) + client->text("I got your text message"); + else + client->binary("I got your binary message"); + } + } + } + } +} + const char* ssid = "**********"; const char* password = "************"; const char* http_username = "admin"; @@ -124,6 +188,9 @@ void setup(){ if(WiFi.waitForConnectResult() != WL_CONNECTED){ Serial.printf("WiFi Failed!\n"); } + + ws.onEvent(onEvent); + server.addHandler(&ws); server.serveStatic("/fs", SPIFFS, "/"); diff --git a/examples/ESP32_AsyncFSBrowser/data/index.htm b/examples/ESP32_AsyncFSBrowser/data/index.htm index 7523b22..b9aea57 100644 --- a/examples/ESP32_AsyncFSBrowser/data/index.htm +++ b/examples/ESP32_AsyncFSBrowser/data/index.htm @@ -17,350 +17,94 @@ --> - - - ESP Monitor - - - - - - -
-
-
- - - - -
-
+ + + +

+    
+ $
-
-
-
-
-
-
-
- -
-
-
-
-
- - - - - - - - - - - - - -
   SSIDBSSIDChannelSecureHiddenRSSI
-
- - + - diff --git a/examples/ESP_AsyncFSBrowser/ESP_AsyncFSBrowser.ino b/examples/ESP_AsyncFSBrowser/ESP_AsyncFSBrowser.ino index 0594d20..a670185 100644 --- a/examples/ESP_AsyncFSBrowser/ESP_AsyncFSBrowser.ino +++ b/examples/ESP_AsyncFSBrowser/ESP_AsyncFSBrowser.ino @@ -112,6 +112,71 @@ class SPIFFSEditor: public AsyncWebHandler { // SKETCH BEGIN AsyncWebServer server(80); +AsyncWebSocket ws("/ws"); + +void onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){ + if(type == WS_EVT_CONNECT){ + os_printf("ws[%s][%u] connect\n", server->url(), client->id()); + client->printf("Hello Client %u :)", client->id()); + client->ping(); + } else if(type == WS_EVT_DISCONNECT){ + os_printf("ws[%s][%u] disconnect: %u\n", server->url(), client->id()); + } else if(type == WS_EVT_ERROR){ + os_printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data); + } else if(type == WS_EVT_PONG){ + os_printf("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len)?(char*)data:""); + } else if(type == WS_EVT_DATA){ + AwsFrameInfo * info = (AwsFrameInfo*)arg; + if(info->final && info->index == 0 && info->len == len){ + //the whole message is in a single frame and we got all of it's data + os_printf("ws[%s][%u] %s-message[%llu]: ", server->url(), client->id(), (info->opcode == WS_TEXT)?"text":"binary", info->len); + if(info->opcode == WS_TEXT){ + data[len] = 0; + os_printf("%s\n", (char*)data); + } else { + for(size_t i=0; i < info->len; i++){ + os_printf("%02x ", data[i]); + } + os_printf("\n"); + } + if(info->opcode == WS_TEXT) + client->text("I got your text message"); + else + client->binary("I got your binary message"); + } else { + //message is comprised of multiple frames or the frame is split into multiple packets + if(info->index == 0){ + if(info->num == 0) + os_printf("ws[%s][%u] %s-message start\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary"); + os_printf("ws[%s][%u] frame[%u] start[%llu]\n", server->url(), client->id(), info->num, info->len); + } + + os_printf("ws[%s][%u] frame[%u] %s[%llu - %llu]: ", server->url(), client->id(), info->num, (info->message_opcode == WS_TEXT)?"text":"binary", info->index, info->index + len); + if(info->message_opcode == WS_TEXT){ + data[len] = 0; + os_printf("%s\n", (char*)data); + } else { + for(size_t i=0; i < len; i++){ + os_printf("%02x ", data[i]); + } + os_printf("\n"); + } + + if((info->index + len) == info->len){ + os_printf("ws[%s][%u] frame[%u] end[%llu]\n", server->url(), client->id(), info->num, info->len); + if(info->final){ + os_printf("ws[%s][%u] %s-message end\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary"); + if(info->message_opcode == WS_TEXT) + client->text("I got your text message"); + else + client->binary("I got your binary message"); + } + } + } + } +} + + const char* ssid = "**********"; const char* password = "************"; const char* http_username = "admin"; @@ -147,6 +212,9 @@ void setup(){ ArduinoOTA.begin(); //Serial.printf("format start\n"); SPIFFS.format(); Serial.printf("format end\n"); + ws.onEvent(onEvent); + server.addHandler(&ws); + server.serveStatic("/fs", SPIFFS, "/"); server.on("/heap", HTTP_GET, [](AsyncWebServerRequest *request){ diff --git a/examples/ESP_AsyncFSBrowser/data/index.htm b/examples/ESP_AsyncFSBrowser/data/index.htm index 7523b22..b9aea57 100644 --- a/examples/ESP_AsyncFSBrowser/data/index.htm +++ b/examples/ESP_AsyncFSBrowser/data/index.htm @@ -17,350 +17,94 @@ --> - - - ESP Monitor - - - - - - -
-
-
- - - - -
-
+ + + +

+    
+ $
-
-
-
-
-
-
-
- -
-
-
-
-
- - - - - - - - - - - - - -
   SSIDBSSIDChannelSecureHiddenRSSI
-
- - + - diff --git a/src/AsyncWebSocket.cpp b/src/AsyncWebSocket.cpp new file mode 100644 index 0000000..c32ca86 --- /dev/null +++ b/src/AsyncWebSocket.cpp @@ -0,0 +1,860 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + 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 "Arduino.h" +#include "AsyncWebSocket.h" + +#include + +#ifndef ESP8266 +extern "C" { +typedef struct { + uint32_t state[5]; + uint32_t count[2]; + unsigned char buffer[64]; +} SHA1_CTX; + +void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]); +void SHA1Init(SHA1_CTX* context); +void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len); +void SHA1Final(unsigned char digest[20], SHA1_CTX* context); +} +#else +#include +#endif + + +size_t webSocketSendFrameWindow(AsyncClient *client){ + if(!client->canSend()) + return 0; + size_t space = client->space(); + if(space < 9) + return 0; + return space - 8; +} + +size_t webSocketSendFrame(AsyncClient *client, bool final, uint8_t opcode, bool mask, uint8_t *data, size_t len){ + if(!client->canSend()) + return 0; + size_t space = client->space(); + if(space < 2) + return 0; + uint8_t mbuf[4] = {0,0,0,0}; + uint8_t headLen = 2; + if(len && mask){ + headLen += 4; + mbuf[0] = rand() % 0xFF; + mbuf[1] = rand() % 0xFF; + mbuf[2] = rand() % 0xFF; + mbuf[3] = rand() % 0xFF; + os_printf("frame mask: 0x%02x 0x%02x 0x%02x 0x%02x\n", mbuf[0], mbuf[1], mbuf[2], mbuf[3]); + } + if(len > 125) + headLen += 2; + if(space < headLen) + return 0; + space -= headLen; + + if(len > space) len = space; + + uint8_t *buf = (uint8_t*)malloc(headLen); + if(buf == NULL){ + os_printf("could not malloc %u bytes for frame header\n", headLen); + return 0; + } + + buf[0] = opcode & 0x0F; + if(final) + buf[0] |= 0x80; + if(len < 126) + buf[1] = len & 0x7F; + else { + buf[1] = 126; + buf[2] = (uint8_t)((len >> 8) & 0xFF); + buf[3] = (uint8_t)(len & 0xFF); + } + if(len && mask){ + buf[1] |= 0x80; + memcpy(buf + (headLen - 4), mbuf, 4); + } + if(client->add((const char *)buf, headLen) != headLen){ + os_printf("error adding %lu header bytes\n", headLen); + free(buf); + return 0; + } + free(buf); + + if(len){ + if(len && mask){ + os_printf("masking the payload: %lu\n", len); + size_t i; + for(i=0;iadd((const char *)data, len) != len){ + os_printf("error adding %lu data bytes\n", len); + return 0; + } + } + if(!client->send()){ + os_printf("error sending frame: %lu\n", headLen+len); + return 0; + } + return len; +} + + + + +/* + * Control Frame + */ + +class AsyncWebSocketControl { + private: + uint8_t _opcode; + uint8_t *_data; + size_t _len; + bool _mask; + bool _finished; + public: + AsyncWebSocketControl * next; + AsyncWebSocketControl(uint8_t opcode, uint8_t *data=NULL, size_t len=0, bool mask=false) + :_opcode(opcode) + ,_len(len) + ,_mask(len && mask) + ,_finished(false) + ,next(NULL){ + if(data == NULL) + _len = 0; + if(_len){ + if(_len > 125) + _len = 125; + _data = (uint8_t*)malloc(_len); + if(_data == NULL) + _len = 0; + else memcpy(_data, data, len); + } else _data = NULL; + } + ~AsyncWebSocketControl(){ + if(_data != NULL) + free(_data); + } + bool finished(){ return _finished; } + uint8_t opcode(){ return _opcode; } + uint8_t len(){ return _len + 2; } + size_t send(AsyncClient *client){ + _finished = true; + return webSocketSendFrame(client, true, _opcode & 0x0F, _mask, _data, _len); + } +}; + +/* + * Basic Buffered Message + */ + +class AsyncWebSocketBasicMessage: public AsyncWebSocketMessage { + private: + uint8_t * _data; + size_t _len; + size_t _sent; + size_t _ack; + size_t _acked; + + public: + AsyncWebSocketBasicMessage(const char * data, size_t len, uint8_t opcode=WS_TEXT, bool mask=false) + :_len(len) + ,_sent(0) + ,_ack(0) + ,_acked(0) + { + _opcode = opcode & 0x07; + _mask = mask; + _data = (uint8_t*)malloc(_len+1); + if(_data == NULL){ + _len = 0; + _status = WS_MSG_ERROR; + } else { + _status = WS_MSG_SENDING; + memcpy(_data, data, _len); + _data[_len] = 0; + } + } + virtual ~AsyncWebSocketBasicMessage(){ + if(_data != NULL) + free(_data); + } + virtual bool betweenFrames(){ return _acked == _ack; } + virtual void ack(size_t len, uint32_t time){ + _acked += len; + if(_sent == _len && _acked == _ack){ + _status = WS_MSG_SENT; + } + } + virtual size_t send(AsyncClient *client){ + if(_status != WS_MSG_SENDING) + return 0; + if(_acked < _ack){ + return 0; + } + if(_sent == _len){ + if(_acked == _ack) + _status = WS_MSG_SENT; + return 0; + } + size_t window = webSocketSendFrameWindow(client); + size_t toSend = _len - _sent; + if(window < toSend) toSend = window; + bool final = ((toSend + _sent) == _len); + size_t sent = webSocketSendFrame(client, final, (_sent == 0)?_opcode:WS_CONTINUATION, _mask, (uint8_t*)(_data+_sent), toSend); + _sent += sent; + uint8_t headLen = ((sent < 126)?2:4)+(_mask*4); + _ack += sent + headLen; + return sent; + } +}; + + + + + +/* + * Async WebSocket Client + */ + +AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server){ + _client = request->client(); + _server = server; + _clientId = _server->_getNextId(); + _status = WS_CONNECTED; + _controlQueue = NULL; + _messageQueue = NULL; + _pstate = 0; + next = NULL; + delete request; + _client->onError([](void *r, AsyncClient* c, int8_t error){ ((AsyncWebSocketClient*)(r))->_onError(error); }, this); + _client->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ ((AsyncWebSocketClient*)(r))->_onAck(len, time); }, this); + _client->onDisconnect([](void *r, AsyncClient* c){ ((AsyncWebSocketClient*)(r))->_onDisconnect(); }, this); + _client->onTimeout([](void *r, AsyncClient* c, uint32_t time){ ((AsyncWebSocketClient*)(r))->_onTimeout(time); }, this); + _client->onData([](void *r, AsyncClient* c, void *buf, size_t len){ ((AsyncWebSocketClient*)(r))->_onData(buf, len); }, this); + _client->onPoll([](void *r, AsyncClient* c){ ((AsyncWebSocketClient*)(r))->_onPoll(); }, this); + _server->_addClient(this); + _server->_handleEvent(this, WS_EVT_CONNECT, NULL, NULL, 0); +} + +AsyncWebSocketClient::~AsyncWebSocketClient(){ + while(_messageQueue != NULL){ + AsyncWebSocketMessage * m = _messageQueue; + _messageQueue = _messageQueue->next; + delete(m); + } + while(_controlQueue != NULL){ + AsyncWebSocketControl * c = _controlQueue; + _controlQueue = _controlQueue->next; + delete(c); + } + _server->_handleEvent(this, WS_EVT_DISCONNECT, NULL, NULL, 0); +} + +void AsyncWebSocketClient::_onAck(size_t len, uint32_t time){ + if(_controlQueue != NULL){ + AsyncWebSocketControl *controlMessage = _controlQueue; + if(controlMessage->finished()){ + _controlQueue = _controlQueue->next; + len -= controlMessage->len(); + if(_status == WS_DISCONNECTING && controlMessage->opcode() == WS_DISCONNECT){ + delete controlMessage; + _status = WS_DISCONNECTED; + _client->close(true); + return; + } + delete controlMessage; + } + } + if(len && _messageQueue != NULL){ + _messageQueue->ack(len, time); + } + _runQueue(); +} + +void AsyncWebSocketClient::_onPoll(){ + if(_client->canSend() && (_controlQueue != NULL || _messageQueue != NULL)){ + _runQueue(); + } + //ToDo: check for last activity here and maybe ping? + //ping(); +} + +void AsyncWebSocketClient::_runQueue(){ + while(_messageQueue != NULL && _messageQueue->finished()){ + AsyncWebSocketMessage * m = _messageQueue; + _messageQueue = _messageQueue->next; + delete(m); + } + + if(_controlQueue != NULL && (_messageQueue == NULL || _messageQueue->betweenFrames()) && webSocketSendFrameWindow(_client) > (size_t)(_controlQueue->len() - 1)){ + AsyncWebSocketControl *control = _controlQueue; + control->send(_client); + } else if(_messageQueue != NULL && _messageQueue->betweenFrames() && webSocketSendFrameWindow(_client)){ + _messageQueue->send(_client); + } +} + +void AsyncWebSocketClient::_queueMessage(AsyncWebSocketMessage *dataMessage){ + if(dataMessage == NULL) + return; + if(_status != WS_CONNECTED){ + delete dataMessage; + return; + } + if(_messageQueue == NULL){ + _messageQueue = dataMessage; + } else { + AsyncWebSocketMessage * m = _messageQueue; + while(m->next != NULL) m = m->next; + m->next = dataMessage; + } + if(_client->canSend()) + _runQueue(); +} + +void AsyncWebSocketClient::_queueControl(AsyncWebSocketControl *controlMessage){ + if(controlMessage == NULL) + return; + if(_controlQueue == NULL){ + _controlQueue = controlMessage; + } else { + AsyncWebSocketControl * m = _controlQueue; + while(m->next != NULL) m = m->next; + m->next = controlMessage; + } + if(_client->canSend()) + _runQueue(); +} + +void AsyncWebSocketClient::close(uint16_t code, const char * message){ + if(_status != WS_CONNECTED) + return; + if(code){ + uint8_t packetLen = 2; + if(message != NULL){ + size_t mlen = strlen(message); + if(mlen > 123) mlen = 123; + packetLen += mlen; + } + char * buf = (char*)malloc(packetLen); + if(buf != NULL){ + buf[0] = (uint8_t)(code >> 8); + buf[1] = (uint8_t)(code & 0xFF); + if(message != NULL){ + memcpy(buf+2, message, packetLen -2); + } + _queueControl(new AsyncWebSocketControl(WS_DISCONNECT,(uint8_t*)buf,packetLen)); + free(buf); + return; + } + } + _queueControl(new AsyncWebSocketControl(WS_DISCONNECT)); +} + +void AsyncWebSocketClient::ping(uint8_t *data, size_t len){ + if(_status == WS_CONNECTED) + _queueControl(new AsyncWebSocketControl(WS_PING, data, len)); +} + +void AsyncWebSocketClient::_onError(int8_t){ + +} + +void AsyncWebSocketClient::_onTimeout(uint32_t time){ + os_printf("_onTimeout: %u, state: %s\n", time, _client->stateToString()); + _client->close(); +} + +void AsyncWebSocketClient::_onDisconnect(){ + _client->free(); + delete _client; + _server->_handleDisconnect(this); +} + +void AsyncWebSocketClient::_onData(void *buf, size_t plen){ + uint8_t *fdata = (uint8_t*)buf; + uint8_t * data = fdata; + if(!_pstate){ + _pinfo.index = 0; + _pinfo.final = (fdata[0] & 0x80) != 0; + _pinfo.opcode = fdata[0] & 0x0F; + _pinfo.masked = (fdata[1] & 0x80) != 0; + _pinfo.len = fdata[1] & 0x7F; + data += 2; + plen = plen - 2; + if(_pinfo.len == 126){ + _pinfo.len = fdata[3] | (uint16_t)(fdata[2]) << 8; + data += 2; + plen = plen - 2; + } else if(_pinfo.len == 127){ + _pinfo.len = fdata[9] | (uint16_t)(fdata[8]) << 8 | (uint32_t)(fdata[7]) << 16 | (uint32_t)(fdata[6]) << 24 | (uint64_t)(fdata[5]) << 32 | (uint64_t)(fdata[4]) << 40 | (uint64_t)(fdata[3]) << 48 | (uint64_t)(fdata[2]) << 56; + data += 8; + plen = plen - 8; + } + + if(_pinfo.masked){ + memcpy(_pinfo.mask, data, 4); + data += 4; + plen = plen - 4; + size_t i; + for(i=0;i_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, (uint8_t*)data, plen); + + _pinfo.index += plen; + } else if((plen + _pinfo.index) == _pinfo.len){ + _pstate = 0; + if(_pinfo.opcode == WS_DISCONNECT){ + if(plen){ + uint16_t reasonCode = (uint16_t)(data[0] << 8) + data[1]; + char * reasonString = (char*)(data+2); + if(reasonCode > 1001){ + _server->_handleEvent(this, WS_EVT_ERROR, (void *)&reasonCode, (uint8_t*)reasonString, strlen(reasonString)); + } + } + if(_status == WS_DISCONNECTING){ + _status = WS_DISCONNECTED; + _client->close(true); + } else { + _status = WS_DISCONNECTING; + _queueControl(new AsyncWebSocketControl(WS_DISCONNECT, data, plen)); + } + } else if(_pinfo.opcode == WS_PING){ + _queueControl(new AsyncWebSocketControl(WS_PONG, data, plen)); + } else if(_pinfo.opcode == WS_PONG){ + _server->_handleEvent(this, WS_EVT_PONG, NULL, (uint8_t*)data, plen); + } else if(_pinfo.opcode < 8){//continuation or text/binary frame + _server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, (uint8_t*)data, plen); + } + } else { + os_printf("frame error: len: %u, index: %llu, total: %llu\n", plen, _pinfo.index, _pinfo.len); + //what should we do? + } +} + +size_t AsyncWebSocketClient::printf(const char *format, ...) { + va_list arg; + va_start(arg, format); +#ifdef ESP8266 + //ToDo: figure out a way around this + size_t len = 1440; +#else + size_t len = vsnprintf(NULL, 0, format, arg); +#endif + char * msg = (char*)malloc(len+1); + if(msg == NULL){ + va_end(arg); + return 0; + } + len = vsnprintf(msg, len, format, arg); + msg[len] = 0; + text(msg); + va_end(arg); + free(msg); + return len; +} + +void AsyncWebSocketClient::text(const char * message, size_t len){ + _queueMessage(new AsyncWebSocketBasicMessage(message, len)); +} +void AsyncWebSocketClient::text(const char * message){ + text(message, strlen(message)); +} +void AsyncWebSocketClient::text(uint8_t * message, size_t len){ + text((const char *)message, len); +} +void AsyncWebSocketClient::text(char * message){ + text(message, strlen(message)); +} +void AsyncWebSocketClient::text(String &message){ + text(message.c_str(), message.length()); +} + +void AsyncWebSocketClient::binary(const char * message, size_t len){ + _queueMessage(new AsyncWebSocketBasicMessage(message, len, WS_BINARY)); +} +void AsyncWebSocketClient::binary(const char * message){ + binary(message, strlen(message)); +} +void AsyncWebSocketClient::binary(uint8_t * message, size_t len){ + binary((const char *)message, len); +} +void AsyncWebSocketClient::binary(char * message){ + binary(message, strlen(message)); +} +void AsyncWebSocketClient::binary(String &message){ + binary(message.c_str(), message.length()); +} + + + + +/* + * Async Web Socket - Each separate socket location + */ + +AsyncWebSocket::AsyncWebSocket(String url) + :_url(url) + ,_clients(NULL) + ,_cNextId(0) +{ + _eventHandler = NULL; +} + +AsyncWebSocket::~AsyncWebSocket(){} + +void AsyncWebSocket::_handleEvent(AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){ + if(_eventHandler != NULL){ + _eventHandler(this, client, type, arg, data, len); + } +} + +void AsyncWebSocket::_addClient(AsyncWebSocketClient * client){ + if(_clients == NULL){ + _clients = client; + return; + } + AsyncWebSocketClient * c = _clients; + while(c->next != NULL) c = c->next; + c->next = client; +} + +void AsyncWebSocket::_handleDisconnect(AsyncWebSocketClient * client){ + if(_clients == NULL){ + os_printf("we have no clients to disconnect!"); + return; + } + if(_clients->id() == client->id()){ + _clients = client->next; + delete client; + return; + } + AsyncWebSocketClient * c = _clients; + while(c->next != NULL && c->next->id() != client->id()) c = c->next; + if(c->next == NULL){ + os_printf("we could not find client [%u] to disconnect!", client->id()); + return; + } + c->next = client->next; + delete client; +} + +size_t AsyncWebSocket::count(){ + size_t i = 0; + AsyncWebSocketClient * c = _clients; + while(c != NULL){ + if(c->status() == WS_CONNECTED) + i++; + c = c->next; + } + return i; +} + +AsyncWebSocketClient * AsyncWebSocket::client(uint32_t id){ + AsyncWebSocketClient * c = _clients; + while(c != NULL && c->id() != id) + c = c->next; + if(c != NULL && c->status() == WS_CONNECTED) + return c; + return NULL; +} + + +void AsyncWebSocket::close(uint32_t id, uint16_t code, const char * message){ + AsyncWebSocketClient * c = client(id); + if(c != NULL) + c->close(code, message); +} + +void AsyncWebSocket::closeAll(uint16_t code, const char * message){ + AsyncWebSocketClient * c = _clients; + while(c != NULL){ + if(c->status() == WS_CONNECTED) + c->close(code, message); + c = c->next; + } +} + +void AsyncWebSocket::ping(uint32_t id, uint8_t *data, size_t len){ + AsyncWebSocketClient * c = client(id); + if(c != NULL) + c->ping(data, len); +} + +void AsyncWebSocket::pingAll(uint8_t *data, size_t len){ + AsyncWebSocketClient * c = _clients; + while(c != NULL){ + if(c->status() == WS_CONNECTED) + c->ping(data, len); + c = c->next; + } + +} + +void AsyncWebSocket::text(uint32_t id, const char * message, size_t len){ + AsyncWebSocketClient * c = client(id); + if(c != NULL) + c->text(message, len); +} + +void AsyncWebSocket::textAll(const char * message, size_t len){ + AsyncWebSocketClient * c = _clients; + while(c != NULL){ + if(c->status() == WS_CONNECTED) + c->text(message, len); + c = c->next; + } +} + +void AsyncWebSocket::binary(uint32_t id, const char * message, size_t len){ + AsyncWebSocketClient * c = client(id); + if(c != NULL) + c->binary(message, len); +} + +void AsyncWebSocket::binaryAll(const char * message, size_t len){ + AsyncWebSocketClient * c = _clients; + while(c != NULL){ + if(c->status() == WS_CONNECTED) + c->binary(message, len); + c = c->next; + } +} + +void AsyncWebSocket::message(uint32_t id, AsyncWebSocketMessage *message){ + AsyncWebSocketClient * c = client(id); + if(c != NULL) + c->message(message); +} + +void AsyncWebSocket::messageAll(AsyncWebSocketMessage *message){ + AsyncWebSocketClient * c = _clients; + while(c != NULL){ + if(c->status() == WS_CONNECTED) + c->message(message); + c = c->next; + } +} + +size_t AsyncWebSocket::printf(uint32_t id, const char *format, ...){ + AsyncWebSocketClient * c = client(id); + if(c != NULL){ + va_list arg; + va_start(arg, format); + size_t len = c->printf(format, arg); + va_end(arg); + return len; + } + return 0; +} + +size_t AsyncWebSocket::printfAll(const char *format, ...) { + va_list arg; + va_start(arg, format); +#ifdef ESP8266 + //ToDo: figure out a way around this + size_t len = 1440; +#else + size_t len = vsnprintf(NULL, 0, format, arg); +#endif + char * msg = (char*)malloc(len+1); + if(msg == NULL){ + va_end(arg); + return 0; + } + len = vsnprintf(msg, len, format, arg); + msg[len] = 0; + textAll(msg); + va_end(arg); + free(msg); + return len; +} + +void AsyncWebSocket::text(uint32_t id, const char * message){ + text(id, message, strlen(message)); +} +void AsyncWebSocket::text(uint32_t id, uint8_t * message, size_t len){ + text(id, (const char *)message, len); +} +void AsyncWebSocket::text(uint32_t id, char * message){ + text(id, message, strlen(message)); +} +void AsyncWebSocket::text(uint32_t id, String &message){ + text(id, message.c_str(), message.length()); +} +void AsyncWebSocket::textAll(const char * message){ + textAll(message, strlen(message)); +} +void AsyncWebSocket::textAll(uint8_t * message, size_t len){ + textAll((const char *)message, len); +} +void AsyncWebSocket::textAll(char * message){ + textAll(message, strlen(message)); +} +void AsyncWebSocket::textAll(String &message){ + textAll(message.c_str(), message.length()); +} +void AsyncWebSocket::binary(uint32_t id, const char * message){ + binary(id, message, strlen(message)); +} +void AsyncWebSocket::binary(uint32_t id, uint8_t * message, size_t len){ + binary(id, (const char *)message, len); +} +void AsyncWebSocket::binary(uint32_t id, char * message){ + binary(id, message, strlen(message)); +} +void AsyncWebSocket::binary(uint32_t id, String &message){ + binary(id, message.c_str(), message.length()); +} +void AsyncWebSocket::binaryAll(const char * message){ + binaryAll(message, strlen(message)); +} +void AsyncWebSocket::binaryAll(uint8_t * message, size_t len){ + binaryAll((const char *)message, len); +} +void AsyncWebSocket::binaryAll(char * message){ + binaryAll(message, strlen(message)); +} +void AsyncWebSocket::binaryAll(String &message){ + binaryAll(message.c_str(), message.length()); +} + +const char * WS_STR_CONNECTION = "Connection"; +const char * WS_STR_UPGRADE = "Upgrade"; +const char * WS_STR_ORIGIN = "Origin"; +const char * WS_STR_VERSION = "Sec-WebSocket-Version"; +const char * WS_STR_KEY = "Sec-WebSocket-Key"; +const char * WS_STR_PROTOCOL = "Sec-WebSocket-Protocol"; +const char * WS_STR_ACCEPT = "Sec-WebSocket-Accept"; +const char * WS_STR_UUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + +bool AsyncWebSocket::canHandle(AsyncWebServerRequest *request){ + if(request->method() != HTTP_GET || !request->url().equals(_url)) + return false; + + request->addInterestingHeader(WS_STR_CONNECTION); + request->addInterestingHeader(WS_STR_UPGRADE); + request->addInterestingHeader(WS_STR_ORIGIN); + request->addInterestingHeader(WS_STR_VERSION); + request->addInterestingHeader(WS_STR_KEY); + request->addInterestingHeader(WS_STR_PROTOCOL); + return true; +} + +void AsyncWebSocket::handleRequest(AsyncWebServerRequest *request){ + if(!request->hasHeader(WS_STR_VERSION) || !request->hasHeader(WS_STR_KEY)){ + request->send(400); + return; + } + AsyncWebHeader* version = request->getHeader(WS_STR_VERSION); + if(version->value().toInt() != 13){ + AsyncWebServerResponse *response = request->beginResponse(400); + response->addHeader(WS_STR_VERSION,"13"); + request->send(response); + return; + } + AsyncWebHeader* key = request->getHeader(WS_STR_KEY); + AsyncWebServerResponse *response = new AsyncWebSocketResponse(key->value(), this); + if(request->hasHeader(WS_STR_PROTOCOL)){ + AsyncWebHeader* protocol = request->getHeader(WS_STR_PROTOCOL); + //ToDo: check protocol + response->addHeader(WS_STR_PROTOCOL, protocol->value()); + } + request->send(response); +} + + +/* + * Response to Web Socket request - sends the authorization and detaches the TCP Client from the web server + */ + +AsyncWebSocketResponse::AsyncWebSocketResponse(String key, AsyncWebSocket *server){ + _server = server; + _code = 101; + uint8_t * hash = (uint8_t*)malloc(20); + if(hash == NULL){ + _state = RESPONSE_FAILED; + return; + } + char * buffer = (char *) malloc(33); + if(buffer == NULL){ + free(hash); + _state = RESPONSE_FAILED; + return; + } +#ifdef ESP8266 + sha1(key + WS_STR_UUID, hash); +#else + key += WS_STR_UUID; + SHA1_CTX ctx; + SHA1Init(&ctx); + SHA1Update(&ctx, (const unsigned char*)key.c_str(), key.length()); + SHA1Final(hash, &ctx); +#endif + base64_encodestate _state; + base64_init_encodestate(&_state); + int len = base64_encode_block((const char *) hash, 20, buffer, &_state); + len = base64_encode_blockend((buffer + len), &_state); + addHeader(WS_STR_CONNECTION, WS_STR_UPGRADE); + addHeader(WS_STR_UPGRADE, "websocket"); + addHeader(WS_STR_ACCEPT,buffer); + free(buffer); + free(hash); +} + +void AsyncWebSocketResponse::_respond(AsyncWebServerRequest *request){ + if(_state == RESPONSE_FAILED){ + request->client()->close(true); + return; + } + String out = _assembleHead(request->version()); + request->client()->write(out.c_str(), _headLength); + _state = RESPONSE_WAIT_ACK; +} + +size_t AsyncWebSocketResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ + if(len){ + new AsyncWebSocketClient(request, _server); + } + return 0; +} diff --git a/src/AsyncWebSocket.h b/src/AsyncWebSocket.h new file mode 100644 index 0000000..a67ea70 --- /dev/null +++ b/src/AsyncWebSocket.h @@ -0,0 +1,202 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + 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 +*/ +#ifndef ASYNCWEBSOCKET_H_ +#define ASYNCWEBSOCKET_H_ + +#include +#include +#include + +class AsyncWebSocket; +class AsyncWebSocketResponse; +class AsyncWebSocketClient; +class AsyncWebSocketControl; + +typedef struct { + uint8_t message_opcode; + uint32_t num; + uint8_t final; + uint8_t masked; + uint8_t opcode; + uint64_t len; + uint8_t mask[4]; + uint64_t index; +} AwsFrameInfo; + +typedef enum { WS_DISCONNECTED, WS_CONNECTED, WS_DISCONNECTING } AwsClientStatus; +typedef enum { WS_CONTINUATION, WS_TEXT, WS_BINARY, WS_DISCONNECT = 0x08, WS_PING, WS_PONG } AwsFrameType; +typedef enum { WS_MSG_SENDING, WS_MSG_SENT, WS_MSG_ERROR } AwsMessageStatus; +typedef enum { WS_EVT_CONNECT, WS_EVT_DISCONNECT, WS_EVT_PONG, WS_EVT_ERROR, WS_EVT_DATA } AwsEventType; + +class AsyncWebSocketMessage { + protected: + uint8_t _opcode; + bool _mask; + AwsMessageStatus _status; + public: + AsyncWebSocketMessage * next; + AsyncWebSocketMessage():_opcode(WS_TEXT),_mask(false),_status(WS_MSG_ERROR),next(NULL){} + virtual ~AsyncWebSocketMessage(){} + virtual void ack(size_t len, uint32_t time){} + virtual size_t send(AsyncClient *client){ return 0; } + virtual bool finished(){ return _status != WS_MSG_SENDING; } + virtual bool betweenFrames(){ return false; } +}; + +class AsyncWebSocketClient { + private: + AsyncClient *_client; + AsyncWebSocket *_server; + uint32_t _clientId; + AwsClientStatus _status; + + AsyncWebSocketControl * _controlQueue; + AsyncWebSocketMessage * _messageQueue; + + uint8_t _pstate; + AwsFrameInfo _pinfo; + + void _queueMessage(AsyncWebSocketMessage *dataMessage); + void _queueControl(AsyncWebSocketControl *controlMessage); + void _runQueue(); + + public: + AsyncWebSocketClient * next; + + AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server); + ~AsyncWebSocketClient(); + + //client id increments for the given server + uint32_t id(){ return _clientId; } + AwsClientStatus status(){ return _status; } + + //control frames + void close(uint16_t code=0, const char * message=NULL); + void ping(uint8_t *data=NULL, size_t len=0); + + //data packets + void message(AsyncWebSocketMessage *message){ _queueMessage(message); } + + size_t printf(const char *format, ...); + + void text(const char * message, size_t len); + void text(const char * message); + void text(uint8_t * message, size_t len); + void text(char * message); + void text(String &message); + + void binary(const char * message, size_t len); + void binary(const char * message); + void binary(uint8_t * message, size_t len); + void binary(char * message); + void binary(String &message); + + //system callbacks (do not call) + void _onAck(size_t len, uint32_t time); + void _onError(int8_t); + void _onPoll(); + void _onTimeout(uint32_t time); + void _onDisconnect(); + void _onData(void *buf, size_t plen); +}; + +typedef std::function AwsEventHandler; + +//WebServer Handler implementation that plays the role of a socket server +class AsyncWebSocket: public AsyncWebHandler { + private: + String _url; + AsyncWebSocketClient * _clients; + uint32_t _cNextId; + AwsEventHandler _eventHandler; + public: + AsyncWebSocket(String url); + ~AsyncWebSocket(); + const char * url(){ return _url.c_str(); } + + size_t count(); + AsyncWebSocketClient * client(uint32_t id); + bool hasClient(uint32_t id){ return client(id) != NULL; } + + void close(uint32_t id, uint16_t code=0, const char * message=NULL); + void closeAll(uint16_t code=0, const char * message=NULL); + + void ping(uint32_t id, uint8_t *data=NULL, size_t len=0); + void pingAll(uint8_t *data=NULL, size_t len=0); + + void text(uint32_t id, const char * message, size_t len); + void text(uint32_t id, const char * message); + void text(uint32_t id, uint8_t * message, size_t len); + void text(uint32_t id, char * message); + void text(uint32_t id, String &message); + + void textAll(const char * message, size_t len); + void textAll(const char * message); + void textAll(uint8_t * message, size_t len); + void textAll(char * message); + void textAll(String &message); + + void binary(uint32_t id, const char * message, size_t len); + void binary(uint32_t id, const char * message); + void binary(uint32_t id, uint8_t * message, size_t len); + void binary(uint32_t id, char * message); + void binary(uint32_t id, String &message); + + void binaryAll(const char * message, size_t len); + void binaryAll(const char * message); + void binaryAll(uint8_t * message, size_t len); + void binaryAll(char * message); + void binaryAll(String &message); + + void message(uint32_t id, AsyncWebSocketMessage *message); + void messageAll(AsyncWebSocketMessage *message); + + size_t printf(uint32_t id, const char *format, ...); + size_t printfAll(const char *format, ...); + + //event listener + void onEvent(AwsEventHandler handler){ + _eventHandler = handler; + } + + //system callbacks (do not call) + uint32_t _getNextId(){ return _cNextId++; } + void _addClient(AsyncWebSocketClient * client); + void _handleDisconnect(AsyncWebSocketClient * client); + void _handleEvent(AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len); + bool canHandle(AsyncWebServerRequest *request); + void handleRequest(AsyncWebServerRequest *request); +}; + +//WebServer response to authenticate the socket and detach the tcp client from the web server request +class AsyncWebSocketResponse: public AsyncWebServerResponse { + private: + String _content; + AsyncWebSocket *_server; + public: + AsyncWebSocketResponse(String key, AsyncWebSocket *server); + void _respond(AsyncWebServerRequest *request); + size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); + bool _sourceValid(){ return true; } +}; + + +#endif /* ASYNCWEBSOCKET_H_ */ diff --git a/src/ESPAsyncWebServer.h b/src/ESPAsyncWebServer.h index 1e67ecf..94d8f89 100644 --- a/src/ESPAsyncWebServer.h +++ b/src/ESPAsyncWebServer.h @@ -308,5 +308,6 @@ class AsyncWebServer { }; #include "WebResponseImpl.h" +#include "AsyncWebSocket.h" #endif /* _AsyncWebServer_H_ */ diff --git a/src/WebResponses.cpp b/src/WebResponses.cpp index e8380cb..e063c70 100644 --- a/src/WebResponses.cpp +++ b/src/WebResponses.cpp @@ -82,10 +82,7 @@ AsyncWebServerResponse::AsyncWebServerResponse() , _sentLength(0) , _ackedLength(0) , _state(RESPONSE_SETUP) -{ - addHeader("Connection","close"); - addHeader("Access-Control-Allow-Origin","*"); -} +{} AsyncWebServerResponse::~AsyncWebServerResponse(){ while(_headers != NULL){ @@ -160,6 +157,8 @@ AsyncBasicResponse::AsyncBasicResponse(int code, String contentType, String cont if(!_contentType.length()) _contentType = "text/plain"; } + addHeader("Connection","close"); + addHeader("Access-Control-Allow-Origin","*"); } void AsyncBasicResponse::_respond(AsyncWebServerRequest *request){ @@ -228,6 +227,8 @@ size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint * */ void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request){ + addHeader("Connection","close"); + addHeader("Access-Control-Allow-Origin","*"); _head = _assembleHead(request->version()); _state = RESPONSE_HEADERS; _ack(request, 0, 0);