From 3f485b6cb6521e52eaad522cfd7051968000a288 Mon Sep 17 00:00:00 2001 From: Gord1 <30983420+Gord1@users.noreply.github.com> Date: Sun, 30 Mar 2025 19:50:14 -0400 Subject: [PATCH 1/5] Add files via upload Changes to these files to fix the Heart Beat not working when client's wifi drops or it goes to to sleep causing the server to slow to a crawl. Heart Beat now works and client disconnects occur quickly with little disruption to other clients. Tested on ESP8266 with two WS servers and 5 clients. Clients can now reconnect after wifi drop. More debug statements added to trace the program flow. --- src/WebSockets.cpp | 29 +++++++++++++++++------------ src/WebSockets.h | 2 +- src/WebSocketsServer.cpp | 16 ++++++++++++---- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/WebSockets.cpp b/src/WebSockets.cpp index 2d3a254..5354abf 100644 --- a/src/WebSockets.cpp +++ b/src/WebSockets.cpp @@ -375,8 +375,7 @@ bool WebSockets::handleWebsocketWaitFor(WSclient_t * client, size_t size) { // timeout or error server->clientDisconnect(client, 1002); } - }, - this, size, std::placeholders::_1, std::placeholders::_2)); + }, this, size, std::placeholders::_1, std::placeholders::_2)); return false; } @@ -459,7 +458,12 @@ void WebSockets::handleWebsocketCb(WSclient_t * client) { clientDisconnect(client, 1011); return; } - readCb(client, payload, header->payloadLen, std::bind(&WebSockets::handleWebsocketPayloadCb, this, std::placeholders::_1, std::placeholders::_2, payload)); + //readCb(client, payload, header->payloadLen, std::bind(&WebSockets::handleWebsocketPayloadCb, this, std::placeholders::_1, std::placeholders::_2, payload)); + //todo this is because the original arguments don't match + readCb(client, payload, header->payloadLen, [this, payload](WSclient_t * client, bool ok) { + // Call the original method with the additional `payload` argument + this->handleWebsocketPayloadCb(client, ok, payload); + }); } else { handleWebsocketPayloadCb(client, true, NULL); } @@ -676,7 +680,7 @@ size_t WebSockets::write(WSclient_t * client, uint8_t * out, size_t n) { unsigned long t = millis(); size_t len = 0; size_t total = 0; - DEBUG_WEBSOCKETS("[write] n: %zu t: %lu\n", n, t); + DEBUG_WEBSOCKETS("[write][%d] n: %zu t: %lu\n", client->num, n, t); while(n > 0) { if(client->tcp == NULL) { DEBUG_WEBSOCKETS("[write] tcp is null!\n"); @@ -684,24 +688,24 @@ size_t WebSockets::write(WSclient_t * client, uint8_t * out, size_t n) { } if(!client->tcp->connected()) { - DEBUG_WEBSOCKETS("[write] not connected!\n"); + DEBUG_WEBSOCKETS("[write][%d] not connected!\n", client->num); break; } if((millis() - t) > WEBSOCKETS_TCP_TIMEOUT) { - DEBUG_WEBSOCKETS("[write] write TIMEOUT! %lu\n", (millis() - t)); + DEBUG_WEBSOCKETS("[write][%d] write TIMEOUT! %lu\n", client->num, (millis() - t)); break; } len = client->tcp->write((const uint8_t *)out, n); if(len) { - t = millis(); + t = millis(); //why restart time? out += len; n -= len; total += len; - // DEBUG_WEBSOCKETS("write %d left %d!\n", len, n); + DEBUG_WEBSOCKETS("WS[write] normal sent: %d, left: %d, t: %lu\n", len, n, millis()); } else { - DEBUG_WEBSOCKETS("WS write %d failed left %d!\n", len, n); + DEBUG_WEBSOCKETS("WS [write][%d] error sent: %d, left: %d, %lu\n", client->num, len, n, millis()); } if(n > 0) { WEBSOCKETS_YIELD(); @@ -740,7 +744,7 @@ void WebSockets::enableHeartbeat(WSclient_t * client, uint32_t pingInterval, uin * @param client WSclient_t * */ void WebSockets::handleHBTimeout(WSclient_t * client) { - if(client->pingInterval) { // if heartbeat is enabled + if( (client->pingInterval) && (client->status == WSC_CONNECTED)) { // if heartbeat is enabled and connected uint32_t pi = millis() - client->lastPing; if(client->pongReceived) { @@ -748,9 +752,10 @@ void WebSockets::handleHBTimeout(WSclient_t * client) { } else { if(pi > client->pongTimeout) { // pong not received in time client->pongTimeoutCount++; - client->lastPing = millis() - client->pingInterval - 500; // force ping on the next run - DEBUG_WEBSOCKETS("[HBtimeout] pong TIMEOUT! lp=%d millis=%lu pi=%d count=%d\n", client->lastPing, millis(), pi, client->pongTimeoutCount); + DEBUG_WEBSOCKETS("[HBtimeout][%d] pong TIMEOUT! lp=%d millis=%lu pi=%d count=%d\n", client->num, client->lastPing, millis(), pi, client->pongTimeoutCount); + + client->lastPing = millis() - client->pingInterval - 500; // give 500ms and force ping next run if(client->disconnectTimeoutCount && client->pongTimeoutCount >= client->disconnectTimeoutCount) { DEBUG_WEBSOCKETS("[HBtimeout] count=%d, DISCONNECTING\n", client->pongTimeoutCount); diff --git a/src/WebSockets.h b/src/WebSockets.h index c918c9f..226ae82 100644 --- a/src/WebSockets.h +++ b/src/WebSockets.h @@ -50,7 +50,7 @@ DEBUG_ESP_PORT.flush(); \ } #else -// #define DEBUG_WEBSOCKETS(...) os_printf( __VA_ARGS__ ) + //#define DEBUG_WEBSOCKETS(...) os_printf( __VA_ARGS__ ) // Uncomment to debug #endif #endif diff --git a/src/WebSocketsServer.cpp b/src/WebSocketsServer.cpp index d3857e6..7b26d62 100644 --- a/src/WebSocketsServer.cpp +++ b/src/WebSocketsServer.cpp @@ -451,8 +451,10 @@ WSclient_t * WebSocketsServerCore::newClient(WEBSOCKETS_NETWORK_CLASS * TCPclien return client; } #endif + DEBUG_WEBSOCKETS("[WS-Server][newclient][%d] is in use\n", client->num); } else { // state is not connected or tcp connection is lost + DEBUG_WEBSOCKETS("[WS-Server][newclient][%d] available for connection\n", client->num); client->tcp = TCPclient; #if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) @@ -491,9 +493,10 @@ WSclient_t * WebSocketsServerCore::newClient(WEBSOCKETS_NETWORK_CLASS * TCPclien client->pingInterval = _pingInterval; client->pongTimeout = _pongTimeout; + client->pongTimeoutCount = 0; //gw addition reset to zero for new client client->disconnectTimeoutCount = _disconnectTimeoutCount; client->lastPing = millis(); - client->pongReceived = false; + client->pongReceived = true; //gw set true - no spurious timeout before first ping sent following WSC_CONNECTED return client; break; @@ -599,6 +602,8 @@ void WebSocketsServerCore::clientDisconnect(WSclient_t * client) { DEBUG_WEBSOCKETS("[WS-Server][%d] client disconnected.\n", client->num); runCbEvent(client->num, WStype_DISCONNECTED, NULL, 0); + + DEBUG_WEBSOCKETS("WS-Server][%d], pongTimeoutCount %d, (should be zero)\n", client->num, client->pongTimeoutCount); //todo this is for testing } /** @@ -657,6 +662,9 @@ WSclient_t * WebSocketsServerCore::handleNewClient(WEBSOCKETS_NETWORK_CLASS * tc return nullptr; } + //test to see if ping timeout values are correct todo remove + DEBUG_WEBSOCKETS("[WS-Client] [handleNewClients] [%d] PongTimeoutCount %d, should be zero for new client\n", client->num, client->pongTimeoutCount); + WEBSOCKETS_YIELD(); return client; @@ -882,8 +890,8 @@ void WebSocketsServerCore::handleHeader(WSclient_t * client, String * headerLine headerDone(client); - // send ping - WebSockets::sendFrame(client, WSop_ping); + // Send HeartBeat Ping if enabled + handleHBPing(client); runCbEvent(client->num, WStype_CONNECTED, (uint8_t *)client->cUrl.c_str(), client->cUrl.length()); @@ -897,7 +905,7 @@ void WebSocketsServerCore::handleHeader(WSclient_t * client, String * headerLine * send heartbeat ping to server in set intervals */ void WebSocketsServerCore::handleHBPing(WSclient_t * client) { - if(client->pingInterval == 0) + if( (client->pingInterval == 0) || client->status != WSC_CONNECTED) return; uint32_t pi = millis() - client->lastPing; if(pi > client->pingInterval) { From d86905da3942bcc60f3879380b1cc81ed0e00e23 Mon Sep 17 00:00:00 2001 From: Gord1 <30983420+Gord1@users.noreply.github.com> Date: Sun, 30 Mar 2025 20:03:47 -0400 Subject: [PATCH 2/5] Add files via upload --- WebSockets.cpp | 767 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 767 insertions(+) create mode 100644 WebSockets.cpp diff --git a/WebSockets.cpp b/WebSockets.cpp new file mode 100644 index 0000000..52f151a --- /dev/null +++ b/WebSockets.cpp @@ -0,0 +1,767 @@ +/** + * @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" + +#ifdef ESP8266 +#include +#endif + +extern "C" { +#ifdef CORE_HAS_LIBB64 +#include +#else +#include "libb64/cencode_inc.h" +#endif +} + +#ifdef ESP8266 +#include +#elif defined(ESP32) +#include + +#if ESP_IDF_VERSION_MAJOR >= 4 +#if(ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(1, 0, 6)) +#include "sha/sha_parallel_engine.h" +#else +#include +#endif +#else +#include +#endif + +#else + +extern "C" { +#include "libsha1/libsha1.h" +} + +#endif + +/** + * + * @param client WSclient_t * ptr to the client struct + * @param code uint16_t see RFC + * @param reason ptr to the disconnect reason message + * @param reasonLen length of the disconnect reason message + */ +void WebSockets::clientDisconnect(WSclient_t * client, uint16_t code, char * reason, size_t reasonLen) { + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] clientDisconnect code: %u\n", client->num, code); + if(client->status == WSC_CONNECTED && code) { + if(reason) { + sendFrame(client, WSop_close, (uint8_t *)reason, reasonLen); + } else { + uint8_t buffer[2]; + buffer[0] = ((code >> 8) & 0xFF); + buffer[1] = (code & 0xFF); + sendFrame(client, WSop_close, &buffer[0], 2); + } + } + clientDisconnect(client); +} + +/** + * + * @param buf uint8_t * ptr to the buffer for writing + * @param opcode WSopcode_t + * @param length size_t length of the payload + * @param mask bool add dummy mask to the frame (needed for web browser) + * @param maskkey uint8_t[4] key used for payload + * @param fin bool can be used to send data in more then one frame (set fin on the last frame) + */ +uint8_t WebSockets::createHeader(uint8_t * headerPtr, WSopcode_t opcode, size_t length, bool mask, uint8_t maskKey[4], bool fin) { + uint8_t headerSize; + // calculate header Size + if(length < 126) { + headerSize = 2; + } else if(length < 0xFFFF) { + headerSize = 4; + } else { + headerSize = 10; + } + + if(mask) { + headerSize += 4; + } + + // create header + + // byte 0 + *headerPtr = 0x00; + if(fin) { + *headerPtr |= bit(7); ///< set Fin + } + *headerPtr |= opcode; ///< set opcode + headerPtr++; + + // byte 1 + *headerPtr = 0x00; + if(mask) { + *headerPtr |= bit(7); ///< set mask + } + + if(length < 126) { + *headerPtr |= length; + headerPtr++; + } else if(length < 0xFFFF) { + *headerPtr |= 126; + headerPtr++; + *headerPtr = ((length >> 8) & 0xFF); + headerPtr++; + *headerPtr = (length & 0xFF); + headerPtr++; + } else { + // Normally we never get here (to less memory) + *headerPtr |= 127; + headerPtr++; + *headerPtr = 0x00; + headerPtr++; + *headerPtr = 0x00; + headerPtr++; + *headerPtr = 0x00; + headerPtr++; + *headerPtr = 0x00; + headerPtr++; + *headerPtr = ((length >> 24) & 0xFF); + headerPtr++; + *headerPtr = ((length >> 16) & 0xFF); + headerPtr++; + *headerPtr = ((length >> 8) & 0xFF); + headerPtr++; + *headerPtr = (length & 0xFF); + headerPtr++; + } + + if(mask) { + *headerPtr = maskKey[0]; + headerPtr++; + *headerPtr = maskKey[1]; + headerPtr++; + *headerPtr = maskKey[2]; + headerPtr++; + *headerPtr = maskKey[3]; + headerPtr++; + } + return headerSize; +} + +/** + * + * @param client WSclient_t * ptr to the client struct + * @param opcode WSopcode_t + * @param length size_t length of the payload + * @param fin bool can be used to send data in more then one frame (set fin on the last frame) + * @return true if ok + */ +bool WebSockets::sendFrameHeader(WSclient_t * client, WSopcode_t opcode, size_t length, bool fin) { + uint8_t maskKey[4] = { 0x00, 0x00, 0x00, 0x00 }; + uint8_t buffer[WEBSOCKETS_MAX_HEADER_SIZE] = { 0 }; + + uint8_t headerSize = createHeader(&buffer[0], opcode, length, client->cIsClient, maskKey, fin); + + if(write(client, &buffer[0], headerSize) != headerSize) { + return false; + } + + return true; +} + +/** + * + * @param client WSclient_t * ptr to the client struct + * @param opcode WSopcode_t + * @param payload uint8_t * ptr to the payload + * @param length size_t length of the payload + * @param fin bool can be used to send data in more then one frame (set fin on the last frame) + * @param headerToPayload bool set true if the payload has reserved 14 Byte at the beginning to dynamically add the Header (payload neet to be in RAM!) + * @return true if ok + */ +bool WebSockets::sendFrame(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin, bool headerToPayload) { + if(client->tcp && !client->tcp->connected()) { + DEBUG_WEBSOCKETS("[WS][%d][sendFrame] not Connected!?\n", client->num); + return false; + } + + if(client->status != WSC_CONNECTED) { + DEBUG_WEBSOCKETS("[WS][%d][sendFrame] not in WSC_CONNECTED state!?\n", client->num); + return false; + } + + DEBUG_WEBSOCKETS("[WS][%d][sendFrame] ------- send message frame -------\n", client->num); + DEBUG_WEBSOCKETS("[WS][%d][sendFrame] fin: %u opCode: %u mask: %u length: %u headerToPayload: %u\n", client->num, fin, opcode, client->cIsClient, length, headerToPayload); + + if(opcode == WSop_text) { + DEBUG_WEBSOCKETS("[WS][%d][sendFrame] text: %s\n", client->num, (payload + (headerToPayload ? 14 : 0))); + } + + uint8_t maskKey[4] = { 0x00, 0x00, 0x00, 0x00 }; + uint8_t buffer[WEBSOCKETS_MAX_HEADER_SIZE] = { 0 }; + + uint8_t headerSize; + uint8_t * headerPtr; + uint8_t * payloadPtr = payload; + bool useInternBuffer = false; + bool ret = true; + + // calculate header Size + if(length < 126) { + headerSize = 2; + } else if(length < 0xFFFF) { + headerSize = 4; + } else { + headerSize = 10; + } + + if(client->cIsClient) { + headerSize += 4; + } + +#ifdef WEBSOCKETS_USE_BIG_MEM + // only for ESP since AVR has less HEAP + // try to send data in one TCP package (only if some free Heap is there) + if(!headerToPayload && ((length > 0) && (length < 1400)) && (GET_FREE_HEAP > 6000)) { + DEBUG_WEBSOCKETS("[WS][%d][sendFrame] pack to one TCP package...\n", client->num); + uint8_t * dataPtr = (uint8_t *)malloc(length + WEBSOCKETS_MAX_HEADER_SIZE); + if(dataPtr) { + memcpy((dataPtr + WEBSOCKETS_MAX_HEADER_SIZE), payload, length); + headerToPayload = true; + useInternBuffer = true; + payloadPtr = dataPtr; + } + } +#endif + + // set Header Pointer + if(headerToPayload) { + // calculate offset in payload + headerPtr = (payloadPtr + (WEBSOCKETS_MAX_HEADER_SIZE - headerSize)); + } else { + headerPtr = &buffer[0]; + } + + if(client->cIsClient && useInternBuffer) { + // if we use a Intern Buffer we can modify the data + // by this fact its possible the do the masking + for(uint8_t x = 0; x < sizeof(maskKey); x++) { + maskKey[x] = random(0xFF); + } + } + + createHeader(headerPtr, opcode, length, client->cIsClient, maskKey, fin); + + if(client->cIsClient && useInternBuffer) { + uint8_t * dataMaskPtr; + + if(headerToPayload) { + dataMaskPtr = (payloadPtr + WEBSOCKETS_MAX_HEADER_SIZE); + } else { + dataMaskPtr = payloadPtr; + } + + for(size_t x = 0; x < length; x++) { + dataMaskPtr[x] = (dataMaskPtr[x] ^ maskKey[x % 4]); + } + } + +#ifndef NODEBUG_WEBSOCKETS + unsigned long start = micros(); +#endif + + if(headerToPayload) { + // header has be added to payload + // payload is forced to reserved 14 Byte but we may not need all based on the length and mask settings + // offset in payload is calculatetd 14 - headerSize + if(write(client, &payloadPtr[(WEBSOCKETS_MAX_HEADER_SIZE - headerSize)], (length + headerSize)) != (length + headerSize)) { + ret = false; + } + } else { + // send header + if(write(client, &buffer[0], headerSize) != headerSize) { + ret = false; + } + + if(payloadPtr && length > 0) { + // send payload + if(write(client, &payloadPtr[0], length) != length) { + ret = false; + } + } + } + + DEBUG_WEBSOCKETS("[WS][%d][sendFrame] sending Frame Done (%luus).\n", client->num, (micros() - start)); + +#ifdef WEBSOCKETS_USE_BIG_MEM + if(useInternBuffer && payloadPtr) { + free(payloadPtr); + } +#endif + + return ret; +} + +/** + * callen when HTTP header is done + * @param client WSclient_t * ptr to the client struct + */ +void WebSockets::headerDone(WSclient_t * client) { + client->status = WSC_CONNECTED; + client->cWsRXsize = 0; + DEBUG_WEBSOCKETS("[WS][%d][headerDone] Header Handling Done.\n", client->num); +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + client->cHttpLine = ""; + handleWebsocket(client); +#endif +} + +/** + * handle the WebSocket stream + * @param client WSclient_t * ptr to the client struct + */ +void WebSockets::handleWebsocket(WSclient_t * client) { + if(client->cWsRXsize == 0) { + handleWebsocketCb(client); + } +} + +/** + * wait for + * @param client + * @param size + */ +bool WebSockets::handleWebsocketWaitFor(WSclient_t * client, size_t size) { + if(!client->tcp || !client->tcp->connected()) { + return false; + } + + if(size > WEBSOCKETS_MAX_HEADER_SIZE) { + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocketWaitFor] size: %d too big!\n", client->num, size); + return false; + } + + if(client->cWsRXsize >= size) { + return true; + } + + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocketWaitFor] size: %d cWsRXsize: %d\n", client->num, size, client->cWsRXsize); + readCb(client, &client->cWsHeader[client->cWsRXsize], (size - client->cWsRXsize), std::bind([](WebSockets * server, size_t size, WSclient_t * client, bool ok) { + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocketWaitFor][readCb] size: %d ok: %d\n", client->num, size, ok); + if(ok) { + client->cWsRXsize = size; + server->handleWebsocketCb(client); + } else { + DEBUG_WEBSOCKETS("[WS][%d][readCb] failed.\n", client->num); + client->cWsRXsize = 0; + // timeout or error + server->clientDisconnect(client, 1002); + } + }, this, size, std::placeholders::_1, std::placeholders::_2)); + return false; +} + +void WebSockets::handleWebsocketCb(WSclient_t * client) { + if(!client->tcp || !client->tcp->connected()) { + return; + } + + uint8_t * buffer = client->cWsHeader; + + WSMessageHeader_t * header = &client->cWsHeaderDecode; + uint8_t * payload = NULL; + + uint8_t headerLen = 2; + + if(!handleWebsocketWaitFor(client, headerLen)) { + return; + } + + // split first 2 bytes in the data + header->fin = ((*buffer >> 7) & 0x01); + header->rsv1 = ((*buffer >> 6) & 0x01); + header->rsv2 = ((*buffer >> 5) & 0x01); + header->rsv3 = ((*buffer >> 4) & 0x01); + header->opCode = (WSopcode_t)(*buffer & 0x0F); + buffer++; + + header->mask = ((*buffer >> 7) & 0x01); + header->payloadLen = (WSopcode_t)(*buffer & 0x7F); + buffer++; + + if(header->payloadLen == 126) { + headerLen += 2; + if(!handleWebsocketWaitFor(client, headerLen)) { + return; + } + header->payloadLen = buffer[0] << 8 | buffer[1]; + buffer += 2; + } else if(header->payloadLen == 127) { + headerLen += 8; + // read 64bit integer as length + if(!handleWebsocketWaitFor(client, headerLen)) { + return; + } + + if(buffer[0] != 0 || buffer[1] != 0 || buffer[2] != 0 || buffer[3] != 0) { + // really too big! + header->payloadLen = 0xFFFFFFFF; + } else { + header->payloadLen = buffer[4] << 24 | buffer[5] << 16 | buffer[6] << 8 | buffer[7]; + } + buffer += 8; + } + + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] ------- read massage frame -------\n", client->num); + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] fin: %u rsv1: %u rsv2: %u rsv3 %u opCode: %u\n", client->num, header->fin, header->rsv1, header->rsv2, header->rsv3, header->opCode); + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] mask: %u payloadLen: %u\n", client->num, header->mask, header->payloadLen); + + if(header->payloadLen > WEBSOCKETS_MAX_DATA_SIZE) { + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] payload too big! (%u)\n", client->num, header->payloadLen); + clientDisconnect(client, 1009); + return; + } + + if(header->mask) { + headerLen += 4; + if(!handleWebsocketWaitFor(client, headerLen)) { + return; + } + header->maskKey = buffer; + buffer += 4; + } + + if(header->payloadLen > 0) { + // if text data we need one more + payload = (uint8_t *)malloc(header->payloadLen + 1); + + if(!payload) { + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] to less memory to handle payload %d!\n", client->num, header->payloadLen); + clientDisconnect(client, 1011); + return; + } + //readCb(client, payload, header->payloadLen, std::bind(&WebSockets::handleWebsocketPayloadCb, this, std::placeholders::_1, std::placeholders::_2, payload)); + //todo this is because the original arguments don't match + readCb(client, payload, header->payloadLen, [this, payload](WSclient_t * client, bool ok) { + // Call the original method with the additional `payload` argument + this->handleWebsocketPayloadCb(client, ok, payload); + }); + } else { + handleWebsocketPayloadCb(client, true, NULL); + } +} + +void WebSockets::handleWebsocketPayloadCb(WSclient_t * client, bool ok, uint8_t * payload) { + WSMessageHeader_t * header = &client->cWsHeaderDecode; + if(ok) { + if(header->payloadLen > 0) { + payload[header->payloadLen] = 0x00; + + if(header->mask) { + // decode XOR + for(size_t i = 0; i < header->payloadLen; i++) { + payload[i] = (payload[i] ^ header->maskKey[i % 4]); + } + } + } + + switch(header->opCode) { + case WSop_text: + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] text: %s\n", client->num, payload); + // fallthrough + case WSop_binary: + case WSop_continuation: + messageReceived(client, header->opCode, payload, header->payloadLen, header->fin); + break; + case WSop_ping: + // send pong back + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] ping received (%s)\n", client->num, payload ? (const char *)payload : ""); + sendFrame(client, WSop_pong, payload, header->payloadLen); + messageReceived(client, header->opCode, payload, header->payloadLen, header->fin); + break; + case WSop_pong: + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] get pong (%s)\n", client->num, payload ? (const char *)payload : ""); + client->pongReceived = true; + messageReceived(client, header->opCode, payload, header->payloadLen, header->fin); + break; + case WSop_close: { +#ifndef NODEBUG_WEBSOCKETS + uint16_t reasonCode = 1000; + if(header->payloadLen >= 2) { + reasonCode = payload[0] << 8 | payload[1]; + } +#endif + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] get ask for close. Code: %d\n", client->num, reasonCode); + if(header->payloadLen > 2) { + DEBUG_WEBSOCKETS(" (%s)\n", (payload + 2)); + } else { + DEBUG_WEBSOCKETS("\n"); + } + clientDisconnect(client, 1000); + } break; + default: + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] got unknown opcode: %d\n", client->num, header->opCode); + clientDisconnect(client, 1002); + break; + } + + if(payload) { + free(payload); + } + + // reset input + client->cWsRXsize = 0; +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + // register callback for next message + handleWebsocketWaitFor(client, 2); +#endif + + } else { + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] missing data!\n", client->num); + free(payload); + clientDisconnect(client, 1002); + } +} + +/** + * generate the key for Sec-WebSocket-Accept + * @param clientKey String + * @return String Accept Key + */ +String WebSockets::acceptKey(String & clientKey) { + uint8_t sha1HashBin[20] = { 0 }; +#ifdef ESP8266 + sha1(clientKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", &sha1HashBin[0]); +#elif defined(ESP32) + String data = clientKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + esp_sha(SHA1, (unsigned char *)data.c_str(), data.length(), &sha1HashBin[0]); +#else + clientKey += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + SHA1_CTX ctx; + SHA1Init(&ctx); + SHA1Update(&ctx, (const unsigned char *)clientKey.c_str(), clientKey.length()); + SHA1Final(&sha1HashBin[0], &ctx); +#endif + + String key = base64_encode(sha1HashBin, 20); + key.trim(); + + return key; +} + +/** + * base64_encode + * @param data uint8_t * + * @param length size_t + * @return base64 encoded String + */ +String WebSockets::base64_encode(uint8_t * data, size_t length) { + size_t size = ((length * 1.6f) + 1); + size = std::max(size, (size_t)5); // minimum buffer size + char * buffer = (char *)malloc(size); + if(buffer) { + base64_encodestate _state; + base64_init_encodestate(&_state); + int len = base64_encode_block((const char *)&data[0], length, &buffer[0], &_state); + len = base64_encode_blockend((buffer + len), &_state); + + String base64 = String(buffer); + free(buffer); + return base64; + } + return String("-FAIL-"); +} + +/** + * read x byte from tcp or get timeout + * @param client WSclient_t * + * @param out uint8_t * data buffer + * @param n size_t byte count + * @return true if ok + */ +bool WebSockets::readCb(WSclient_t * client, uint8_t * out, size_t n, WSreadWaitCb cb) { +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + if(!client->tcp || !client->tcp->connected()) { + return false; + } + + client->tcp->readBytes(out, n, std::bind([](WSclient_t * client, bool ok, WSreadWaitCb cb) { + if(cb) { + cb(client, ok); + } + }, + client, std::placeholders::_1, cb)); + +#else + unsigned long t = millis(); + ssize_t len; + DEBUG_WEBSOCKETS("[readCb] n: %zu t: %lu\n", n, t); + while(n > 0) { + if(client->tcp == NULL) { + DEBUG_WEBSOCKETS("[readCb] tcp is null!\n"); + if(cb) { + cb(client, false); + } + return false; + } + + if(!client->tcp->connected()) { + DEBUG_WEBSOCKETS("[readCb] not connected!\n"); + if(cb) { + cb(client, false); + } + return false; + } + + if((millis() - t) > WEBSOCKETS_TCP_TIMEOUT) { + DEBUG_WEBSOCKETS("[readCb] receive TIMEOUT! %lu\n", (millis() - t)); + if(cb) { + cb(client, false); + } + return false; + } + + if(!client->tcp->available()) { + WEBSOCKETS_YIELD_MORE(); + continue; + } + + len = client->tcp->read((uint8_t *)out, n); + if(len > 0) { + 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); + } + if(n > 0) { + WEBSOCKETS_YIELD(); + } + } + if(cb) { + cb(client, true); + } + WEBSOCKETS_YIELD(); +#endif + return true; +} + +/** + * write x byte to tcp or get timeout + * @param client WSclient_t * + * @param out uint8_t * data buffer + * @param n size_t byte count + * @return bytes send + */ +size_t WebSockets::write(WSclient_t * client, uint8_t * out, size_t n) { + if(out == NULL) + return 0; + if(client == NULL) + return 0; + unsigned long t = millis(); + size_t len = 0; + size_t total = 0; + DEBUG_WEBSOCKETS("[write][%d] n: %zu t: %lu\n", client->num, n, t); + while(n > 0) { + if(client->tcp == NULL) { + DEBUG_WEBSOCKETS("[write] tcp is null!\n"); + break; + } + + if(!client->tcp->connected()) { + DEBUG_WEBSOCKETS("[write][%d] not connected!\n", client->num); + break; + } + + if((millis() - t) > WEBSOCKETS_TCP_TIMEOUT) { + DEBUG_WEBSOCKETS("[write][%d] write TIMEOUT! %lu\n", client->num, (millis() - t)); + break; + } + + len = client->tcp->write((const uint8_t *)out, n); + if(len) { + t = millis(); //why restart time? + out += len; + n -= len; + total += len; + DEBUG_WEBSOCKETS("WS[write] normal sent: %d, left: %d, t: %lu\n", len, n, millis()); + } else { + DEBUG_WEBSOCKETS("WS [write][%d] error sent: %d, left: %d, %lu\n", client->num, len, n, millis()); + } + if(n > 0) { + WEBSOCKETS_YIELD(); + } + } + WEBSOCKETS_YIELD(); + return total; +} + +size_t WebSockets::write(WSclient_t * client, const char * out) { + if(client == NULL) + return 0; + if(out == NULL) + return 0; + return write(client, (uint8_t *)out, strlen(out)); +} + +/** + * enable ping/pong heartbeat process + * @param client WSclient_t * + * @param pingInterval uint32_t how often ping will be sent + * @param pongTimeout uint32_t millis after which pong should timout if not received + * @param disconnectTimeoutCount uint8_t how many timeouts before disconnect, 0=> do not disconnect + */ +void WebSockets::enableHeartbeat(WSclient_t * client, uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount) { + if(client == NULL) + return; + client->pingInterval = pingInterval; + client->pongTimeout = pongTimeout; + client->disconnectTimeoutCount = disconnectTimeoutCount; + client->pongReceived = false; +} + +/** + * handle ping/pong heartbeat timeout process + * @param client WSclient_t * + */ +void WebSockets::handleHBTimeout(WSclient_t * client) { + if( (client->pingInterval) && (client->status == WSC_CONNECTED)) { // if heartbeat is enabled and connected + uint32_t pi = millis() - client->lastPing; + + if(client->pongReceived) { + client->pongTimeoutCount = 0; + } else { + if(pi > client->pongTimeout) { // pong not received in time + client->pongTimeoutCount++; + + DEBUG_WEBSOCKETS("[HBtimeout][%d] pong TIMEOUT! lp=%d millis=%lu pi=%d count=%d\n", client->num, client->lastPing, millis(), pi, client->pongTimeoutCount); + + client->lastPing = millis() - client->pingInterval; //force send ping next loop, next time out in client->pongTimeout + + if(client->disconnectTimeoutCount && client->pongTimeoutCount >= client->disconnectTimeoutCount) { + DEBUG_WEBSOCKETS("[HBtimeout] count=%d, DISCONNECTING\n", client->pongTimeoutCount); + clientDisconnect(client); + } + } + } + } +} From 6caa4418c650b03c0e48bfdd268fda8d41185c4c Mon Sep 17 00:00:00 2001 From: Gord1 <30983420+Gord1@users.noreply.github.com> Date: Sun, 30 Mar 2025 21:37:00 -0400 Subject: [PATCH 3/5] Delete WebSockets.cpp Deleted - in wrong place --- WebSockets.cpp | 767 ------------------------------------------------- 1 file changed, 767 deletions(-) delete mode 100644 WebSockets.cpp diff --git a/WebSockets.cpp b/WebSockets.cpp deleted file mode 100644 index 52f151a..0000000 --- a/WebSockets.cpp +++ /dev/null @@ -1,767 +0,0 @@ -/** - * @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" - -#ifdef ESP8266 -#include -#endif - -extern "C" { -#ifdef CORE_HAS_LIBB64 -#include -#else -#include "libb64/cencode_inc.h" -#endif -} - -#ifdef ESP8266 -#include -#elif defined(ESP32) -#include - -#if ESP_IDF_VERSION_MAJOR >= 4 -#if(ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(1, 0, 6)) -#include "sha/sha_parallel_engine.h" -#else -#include -#endif -#else -#include -#endif - -#else - -extern "C" { -#include "libsha1/libsha1.h" -} - -#endif - -/** - * - * @param client WSclient_t * ptr to the client struct - * @param code uint16_t see RFC - * @param reason ptr to the disconnect reason message - * @param reasonLen length of the disconnect reason message - */ -void WebSockets::clientDisconnect(WSclient_t * client, uint16_t code, char * reason, size_t reasonLen) { - DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] clientDisconnect code: %u\n", client->num, code); - if(client->status == WSC_CONNECTED && code) { - if(reason) { - sendFrame(client, WSop_close, (uint8_t *)reason, reasonLen); - } else { - uint8_t buffer[2]; - buffer[0] = ((code >> 8) & 0xFF); - buffer[1] = (code & 0xFF); - sendFrame(client, WSop_close, &buffer[0], 2); - } - } - clientDisconnect(client); -} - -/** - * - * @param buf uint8_t * ptr to the buffer for writing - * @param opcode WSopcode_t - * @param length size_t length of the payload - * @param mask bool add dummy mask to the frame (needed for web browser) - * @param maskkey uint8_t[4] key used for payload - * @param fin bool can be used to send data in more then one frame (set fin on the last frame) - */ -uint8_t WebSockets::createHeader(uint8_t * headerPtr, WSopcode_t opcode, size_t length, bool mask, uint8_t maskKey[4], bool fin) { - uint8_t headerSize; - // calculate header Size - if(length < 126) { - headerSize = 2; - } else if(length < 0xFFFF) { - headerSize = 4; - } else { - headerSize = 10; - } - - if(mask) { - headerSize += 4; - } - - // create header - - // byte 0 - *headerPtr = 0x00; - if(fin) { - *headerPtr |= bit(7); ///< set Fin - } - *headerPtr |= opcode; ///< set opcode - headerPtr++; - - // byte 1 - *headerPtr = 0x00; - if(mask) { - *headerPtr |= bit(7); ///< set mask - } - - if(length < 126) { - *headerPtr |= length; - headerPtr++; - } else if(length < 0xFFFF) { - *headerPtr |= 126; - headerPtr++; - *headerPtr = ((length >> 8) & 0xFF); - headerPtr++; - *headerPtr = (length & 0xFF); - headerPtr++; - } else { - // Normally we never get here (to less memory) - *headerPtr |= 127; - headerPtr++; - *headerPtr = 0x00; - headerPtr++; - *headerPtr = 0x00; - headerPtr++; - *headerPtr = 0x00; - headerPtr++; - *headerPtr = 0x00; - headerPtr++; - *headerPtr = ((length >> 24) & 0xFF); - headerPtr++; - *headerPtr = ((length >> 16) & 0xFF); - headerPtr++; - *headerPtr = ((length >> 8) & 0xFF); - headerPtr++; - *headerPtr = (length & 0xFF); - headerPtr++; - } - - if(mask) { - *headerPtr = maskKey[0]; - headerPtr++; - *headerPtr = maskKey[1]; - headerPtr++; - *headerPtr = maskKey[2]; - headerPtr++; - *headerPtr = maskKey[3]; - headerPtr++; - } - return headerSize; -} - -/** - * - * @param client WSclient_t * ptr to the client struct - * @param opcode WSopcode_t - * @param length size_t length of the payload - * @param fin bool can be used to send data in more then one frame (set fin on the last frame) - * @return true if ok - */ -bool WebSockets::sendFrameHeader(WSclient_t * client, WSopcode_t opcode, size_t length, bool fin) { - uint8_t maskKey[4] = { 0x00, 0x00, 0x00, 0x00 }; - uint8_t buffer[WEBSOCKETS_MAX_HEADER_SIZE] = { 0 }; - - uint8_t headerSize = createHeader(&buffer[0], opcode, length, client->cIsClient, maskKey, fin); - - if(write(client, &buffer[0], headerSize) != headerSize) { - return false; - } - - return true; -} - -/** - * - * @param client WSclient_t * ptr to the client struct - * @param opcode WSopcode_t - * @param payload uint8_t * ptr to the payload - * @param length size_t length of the payload - * @param fin bool can be used to send data in more then one frame (set fin on the last frame) - * @param headerToPayload bool set true if the payload has reserved 14 Byte at the beginning to dynamically add the Header (payload neet to be in RAM!) - * @return true if ok - */ -bool WebSockets::sendFrame(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin, bool headerToPayload) { - if(client->tcp && !client->tcp->connected()) { - DEBUG_WEBSOCKETS("[WS][%d][sendFrame] not Connected!?\n", client->num); - return false; - } - - if(client->status != WSC_CONNECTED) { - DEBUG_WEBSOCKETS("[WS][%d][sendFrame] not in WSC_CONNECTED state!?\n", client->num); - return false; - } - - DEBUG_WEBSOCKETS("[WS][%d][sendFrame] ------- send message frame -------\n", client->num); - DEBUG_WEBSOCKETS("[WS][%d][sendFrame] fin: %u opCode: %u mask: %u length: %u headerToPayload: %u\n", client->num, fin, opcode, client->cIsClient, length, headerToPayload); - - if(opcode == WSop_text) { - DEBUG_WEBSOCKETS("[WS][%d][sendFrame] text: %s\n", client->num, (payload + (headerToPayload ? 14 : 0))); - } - - uint8_t maskKey[4] = { 0x00, 0x00, 0x00, 0x00 }; - uint8_t buffer[WEBSOCKETS_MAX_HEADER_SIZE] = { 0 }; - - uint8_t headerSize; - uint8_t * headerPtr; - uint8_t * payloadPtr = payload; - bool useInternBuffer = false; - bool ret = true; - - // calculate header Size - if(length < 126) { - headerSize = 2; - } else if(length < 0xFFFF) { - headerSize = 4; - } else { - headerSize = 10; - } - - if(client->cIsClient) { - headerSize += 4; - } - -#ifdef WEBSOCKETS_USE_BIG_MEM - // only for ESP since AVR has less HEAP - // try to send data in one TCP package (only if some free Heap is there) - if(!headerToPayload && ((length > 0) && (length < 1400)) && (GET_FREE_HEAP > 6000)) { - DEBUG_WEBSOCKETS("[WS][%d][sendFrame] pack to one TCP package...\n", client->num); - uint8_t * dataPtr = (uint8_t *)malloc(length + WEBSOCKETS_MAX_HEADER_SIZE); - if(dataPtr) { - memcpy((dataPtr + WEBSOCKETS_MAX_HEADER_SIZE), payload, length); - headerToPayload = true; - useInternBuffer = true; - payloadPtr = dataPtr; - } - } -#endif - - // set Header Pointer - if(headerToPayload) { - // calculate offset in payload - headerPtr = (payloadPtr + (WEBSOCKETS_MAX_HEADER_SIZE - headerSize)); - } else { - headerPtr = &buffer[0]; - } - - if(client->cIsClient && useInternBuffer) { - // if we use a Intern Buffer we can modify the data - // by this fact its possible the do the masking - for(uint8_t x = 0; x < sizeof(maskKey); x++) { - maskKey[x] = random(0xFF); - } - } - - createHeader(headerPtr, opcode, length, client->cIsClient, maskKey, fin); - - if(client->cIsClient && useInternBuffer) { - uint8_t * dataMaskPtr; - - if(headerToPayload) { - dataMaskPtr = (payloadPtr + WEBSOCKETS_MAX_HEADER_SIZE); - } else { - dataMaskPtr = payloadPtr; - } - - for(size_t x = 0; x < length; x++) { - dataMaskPtr[x] = (dataMaskPtr[x] ^ maskKey[x % 4]); - } - } - -#ifndef NODEBUG_WEBSOCKETS - unsigned long start = micros(); -#endif - - if(headerToPayload) { - // header has be added to payload - // payload is forced to reserved 14 Byte but we may not need all based on the length and mask settings - // offset in payload is calculatetd 14 - headerSize - if(write(client, &payloadPtr[(WEBSOCKETS_MAX_HEADER_SIZE - headerSize)], (length + headerSize)) != (length + headerSize)) { - ret = false; - } - } else { - // send header - if(write(client, &buffer[0], headerSize) != headerSize) { - ret = false; - } - - if(payloadPtr && length > 0) { - // send payload - if(write(client, &payloadPtr[0], length) != length) { - ret = false; - } - } - } - - DEBUG_WEBSOCKETS("[WS][%d][sendFrame] sending Frame Done (%luus).\n", client->num, (micros() - start)); - -#ifdef WEBSOCKETS_USE_BIG_MEM - if(useInternBuffer && payloadPtr) { - free(payloadPtr); - } -#endif - - return ret; -} - -/** - * callen when HTTP header is done - * @param client WSclient_t * ptr to the client struct - */ -void WebSockets::headerDone(WSclient_t * client) { - client->status = WSC_CONNECTED; - client->cWsRXsize = 0; - DEBUG_WEBSOCKETS("[WS][%d][headerDone] Header Handling Done.\n", client->num); -#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) - client->cHttpLine = ""; - handleWebsocket(client); -#endif -} - -/** - * handle the WebSocket stream - * @param client WSclient_t * ptr to the client struct - */ -void WebSockets::handleWebsocket(WSclient_t * client) { - if(client->cWsRXsize == 0) { - handleWebsocketCb(client); - } -} - -/** - * wait for - * @param client - * @param size - */ -bool WebSockets::handleWebsocketWaitFor(WSclient_t * client, size_t size) { - if(!client->tcp || !client->tcp->connected()) { - return false; - } - - if(size > WEBSOCKETS_MAX_HEADER_SIZE) { - DEBUG_WEBSOCKETS("[WS][%d][handleWebsocketWaitFor] size: %d too big!\n", client->num, size); - return false; - } - - if(client->cWsRXsize >= size) { - return true; - } - - DEBUG_WEBSOCKETS("[WS][%d][handleWebsocketWaitFor] size: %d cWsRXsize: %d\n", client->num, size, client->cWsRXsize); - readCb(client, &client->cWsHeader[client->cWsRXsize], (size - client->cWsRXsize), std::bind([](WebSockets * server, size_t size, WSclient_t * client, bool ok) { - DEBUG_WEBSOCKETS("[WS][%d][handleWebsocketWaitFor][readCb] size: %d ok: %d\n", client->num, size, ok); - if(ok) { - client->cWsRXsize = size; - server->handleWebsocketCb(client); - } else { - DEBUG_WEBSOCKETS("[WS][%d][readCb] failed.\n", client->num); - client->cWsRXsize = 0; - // timeout or error - server->clientDisconnect(client, 1002); - } - }, this, size, std::placeholders::_1, std::placeholders::_2)); - return false; -} - -void WebSockets::handleWebsocketCb(WSclient_t * client) { - if(!client->tcp || !client->tcp->connected()) { - return; - } - - uint8_t * buffer = client->cWsHeader; - - WSMessageHeader_t * header = &client->cWsHeaderDecode; - uint8_t * payload = NULL; - - uint8_t headerLen = 2; - - if(!handleWebsocketWaitFor(client, headerLen)) { - return; - } - - // split first 2 bytes in the data - header->fin = ((*buffer >> 7) & 0x01); - header->rsv1 = ((*buffer >> 6) & 0x01); - header->rsv2 = ((*buffer >> 5) & 0x01); - header->rsv3 = ((*buffer >> 4) & 0x01); - header->opCode = (WSopcode_t)(*buffer & 0x0F); - buffer++; - - header->mask = ((*buffer >> 7) & 0x01); - header->payloadLen = (WSopcode_t)(*buffer & 0x7F); - buffer++; - - if(header->payloadLen == 126) { - headerLen += 2; - if(!handleWebsocketWaitFor(client, headerLen)) { - return; - } - header->payloadLen = buffer[0] << 8 | buffer[1]; - buffer += 2; - } else if(header->payloadLen == 127) { - headerLen += 8; - // read 64bit integer as length - if(!handleWebsocketWaitFor(client, headerLen)) { - return; - } - - if(buffer[0] != 0 || buffer[1] != 0 || buffer[2] != 0 || buffer[3] != 0) { - // really too big! - header->payloadLen = 0xFFFFFFFF; - } else { - header->payloadLen = buffer[4] << 24 | buffer[5] << 16 | buffer[6] << 8 | buffer[7]; - } - buffer += 8; - } - - DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] ------- read massage frame -------\n", client->num); - DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] fin: %u rsv1: %u rsv2: %u rsv3 %u opCode: %u\n", client->num, header->fin, header->rsv1, header->rsv2, header->rsv3, header->opCode); - DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] mask: %u payloadLen: %u\n", client->num, header->mask, header->payloadLen); - - if(header->payloadLen > WEBSOCKETS_MAX_DATA_SIZE) { - DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] payload too big! (%u)\n", client->num, header->payloadLen); - clientDisconnect(client, 1009); - return; - } - - if(header->mask) { - headerLen += 4; - if(!handleWebsocketWaitFor(client, headerLen)) { - return; - } - header->maskKey = buffer; - buffer += 4; - } - - if(header->payloadLen > 0) { - // if text data we need one more - payload = (uint8_t *)malloc(header->payloadLen + 1); - - if(!payload) { - DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] to less memory to handle payload %d!\n", client->num, header->payloadLen); - clientDisconnect(client, 1011); - return; - } - //readCb(client, payload, header->payloadLen, std::bind(&WebSockets::handleWebsocketPayloadCb, this, std::placeholders::_1, std::placeholders::_2, payload)); - //todo this is because the original arguments don't match - readCb(client, payload, header->payloadLen, [this, payload](WSclient_t * client, bool ok) { - // Call the original method with the additional `payload` argument - this->handleWebsocketPayloadCb(client, ok, payload); - }); - } else { - handleWebsocketPayloadCb(client, true, NULL); - } -} - -void WebSockets::handleWebsocketPayloadCb(WSclient_t * client, bool ok, uint8_t * payload) { - WSMessageHeader_t * header = &client->cWsHeaderDecode; - if(ok) { - if(header->payloadLen > 0) { - payload[header->payloadLen] = 0x00; - - if(header->mask) { - // decode XOR - for(size_t i = 0; i < header->payloadLen; i++) { - payload[i] = (payload[i] ^ header->maskKey[i % 4]); - } - } - } - - switch(header->opCode) { - case WSop_text: - DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] text: %s\n", client->num, payload); - // fallthrough - case WSop_binary: - case WSop_continuation: - messageReceived(client, header->opCode, payload, header->payloadLen, header->fin); - break; - case WSop_ping: - // send pong back - DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] ping received (%s)\n", client->num, payload ? (const char *)payload : ""); - sendFrame(client, WSop_pong, payload, header->payloadLen); - messageReceived(client, header->opCode, payload, header->payloadLen, header->fin); - break; - case WSop_pong: - DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] get pong (%s)\n", client->num, payload ? (const char *)payload : ""); - client->pongReceived = true; - messageReceived(client, header->opCode, payload, header->payloadLen, header->fin); - break; - case WSop_close: { -#ifndef NODEBUG_WEBSOCKETS - uint16_t reasonCode = 1000; - if(header->payloadLen >= 2) { - reasonCode = payload[0] << 8 | payload[1]; - } -#endif - DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] get ask for close. Code: %d\n", client->num, reasonCode); - if(header->payloadLen > 2) { - DEBUG_WEBSOCKETS(" (%s)\n", (payload + 2)); - } else { - DEBUG_WEBSOCKETS("\n"); - } - clientDisconnect(client, 1000); - } break; - default: - DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] got unknown opcode: %d\n", client->num, header->opCode); - clientDisconnect(client, 1002); - break; - } - - if(payload) { - free(payload); - } - - // reset input - client->cWsRXsize = 0; -#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) - // register callback for next message - handleWebsocketWaitFor(client, 2); -#endif - - } else { - DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] missing data!\n", client->num); - free(payload); - clientDisconnect(client, 1002); - } -} - -/** - * generate the key for Sec-WebSocket-Accept - * @param clientKey String - * @return String Accept Key - */ -String WebSockets::acceptKey(String & clientKey) { - uint8_t sha1HashBin[20] = { 0 }; -#ifdef ESP8266 - sha1(clientKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", &sha1HashBin[0]); -#elif defined(ESP32) - String data = clientKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - esp_sha(SHA1, (unsigned char *)data.c_str(), data.length(), &sha1HashBin[0]); -#else - clientKey += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - SHA1_CTX ctx; - SHA1Init(&ctx); - SHA1Update(&ctx, (const unsigned char *)clientKey.c_str(), clientKey.length()); - SHA1Final(&sha1HashBin[0], &ctx); -#endif - - String key = base64_encode(sha1HashBin, 20); - key.trim(); - - return key; -} - -/** - * base64_encode - * @param data uint8_t * - * @param length size_t - * @return base64 encoded String - */ -String WebSockets::base64_encode(uint8_t * data, size_t length) { - size_t size = ((length * 1.6f) + 1); - size = std::max(size, (size_t)5); // minimum buffer size - char * buffer = (char *)malloc(size); - if(buffer) { - base64_encodestate _state; - base64_init_encodestate(&_state); - int len = base64_encode_block((const char *)&data[0], length, &buffer[0], &_state); - len = base64_encode_blockend((buffer + len), &_state); - - String base64 = String(buffer); - free(buffer); - return base64; - } - return String("-FAIL-"); -} - -/** - * read x byte from tcp or get timeout - * @param client WSclient_t * - * @param out uint8_t * data buffer - * @param n size_t byte count - * @return true if ok - */ -bool WebSockets::readCb(WSclient_t * client, uint8_t * out, size_t n, WSreadWaitCb cb) { -#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) - if(!client->tcp || !client->tcp->connected()) { - return false; - } - - client->tcp->readBytes(out, n, std::bind([](WSclient_t * client, bool ok, WSreadWaitCb cb) { - if(cb) { - cb(client, ok); - } - }, - client, std::placeholders::_1, cb)); - -#else - unsigned long t = millis(); - ssize_t len; - DEBUG_WEBSOCKETS("[readCb] n: %zu t: %lu\n", n, t); - while(n > 0) { - if(client->tcp == NULL) { - DEBUG_WEBSOCKETS("[readCb] tcp is null!\n"); - if(cb) { - cb(client, false); - } - return false; - } - - if(!client->tcp->connected()) { - DEBUG_WEBSOCKETS("[readCb] not connected!\n"); - if(cb) { - cb(client, false); - } - return false; - } - - if((millis() - t) > WEBSOCKETS_TCP_TIMEOUT) { - DEBUG_WEBSOCKETS("[readCb] receive TIMEOUT! %lu\n", (millis() - t)); - if(cb) { - cb(client, false); - } - return false; - } - - if(!client->tcp->available()) { - WEBSOCKETS_YIELD_MORE(); - continue; - } - - len = client->tcp->read((uint8_t *)out, n); - if(len > 0) { - 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); - } - if(n > 0) { - WEBSOCKETS_YIELD(); - } - } - if(cb) { - cb(client, true); - } - WEBSOCKETS_YIELD(); -#endif - return true; -} - -/** - * write x byte to tcp or get timeout - * @param client WSclient_t * - * @param out uint8_t * data buffer - * @param n size_t byte count - * @return bytes send - */ -size_t WebSockets::write(WSclient_t * client, uint8_t * out, size_t n) { - if(out == NULL) - return 0; - if(client == NULL) - return 0; - unsigned long t = millis(); - size_t len = 0; - size_t total = 0; - DEBUG_WEBSOCKETS("[write][%d] n: %zu t: %lu\n", client->num, n, t); - while(n > 0) { - if(client->tcp == NULL) { - DEBUG_WEBSOCKETS("[write] tcp is null!\n"); - break; - } - - if(!client->tcp->connected()) { - DEBUG_WEBSOCKETS("[write][%d] not connected!\n", client->num); - break; - } - - if((millis() - t) > WEBSOCKETS_TCP_TIMEOUT) { - DEBUG_WEBSOCKETS("[write][%d] write TIMEOUT! %lu\n", client->num, (millis() - t)); - break; - } - - len = client->tcp->write((const uint8_t *)out, n); - if(len) { - t = millis(); //why restart time? - out += len; - n -= len; - total += len; - DEBUG_WEBSOCKETS("WS[write] normal sent: %d, left: %d, t: %lu\n", len, n, millis()); - } else { - DEBUG_WEBSOCKETS("WS [write][%d] error sent: %d, left: %d, %lu\n", client->num, len, n, millis()); - } - if(n > 0) { - WEBSOCKETS_YIELD(); - } - } - WEBSOCKETS_YIELD(); - return total; -} - -size_t WebSockets::write(WSclient_t * client, const char * out) { - if(client == NULL) - return 0; - if(out == NULL) - return 0; - return write(client, (uint8_t *)out, strlen(out)); -} - -/** - * enable ping/pong heartbeat process - * @param client WSclient_t * - * @param pingInterval uint32_t how often ping will be sent - * @param pongTimeout uint32_t millis after which pong should timout if not received - * @param disconnectTimeoutCount uint8_t how many timeouts before disconnect, 0=> do not disconnect - */ -void WebSockets::enableHeartbeat(WSclient_t * client, uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount) { - if(client == NULL) - return; - client->pingInterval = pingInterval; - client->pongTimeout = pongTimeout; - client->disconnectTimeoutCount = disconnectTimeoutCount; - client->pongReceived = false; -} - -/** - * handle ping/pong heartbeat timeout process - * @param client WSclient_t * - */ -void WebSockets::handleHBTimeout(WSclient_t * client) { - if( (client->pingInterval) && (client->status == WSC_CONNECTED)) { // if heartbeat is enabled and connected - uint32_t pi = millis() - client->lastPing; - - if(client->pongReceived) { - client->pongTimeoutCount = 0; - } else { - if(pi > client->pongTimeout) { // pong not received in time - client->pongTimeoutCount++; - - DEBUG_WEBSOCKETS("[HBtimeout][%d] pong TIMEOUT! lp=%d millis=%lu pi=%d count=%d\n", client->num, client->lastPing, millis(), pi, client->pongTimeoutCount); - - client->lastPing = millis() - client->pingInterval; //force send ping next loop, next time out in client->pongTimeout - - if(client->disconnectTimeoutCount && client->pongTimeoutCount >= client->disconnectTimeoutCount) { - DEBUG_WEBSOCKETS("[HBtimeout] count=%d, DISCONNECTING\n", client->pongTimeoutCount); - clientDisconnect(client); - } - } - } - } -} From e5a471dd424fd42f20cf90f0ea4143e2ee400667 Mon Sep 17 00:00:00 2001 From: Gord1 <30983420+Gord1@users.noreply.github.com> Date: Sun, 30 Mar 2025 21:39:39 -0400 Subject: [PATCH 4/5] Add files via upload Modification to force new ping following timeout in WebSockets::handleHBTimeout --- src/WebSockets.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WebSockets.cpp b/src/WebSockets.cpp index 5354abf..52f151a 100644 --- a/src/WebSockets.cpp +++ b/src/WebSockets.cpp @@ -755,7 +755,7 @@ void WebSockets::handleHBTimeout(WSclient_t * client) { DEBUG_WEBSOCKETS("[HBtimeout][%d] pong TIMEOUT! lp=%d millis=%lu pi=%d count=%d\n", client->num, client->lastPing, millis(), pi, client->pongTimeoutCount); - client->lastPing = millis() - client->pingInterval - 500; // give 500ms and force ping next run + client->lastPing = millis() - client->pingInterval; //force send ping next loop, next time out in client->pongTimeout if(client->disconnectTimeoutCount && client->pongTimeoutCount >= client->disconnectTimeoutCount) { DEBUG_WEBSOCKETS("[HBtimeout] count=%d, DISCONNECTING\n", client->pongTimeoutCount); From f63bcd41c6681500c1fe6eb8e268c1301e18ef96 Mon Sep 17 00:00:00 2001 From: Gord1 <30983420+Gord1@users.noreply.github.com> Date: Tue, 1 Apr 2025 13:40:56 -0400 Subject: [PATCH 5/5] WebSockets.cpp modified reverted to - 500 to force ping in HeartBeat time out --- src/WebSockets.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WebSockets.cpp b/src/WebSockets.cpp index 52f151a..fa547ca 100644 --- a/src/WebSockets.cpp +++ b/src/WebSockets.cpp @@ -755,7 +755,7 @@ void WebSockets::handleHBTimeout(WSclient_t * client) { DEBUG_WEBSOCKETS("[HBtimeout][%d] pong TIMEOUT! lp=%d millis=%lu pi=%d count=%d\n", client->num, client->lastPing, millis(), pi, client->pongTimeoutCount); - client->lastPing = millis() - client->pingInterval; //force send ping next loop, next time out in client->pongTimeout + client->lastPing = millis() - client->pingInterval - 500; //force send ping next loop, next time out in client->pongTimeout if(client->disconnectTimeoutCount && client->pongTimeoutCount >= client->disconnectTimeoutCount) { DEBUG_WEBSOCKETS("[HBtimeout] count=%d, DISCONNECTING\n", client->pongTimeoutCount);