1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2025-01-30 22:23:07 +01:00

5.5.5 + websockets

Ethernet not yet included
This commit is contained in:
Asbelos 2025-01-15 09:44:04 +00:00
parent 08076443cb
commit 67f836e886
9 changed files with 565 additions and 15 deletions

View File

@ -31,6 +31,7 @@
#include "DCC.h" #include "DCC.h"
#include "TrackManager.h" #include "TrackManager.h"
#include "StringFormatter.h" #include "StringFormatter.h"
#include "Websockets.h"
// variables to hold clock time // variables to hold clock time
int16_t lastclocktime; 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 // client is using the DCC++ protocol where all commands start
// with '<' // with '<'
if (clients[clientId] == NONE_TYPE) { if (clients[clientId] == NONE_TYPE) {
auto websock=Websockets::checkConnectionString(clientId,buffer,stream);
if (websock) {
clients[clientId]=WEBSOCK_CONNECTING_TYPE;
return;
}
if (buffer[0] == '<') if (buffer[0] == '<')
clients[clientId]=COMMAND_TYPE; clients[clientId]=COMMAND_TYPE;
else else
clients[clientId]=WITHROTTLE_TYPE; clients[clientId]=WITHROTTLE_TYPE;
} }
// mark buffer that is sent to parser // after first inbound transmission the websocket is connected
ring->mark(clientId); if (clients[clientId]==WEBSOCK_CONNECTING_TYPE)
clients[clientId]=WEBSOCKET_TYPE;
// mark buffer that is sent to parser
// When type is known, send the string // When type is known, send the string
// to the right parser // to the right parser
if (clients[clientId] == COMMAND_TYPE) { if (clients[clientId] == COMMAND_TYPE) {
ring->mark(clientId);
DCCEXParser::parse(stream, buffer, ring); DCCEXParser::parse(stream, buffer, ring);
} else if (clients[clientId] == WITHROTTLE_TYPE) { } else if (clients[clientId] == WITHROTTLE_TYPE) {
ring->mark(clientId);
WiThrottle::getThrottle(clientId)->parse(ring, buffer); 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) { if (ring->peekTargetMark()!=RingStream::NO_CLIENT) {
// The commit call will either write the length bytes // 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. // CAH. DIAG removed because LCD does it anyway.
LCD(6,F("Clk Time:%d Sp %d"), clocktime, clockrate); LCD(6,F("Clk Time:%d Sp %d"), clocktime, clockrate);
// look for an event for this time // look for an event for this time
#ifdef EXRAIL_ACTIVE
RMFT2::clockEvent(clocktime,1); RMFT2::clockEvent(clocktime,1);
#endif
// Now tell everyone else what the time is. // Now tell everyone else what the time is.
CommandDistributor::broadcastClockTime(clocktime, clockrate); CommandDistributor::broadcastClockTime(clocktime, clockrate);
lastclocktime = clocktime; lastclocktime = clocktime;

View File

@ -37,7 +37,7 @@
class CommandDistributor { class CommandDistributor {
public: 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: private:
static void broadcastToClients(clientType type); static void broadcastToClients(clientType type);
static StringBuffer * broadcastBufferWriter; static StringBuffer * broadcastBufferWriter;

206
Websockets.cpp Normal file
View File

@ -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));
}

34
Websockets.h Normal file
View File

@ -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

View File

@ -2,6 +2,8 @@
© 2023 Paul M. Antoine © 2023 Paul M. Antoine
© 2021 Harald Barth © 2021 Harald Barth
© 2023 Nathan Kellenicki © 2023 Nathan Kellenicki
© 2025 Chris Harlow
This file is part of CommandStation-EX This file is part of CommandStation-EX
@ -30,6 +32,7 @@
#include "CommandDistributor.h" #include "CommandDistributor.h"
#include "WiThrottle.h" #include "WiThrottle.h"
#include "DCC.h" #include "DCC.h"
#include "Websockets.h"
/* /*
#include "soc/rtc_wdt.h" #include "soc/rtc_wdt.h"
#include "esp_task_wdt.h" #include "esp_task_wdt.h"
@ -378,6 +381,8 @@ void WifiESP::loop() {
// something to write out? // something to write out?
clientId=outboundRing->read(); clientId=outboundRing->read();
bool useWebsocket=clientId & Websockets::WEBSOCK_CLIENT_MARKER;
clientId &= ~ Websockets::WEBSOCK_CLIENT_MARKER;
if (clientId >= 0) { if (clientId >= 0) {
// We have data to send in outboundRing // We have data to send in outboundRing
// and we have a valid clientId. // and we have a valid clientId.
@ -385,19 +390,22 @@ void WifiESP::loop() {
// and then look if it can be sent because // and then look if it can be sent because
// we can not leave it in the ring for ever // we can not leave it in the ring for ever
int count=outboundRing->count(); 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++) { for(int i=0;i<count;i++) {
int c = outboundRing->read(); int c = outboundRing->read();
if (c >= 0) // Panic check, should never be false if (c >= 0) // Panic check, should never be false
buffer[i] = (char)c; buffer[i+wsHeaderLen] = (char)c;
else { else {
DIAG(F("Ringread fail at %d"),i); DIAG(F("Ringread fail at %d"),i);
break; break;
} }
} }
// buffer filled, end with '\0' so we can use it as C string // 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((unsigned int)clientId <= clients.size() && clients[clientId].active(clientId)) {
if (Diag::CMD || Diag::WITHROTTLE) if (Diag::CMD || Diag::WITHROTTLE)
DIAG(F("SEND %d:%s"), clientId, buffer); DIAG(F("SEND %d:%s"), clientId, buffer);

View File

@ -1,7 +1,7 @@
/* /*
* © 2021 Fred Decker * © 2021 Fred Decker
* © 2021 Fred Decker * © 2021 Fred Decker
* © 2020-2021 Chris Harlow * © 2020-2025 Chris Harlow
* © 2020, Chris Harlow. All rights reserved. * © 2020, Chris Harlow. All rights reserved.
* © 2020, Harald Barth. * © 2020, Harald Barth.
* *
@ -26,6 +26,7 @@
#include "RingStream.h" #include "RingStream.h"
#include "CommandDistributor.h" #include "CommandDistributor.h"
#include "DIAG.h" #include "DIAG.h"
#include "Websockets.h"
WifiInboundHandler * WifiInboundHandler::singleton; WifiInboundHandler * WifiInboundHandler::singleton;
@ -67,8 +68,13 @@ void WifiInboundHandler::loop1() {
if (pendingCipsend && millis()-lastCIPSEND > CIPSENDgap) { if (pendingCipsend && millis()-lastCIPSEND > CIPSENDgap) {
if (Diag::WIFI) DIAG( F("WiFi: [[CIPSEND=%d,%d]]"), clientPendingCIPSEND, currentReplySize); // add allowances for websockets
StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), clientPendingCIPSEND, currentReplySize); 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; pendingCipsend=false;
return; 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 // This is a Finite State Automation (FSA) handling the inbound bytes from an ES AT command processor
WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() { WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
const char WebSocketKeyName[]="Sec-WebSocket-Key: ";
static byte prescanPoint=0;
while (wifiStream->available()) { while (wifiStream->available()) {
int ch = wifiStream->read(); int ch = wifiStream->read();
@ -112,9 +121,12 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
} }
if (ch=='>') { 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++) { for (int i=0;i<currentReplySize;i++) {
int cout=outboundRing->read(); int cout=outboundRing->read();
if (websocket && (cout=='\n')) cout='\r';
wifiStream->write(cout); wifiStream->write(cout);
if (Diag::WIFI) StringFormatter::printEscape(cout); // DIAG in disguise if (Diag::WIFI) StringFormatter::printEscape(cout); // DIAG in disguise
} }
@ -195,14 +207,19 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
break; break;
} }
if (Diag::WIFI) DIAG(F("Wifi inbound data(%d:%d):"),runningClientId,dataLength); 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 // This input would overflow the inbound ring, ignore it
loopState=IPD_IGNORE_DATA; loopState=IPD_IGNORE_DATA;
if (Diag::WIFI) DIAG(F("Wifi OVERFLOW IGNORING:")); if (Diag::WIFI) DIAG(F("Wifi OVERFLOW IGNORING:"));
break; break;
} }
inboundRing->mark(runningClientId); inboundRing->mark(runningClientId);
loopState=IPD_DATA; prescanPoint=0;
loopState=(dataLength>100)? IPD_PRESCAN: IPD_DATA;
break; break;
} }
dataLength = dataLength * 10 + (ch - '0'); dataLength = dataLength * 10 + (ch - '0');
@ -217,6 +234,38 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
} }
break; 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 case IPD_IGNORE_DATA: // ignoring data that would not fit in inbound ring
dataLength--; dataLength--;
if (dataLength == 0) loopState = ANYTHING; if (dataLength == 0) loopState = ANYTHING;

View File

@ -2,7 +2,7 @@
* © 2021 Harald Barth * © 2021 Harald Barth
* © 2021 Fred Decker * © 2021 Fred Decker
* (c) 2021 Fred Decker. All rights reserved. * (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 * This file is part of CommandStation-EX
* *
@ -55,7 +55,8 @@ class WifiInboundHandler {
IPD6_LENGTH, // got +IPD,c, reading length IPD6_LENGTH, // got +IPD,c, reading length
IPD_DATA, // got +IPD,c,ll,: collecting data 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_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_ID, // clientid prefix to CONNECTED / CLOSED
GOT_CLIENT_ID2 // clientid prefix to CONNECTED / CLOSED GOT_CLIENT_ID2 // clientid prefix to CONNECTED / CLOSED
}; };
@ -67,7 +68,7 @@ class WifiInboundHandler {
void purgeCurrentCIPSEND(); void purgeCurrentCIPSEND();
Stream * wifiStream; 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 OUTBOUND_RING = sizeof(void*)==2?2048:8192;
static const int CIPSENDgap=100; // millis() between retries of cipsend. static const int CIPSENDgap=100; // millis() between retries of cipsend.

206
libsha1.cpp Normal file
View File

@ -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

26
libsha1.h Normal file
View File

@ -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