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 <https://www.gnu.org/licenses/>.
+ */
+
+
+/**************************************************
+  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 <Arduino.h>
+#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<payloadLength;i++) {
+     payload[i]^=mask[i%4];
+ }
+ 
+ return payload; // payload will be parsed as normal
+     
+ }
+
+ int16_t Websockets::getOutboundHeaderSize(uint16_t dataLength) {
+   return (dataLength>=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 <https://www.gnu.org/licenses/>.
+ */
+#ifndef Websockets_h
+#define Websockets_h
+#include <Arduino.h>
+#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;i<count;i++) {
 	  int c = outboundRing->read();
 	  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;i<currentReplySize;i++) {
              int cout=outboundRing->read();
+             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 <steve@edmweb.com>
+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 <stdio.h>
+#include <string.h>
+#include <stdint.h>
+
+#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 <steve@edmweb.com>
+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