diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp
index cefffb2..e32ff03 100644
--- a/CommandDistributor.cpp
+++ b/CommandDistributor.cpp
@@ -31,6 +31,7 @@
#include "DCC.h"
#include "TrackManager.h"
#include "StringFormatter.h"
+#include "Websockets.h"
// variables to hold clock time
int16_t lastclocktime;
@@ -72,22 +73,39 @@ void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream
// client is using the DCC++ protocol where all commands start
// with '<'
if (clients[clientId] == NONE_TYPE) {
+ auto websock=Websockets::checkConnectionString(clientId,buffer,stream);
+ if (websock) {
+ clients[clientId]=WEBSOCK_CONNECTING_TYPE;
+ return;
+ }
if (buffer[0] == '<')
clients[clientId]=COMMAND_TYPE;
else
clients[clientId]=WITHROTTLE_TYPE;
}
+ // after first inbound transmission the websocket is connected
+ if (clients[clientId]==WEBSOCK_CONNECTING_TYPE)
+ clients[clientId]=WEBSOCKET_TYPE;
+
+
// mark buffer that is sent to parser
- ring->mark(clientId);
-
// When type is known, send the string
// to the right parser
if (clients[clientId] == COMMAND_TYPE) {
+ ring->mark(clientId);
DCCEXParser::parse(stream, buffer, ring);
} else if (clients[clientId] == WITHROTTLE_TYPE) {
+ ring->mark(clientId);
WiThrottle::getThrottle(clientId)->parse(ring, buffer);
}
+ else if (clients[clientId] == WEBSOCKET_TYPE) {
+ buffer=Websockets::unmask(clientId,ring, buffer);
+ if (!buffer) return; // unmask may have handled it alrerday (ping/pong)
+ // mark ring with client flagged as websocket for transmission later
+ ring->mark(clientId | Websockets::WEBSOCK_CLIENT_MARKER);
+ DCCEXParser::parse(stream, buffer, ring);
+ }
if (ring->peekTargetMark()!=RingStream::NO_CLIENT) {
// The commit call will either write the length bytes
@@ -191,7 +209,9 @@ void CommandDistributor::setClockTime(int16_t clocktime, int8_t clockrate, byte
// CAH. DIAG removed because LCD does it anyway.
LCD(6,F("Clk Time:%d Sp %d"), clocktime, clockrate);
// look for an event for this time
+#ifdef EXRAIL_ACTIVE
RMFT2::clockEvent(clocktime,1);
+#endif
// Now tell everyone else what the time is.
CommandDistributor::broadcastClockTime(clocktime, clockrate);
lastclocktime = clocktime;
diff --git a/CommandDistributor.h b/CommandDistributor.h
index be66348..fdf6cf9 100644
--- a/CommandDistributor.h
+++ b/CommandDistributor.h
@@ -37,7 +37,7 @@
class CommandDistributor {
public:
- enum clientType: byte {NONE_TYPE,COMMAND_TYPE,WITHROTTLE_TYPE};
+ enum clientType: byte {NONE_TYPE,COMMAND_TYPE,WITHROTTLE_TYPE,WEBSOCK_CONNECTING_TYPE,WEBSOCKET_TYPE};
private:
static void broadcastToClients(clientType type);
static StringBuffer * broadcastBufferWriter;
diff --git a/Websockets.cpp b/Websockets.cpp
new file mode 100644
index 0000000..c9d964a
--- /dev/null
+++ b/Websockets.cpp
@@ -0,0 +1,206 @@
+/*
+ * © 2023 Chris Harlow
+ * All rights reserved.
+ *
+ * This file is part of CommandStation-EX
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * It 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with CommandStation. If not, see .
+ */
+
+
+/**************************************************
+ HOW IT WORKS
+
+ 1) Refer to https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers
+
+ 2) When a new client sends in a socket stream, the
+ CommandDistributor pass it to this code
+ checkConnectionString() to check for an HTTP
+ protocol GET requesting a change to websocket protocol.
+ If that is found, the relevant answer is generated and queued and
+ the CommandDistributor marks this client as a websocket client awaiting connection.
+ Once the outbound handshake has completed, the CommandDistributor promotes the client
+ from awaiting connection to connected websocket so that all
+ future traffic for this client is handled with websocket protocol.
+
+ 3) When an input is received from a client marked as websocket,
+ CommandDistributor calls unmask() to strip off the websocket header and
+ un-mask the input bytes. The command distributor will flag the
+ clientid in the ringstream so that anyone transmitting this
+ output will know to handle it differently.
+
+ 4) when the Wifi/Ethernet handler needs to transmit the result from the
+ output ring, it recognises the websockets flag and adds the websocket
+ header to the output dynamically.
+
+ *************************************************************/
+#include
+#include "FSH.h"
+#include "RingStream.h"
+#include "libsha1.h"
+#include "Websockets.h"
+#include "DIAG.h"
+#ifdef ARDUINO_ARCH_ESP32
+ // ESP32 runtime or definitions has strlcat_P missing
+ #define strlcat_P strlcat
+#endif
+static const char b64_table[] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
+ 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
+ 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+ 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
+ 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+ 'w', 'x', 'y', 'z', '0', '1', '2', '3',
+ '4', '5', '6', '7', '8', '9', '+', '/'
+};
+
+bool Websockets::checkConnectionString(byte clientId,byte * cmd, RingStream * outbound ) {
+ // returns true if this input is a websocket connect
+ DIAG(F("In websock check"));
+ /* Heuristic suppose this is a websocket GET
+ typically looking like this:
+
+ GET / HTTP/1.1
+ Host: 192.168.1.242:2560
+ Connection: Upgrade
+ Pragma: no-cache
+ Cache-Control: no-cache
+ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0
+ Upgrade: websocket
+ Origin: null
+ Sec-WebSocket-Version: 13
+ Accept-Encoding: gzip, deflate
+ Accept-Language: en-US,en;q=0.9
+ Sec-WebSocket-Key: SpRkQKPPNZcO62pYf1X6Yg==
+ Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
+ */
+
+ // check contents to find Sec-WebSocket-Key: and get key up to \n
+ auto keyPos=strstr_P((char*)cmd,(char*)F("Sec-WebSocket-Key: "));
+ if (!keyPos) return false;
+ keyPos+=19; // length of Sec-Websocket-Key:
+ auto endkeypos=strstr(keyPos,"\r");
+ if (!endkeypos) return false;
+ *endkeypos=0;
+
+ DIAG(F("websock key=\"%s\""),keyPos);
+// generate the reply key
+ uint8_t sha1HashBin[21] = { 0 }; // 21 to make it base64 div 3
+ char replyKey[100];
+ strlcpy(replyKey,keyPos, sizeof(replyKey));
+ strlcat_P(replyKey,(char*)F("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"), sizeof(replyKey));
+
+ DIAG(F("websock replykey=%s"),replyKey);
+
+ SHA1_CTX ctx;
+ SHA1Init(&ctx);
+ SHA1Update(&ctx, (unsigned char *)replyKey, strlen(replyKey));
+ SHA1Final(sha1HashBin, &ctx);
+
+ // generate the response and embed the base64 encode
+ // of the key
+ outbound->mark(clientId);
+ outbound->print(F("HTTP/1.1 101 Switching Protocols\r\n"
+ "Server: DCCEX-WebSocketsServer\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Origin: null\r\n"
+ "Sec-WebSocket-Version: 13\r\n"
+ "Sec-WebSocket-Protocol: DCCEX\r\n"
+ "Sec-WebSocket-Accept: "));
+// encode and emit the reply key as base 64
+ auto * tmp=sha1HashBin;
+ for (int i=0;i<7;i++) {
+ outbound->print(b64_table[(tmp[0] & 0xfc) >> 2]);
+ outbound->print(b64_table[((tmp[0] & 0x03) << 4) + ((tmp[1] & 0xf0) >> 4)]);
+ outbound->print(b64_table[((tmp[1] & 0x0f) << 2) + ((tmp[2] & 0xc0) >> 6)]);
+ if (i<6) outbound->print(b64_table[tmp[2] & 0x3f]);
+ tmp+=3;
+ }
+ outbound->print(F("=\r\n\r\n")); // because we have padded 1 byte
+ outbound->commit();
+ return true;
+}
+
+byte * Websockets::unmask(byte clientId,RingStream *ring, byte * buffer) {
+ // buffer should have a websocket header
+ //byte opcode=buffer[0] & 0x0f;
+ DIAG(F("Websock in: %x %x %x %x %x %x %x %x"),
+ buffer[0],buffer[1],buffer[2],buffer[3],
+ buffer[4],buffer[5],buffer[6]);
+
+ byte opcode=buffer[0];
+ bool maskbit=buffer[1]&0x80;
+ int16_t payloadLength=buffer[1]&0x7f;
+
+ byte * mask;
+ if (payloadLength<126) {
+ mask=buffer+2;
+ }
+ else {
+ payloadLength=(buffer[3]<<8)|(buffer[2]);
+ mask=buffer+4;
+ }
+ DIAG(F("Websock op=%x mb=%b pl=%d m=%x %x %x %x"), opcode, maskbit, payloadLength,
+ mask[0],mask[1],mask[2], mask[3]);
+
+ if (opcode==0x89) { // ping
+ DIAG(F("Websocket ping"));
+ buffer[0]=0x8a; // pong.. and send it back
+ ring->mark(clientId &0x7f); // dont readjust
+ ring->print((char *)buffer);
+ ring->commit();
+ return nullptr;
+ }
+
+ if (opcode!=0x81) {
+ DIAG(F("Websocket unknown opcode 0x%x"),opcode);
+ return nullptr;
+ }
+
+ byte * payload=mask+4;
+ for (int i=0;i=126)? 4:2;
+ }
+
+int Websockets::fillOutboundHeader(uint16_t dataLength, byte * buffer) {
+ // text opcode, flag(126= use 2 length bytes, no mask bit) , length
+ buffer[0]=0x81;
+ if (dataLength<126) {
+ buffer[1]=(byte)dataLength;
+ return 2;
+ }
+ buffer[1]=126;
+ buffer[2]=(byte)(dataLength & 0xFF);
+ buffer[3]= (byte)(dataLength>>8);
+ return 4;
+}
+
+ void Websockets::writeOutboundHeader(Print * stream,uint16_t dataLength) {
+ byte prefix[4];
+ int headerlen=fillOutboundHeader(dataLength,prefix);
+ stream->write(prefix,sizeof(headerlen));
+ }
+
+
+
diff --git a/Websockets.h b/Websockets.h
new file mode 100644
index 0000000..0792b47
--- /dev/null
+++ b/Websockets.h
@@ -0,0 +1,34 @@
+/*
+ * © 2023 Chris Harlow
+ * All rights reserved.
+ *
+ * This file is part of CommandStation-EX
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * It 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with CommandStation. If not, see .
+ */
+#ifndef Websockets_h
+#define Websockets_h
+#include
+#include "RingStream.h"
+class Websockets {
+ public:
+ static bool checkConnectionString(byte clientId,byte * cmd, RingStream * outbound );
+ static byte * unmask(byte clientId,RingStream *ring, byte * buffer);
+ static int16_t getOutboundHeaderSize(uint16_t dataLength);
+ static int fillOutboundHeader(uint16_t dataLength, byte * buffer);
+ static void writeOutboundHeader(Print * stream,uint16_t dataLength);
+ static const byte WEBSOCK_CLIENT_MARKER=0x80;
+};
+
+#endif
\ No newline at end of file
diff --git a/WifiESP32.cpp b/WifiESP32.cpp
index 227484c..5355a0d 100644
--- a/WifiESP32.cpp
+++ b/WifiESP32.cpp
@@ -2,6 +2,8 @@
© 2023 Paul M. Antoine
© 2021 Harald Barth
© 2023 Nathan Kellenicki
+ © 2025 Chris Harlow
+
This file is part of CommandStation-EX
@@ -30,6 +32,7 @@
#include "CommandDistributor.h"
#include "WiThrottle.h"
#include "DCC.h"
+#include "Websockets.h"
/*
#include "soc/rtc_wdt.h"
#include "esp_task_wdt.h"
@@ -378,6 +381,8 @@ void WifiESP::loop() {
// something to write out?
clientId=outboundRing->read();
+ bool useWebsocket=clientId & Websockets::WEBSOCK_CLIENT_MARKER;
+ clientId &= ~ Websockets::WEBSOCK_CLIENT_MARKER;
if (clientId >= 0) {
// We have data to send in outboundRing
// and we have a valid clientId.
@@ -385,19 +390,22 @@ void WifiESP::loop() {
// and then look if it can be sent because
// we can not leave it in the ring for ever
int count=outboundRing->count();
+ auto wsHeaderLen=useWebsocket? Websockets::getOutboundHeaderSize(count) : 0;
{
- char buffer[count+1]; // one extra for '\0'
+
+ byte buffer[wsHeaderLen+count+1]; // one extra for '\0'
+ if (useWebsocket) Websockets::fillOutboundHeader(count, buffer);
for(int i=0;iread();
if (c >= 0) // Panic check, should never be false
- buffer[i] = (char)c;
+ buffer[i+wsHeaderLen] = (char)c;
else {
DIAG(F("Ringread fail at %d"),i);
break;
}
}
// buffer filled, end with '\0' so we can use it as C string
- buffer[count]='\0';
+ buffer[wsHeaderLen+count]='\0';
if((unsigned int)clientId <= clients.size() && clients[clientId].active(clientId)) {
if (Diag::CMD || Diag::WITHROTTLE)
DIAG(F("SEND %d:%s"), clientId, buffer);
diff --git a/WifiInboundHandler.cpp b/WifiInboundHandler.cpp
index b570527..fdc7154 100644
--- a/WifiInboundHandler.cpp
+++ b/WifiInboundHandler.cpp
@@ -1,7 +1,7 @@
/*
* © 2021 Fred Decker
* © 2021 Fred Decker
- * © 2020-2021 Chris Harlow
+ * © 2020-2025 Chris Harlow
* © 2020, Chris Harlow. All rights reserved.
* © 2020, Harald Barth.
*
@@ -26,6 +26,7 @@
#include "RingStream.h"
#include "CommandDistributor.h"
#include "DIAG.h"
+#include "Websockets.h"
WifiInboundHandler * WifiInboundHandler::singleton;
@@ -67,8 +68,13 @@ void WifiInboundHandler::loop1() {
if (pendingCipsend && millis()-lastCIPSEND > CIPSENDgap) {
- if (Diag::WIFI) DIAG( F("WiFi: [[CIPSEND=%d,%d]]"), clientPendingCIPSEND, currentReplySize);
- StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), clientPendingCIPSEND, currentReplySize);
+ // add allowances for websockets
+ bool websocket=clientPendingCIPSEND & Websockets::WEBSOCK_CLIENT_MARKER;
+ byte realClient=clientPendingCIPSEND & ~Websockets::WEBSOCK_CLIENT_MARKER;
+ int16_t realSize=currentReplySize;
+ if (websocket) realSize+=Websockets::getOutboundHeaderSize(currentReplySize);
+ if (Diag::WIFI) DIAG( F("WiFi: [[CIPSEND=%d,%d]]"), realClient, realSize);
+ StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), realClient,realSize);
pendingCipsend=false;
return;
}
@@ -94,6 +100,9 @@ void WifiInboundHandler::loop1() {
// This is a Finite State Automation (FSA) handling the inbound bytes from an ES AT command processor
WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
+ const char WebSocketKeyName[]="Sec-WebSocket-Key: ";
+ static byte prescanPoint=0;
+
while (wifiStream->available()) {
int ch = wifiStream->read();
@@ -112,9 +121,12 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
}
if (ch=='>') {
- if (Diag::WIFI) DIAG(F("[XMIT %d]"),currentReplySize);
+ bool websocket=clientPendingCIPSEND & Websockets::WEBSOCK_CLIENT_MARKER;
+ if (Diag::WIFI) DIAG(F("[XMIT %d ws=%b]"),currentReplySize,websocket);
+ if (websocket) Websockets::writeOutboundHeader(wifiStream,currentReplySize);
for (int i=0;iread();
+ if (websocket && (cout=='\n')) cout='\r';
wifiStream->write(cout);
if (Diag::WIFI) StringFormatter::printEscape(cout); // DIAG in disguise
}
@@ -195,14 +207,19 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
break;
}
if (Diag::WIFI) DIAG(F("Wifi inbound data(%d:%d):"),runningClientId,dataLength);
- if (inboundRing->freeSpace()<=(dataLength+1)) {
+
+ // we normally dont read >100 bytes
+ // so assume its an HTTP GET or similar
+
+ if (dataLength<100 && inboundRing->freeSpace()<=(dataLength+1)) {
// This input would overflow the inbound ring, ignore it
loopState=IPD_IGNORE_DATA;
if (Diag::WIFI) DIAG(F("Wifi OVERFLOW IGNORING:"));
break;
}
inboundRing->mark(runningClientId);
- loopState=IPD_DATA;
+ prescanPoint=0;
+ loopState=(dataLength>100)? IPD_PRESCAN: IPD_DATA;
break;
}
dataLength = dataLength * 10 + (ch - '0');
@@ -217,6 +234,38 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
}
break;
+ case IPD_PRESCAN: // prescan reading data
+ dataLength--;
+ if (dataLength == 0) {
+ // Nothing found, this input is lost
+ DIAG(F("Wifi prescan not found"));
+ inboundRing->commit();
+ loopState = ANYTHING;
+ }
+ if (ch!=WebSocketKeyName[prescanPoint]) {
+ prescanPoint=0;
+ break;
+ }
+ // matched the next char of the key
+ prescanPoint++;
+ if (WebSocketKeyName[prescanPoint]==0) {
+ DIAG(F("Wifi prescan found"));
+ // prescan has detected full key
+ inboundRing->print(WebSocketKeyName);
+ loopState=IPD_POSTSCAN; // continmue as normal
+ }
+ break;
+
+ case IPD_POSTSCAN: // reading data
+ inboundRing->write(ch);
+ dataLength--;
+ if (ch=='\n') {
+ inboundRing->commit();
+ loopState = IPD_IGNORE_DATA;
+ }
+ break;
+
+
case IPD_IGNORE_DATA: // ignoring data that would not fit in inbound ring
dataLength--;
if (dataLength == 0) loopState = ANYTHING;
diff --git a/WifiInboundHandler.h b/WifiInboundHandler.h
index 08f6789..ea3745d 100644
--- a/WifiInboundHandler.h
+++ b/WifiInboundHandler.h
@@ -2,7 +2,7 @@
* © 2021 Harald Barth
* © 2021 Fred Decker
* (c) 2021 Fred Decker. All rights reserved.
- * (c) 2020 Chris Harlow. All rights reserved.
+ * (c) 2020-2025 Chris Harlow. All rights reserved.
*
* This file is part of CommandStation-EX
*
@@ -55,7 +55,8 @@ class WifiInboundHandler {
IPD6_LENGTH, // got +IPD,c, reading length
IPD_DATA, // got +IPD,c,ll,: collecting data
IPD_IGNORE_DATA, // got +IPD,c,ll,: ignoring the data that won't fit inblound Ring
-
+ IPD_PRESCAN, // prescanning data for websocket keys
+ IPD_POSTSCAN, // copyimg data for websocket keys
GOT_CLIENT_ID, // clientid prefix to CONNECTED / CLOSED
GOT_CLIENT_ID2 // clientid prefix to CONNECTED / CLOSED
};
@@ -67,7 +68,7 @@ class WifiInboundHandler {
void purgeCurrentCIPSEND();
Stream * wifiStream;
- static const int INBOUND_RING = 512;
+ static const int INBOUND_RING = 128;
static const int OUTBOUND_RING = sizeof(void*)==2?2048:8192;
static const int CIPSENDgap=100; // millis() between retries of cipsend.
diff --git a/libsha1.cpp b/libsha1.cpp
new file mode 100644
index 0000000..38a5691
--- /dev/null
+++ b/libsha1.cpp
@@ -0,0 +1,206 @@
+// For DCC-EX: This file downloaded from:
+// https://github.com/Links2004/arduinoWebSockets
+// All due credit to Steve Reid
+
+/* from valgrind tests */
+
+/* ================ sha1.c ================ */
+/*
+SHA-1 in C
+By Steve Reid
+100% Public Domain
+
+Test Vectors (from FIPS PUB 180-1)
+"abc"
+ A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D
+"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
+ 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1
+A million repetitions of "a"
+ 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F
+*/
+
+/* #define LITTLE_ENDIAN * This should be #define'd already, if true. */
+/* #define SHA1HANDSOFF * Copies data before messing with it. */
+
+// DCC-EX removed #if !defined(ESP8266) && !defined(ESP32)
+
+#define SHA1HANDSOFF
+
+#include
+#include
+#include
+
+#include "libsha1.h"
+
+
+#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
+
+/* blk0() and blk() perform the initial expand. */
+/* I got the idea of expanding during the round function from SSLeay */
+#if BYTE_ORDER == LITTLE_ENDIAN
+#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \
+ |(rol(block->l[i],8)&0x00FF00FF))
+#elif BYTE_ORDER == BIG_ENDIAN
+#define blk0(i) block->l[i]
+#else
+#error "Endianness not defined!"
+#endif
+#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \
+ ^block->l[(i+2)&15]^block->l[i&15],1))
+
+/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */
+#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30);
+#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);
+#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);
+#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);
+#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);
+
+
+/* Hash a single 512-bit block. This is the core of the algorithm. */
+
+void SHA1Transform(uint32_t state[5], const unsigned char buffer[64])
+{
+ uint32_t a, b, c, d, e;
+ typedef union {
+ unsigned char c[64];
+ uint32_t l[16];
+ } CHAR64LONG16;
+#ifdef SHA1HANDSOFF
+ CHAR64LONG16 block[1]; /* use array to appear as a pointer */
+ memcpy(block, buffer, 64);
+#else
+ /* The following had better never be used because it causes the
+ * pointer-to-const buffer to be cast into a pointer to non-const.
+ * And the result is written through. I threw a "const" in, hoping
+ * this will cause a diagnostic.
+ */
+ CHAR64LONG16* block = (const CHAR64LONG16*)buffer;
+#endif
+ /* Copy context->state[] to working vars */
+ a = state[0];
+ b = state[1];
+ c = state[2];
+ d = state[3];
+ e = state[4];
+ /* 4 rounds of 20 operations each. Loop unrolled. */
+ R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3);
+ R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7);
+ R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11);
+ R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15);
+ R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19);
+ R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23);
+ R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27);
+ R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31);
+ R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35);
+ R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39);
+ R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43);
+ R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47);
+ R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51);
+ R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55);
+ R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59);
+ R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63);
+ R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67);
+ R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71);
+ R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75);
+ R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79);
+ /* Add the working vars back into context.state[] */
+ state[0] += a;
+ state[1] += b;
+ state[2] += c;
+ state[3] += d;
+ state[4] += e;
+ /* Wipe variables */
+ a = b = c = d = e = 0;
+#ifdef SHA1HANDSOFF
+ memset(block, '\0', sizeof(block));
+#endif
+}
+
+
+/* SHA1Init - Initialize new context */
+
+void SHA1Init(SHA1_CTX* context)
+{
+ /* SHA1 initialization constants */
+ context->state[0] = 0x67452301;
+ context->state[1] = 0xEFCDAB89;
+ context->state[2] = 0x98BADCFE;
+ context->state[3] = 0x10325476;
+ context->state[4] = 0xC3D2E1F0;
+ context->count[0] = context->count[1] = 0;
+}
+
+
+/* Run your data through this. */
+
+void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len)
+{
+ uint32_t i, j;
+
+ j = context->count[0];
+ if ((context->count[0] += len << 3) < j)
+ context->count[1]++;
+ context->count[1] += (len>>29);
+ j = (j >> 3) & 63;
+ if ((j + len) > 63) {
+ memcpy(&context->buffer[j], data, (i = 64-j));
+ SHA1Transform(context->state, context->buffer);
+ for ( ; i + 63 < len; i += 64) {
+ SHA1Transform(context->state, &data[i]);
+ }
+ j = 0;
+ }
+ else i = 0;
+ memcpy(&context->buffer[j], &data[i], len - i);
+}
+
+
+/* Add padding and return the message digest. */
+
+void SHA1Final(unsigned char digest[20], SHA1_CTX* context)
+{
+ unsigned i;
+ unsigned char finalcount[8];
+ unsigned char c;
+
+#if 0 /* untested "improvement" by DHR */
+ /* Convert context->count to a sequence of bytes
+ * in finalcount. Second element first, but
+ * big-endian order within element.
+ * But we do it all backwards.
+ */
+ unsigned char *fcp = &finalcount[8];
+
+ for (i = 0; i < 2; i++)
+ {
+ uint32_t t = context->count[i];
+ int j;
+
+ for (j = 0; j < 4; t >>= 8, j++)
+ *--fcp = (unsigned char) t;
+ }
+#else
+ for (i = 0; i < 8; i++) {
+ finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)]
+ >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */
+ }
+#endif
+ c = 0200;
+ SHA1Update(context, &c, 1);
+ while ((context->count[0] & 504) != 448) {
+ c = 0000;
+ SHA1Update(context, &c, 1);
+ }
+ SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */
+ for (i = 0; i < 20; i++) {
+ digest[i] = (unsigned char)
+ ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255);
+ }
+ /* Wipe variables */
+ memset(context, '\0', sizeof(*context));
+ memset(&finalcount, '\0', sizeof(finalcount));
+}
+/* ================ end of sha1.c ================ */
+
+
+// DCC-EX Removed: #endif
diff --git a/libsha1.h b/libsha1.h
new file mode 100644
index 0000000..1dc063d
--- /dev/null
+++ b/libsha1.h
@@ -0,0 +1,26 @@
+// For DCC-EX: This file downloaded from:
+// https://github.com/Links2004/arduinoWebSockets
+// All due credit to Steve Reid
+
+/* ================ sha1.h ================ */
+/*
+SHA-1 in C
+By Steve Reid
+100% Public Domain
+*/
+
+// DCC-EX REMOVED #if !defined(ESP8266) && !defined(ESP32)
+#ifndef libsha1_h
+#define libsha1_h
+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);
+
+#endif
\ No newline at end of file