mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-01-28 21:23:06 +01:00
Squashed commit of the following:
commit04b8e20773
Author: Asbelos <asbelos@btinternet.com> Date: Sat Jan 18 10:13:32 2025 +0000 Fix websocket binary mask issue Includes <D WEBSOCKET ON> commit5600382ae4
Merge:5941866
137008c
Author: Asbelos <asbelos@btinternet.com> Date: Sat Jan 18 08:58:30 2025 +0000 Merge branch 'devel' into devel-websockets commit59418668e2
Author: Asbelos <asbelos@btinternet.com> Date: Fri Jan 17 13:16:29 2025 +0000 WIfiESP websock fix commitc4e2146bd1
Author: Asbelos <asbelos@btinternet.com> Date: Thu Jan 16 08:37:38 2025 +0000 websockets broadcast commit2591d100ca
Author: Asbelos <asbelos@btinternet.com> Date: Wed Jan 15 19:38:41 2025 +0000 browser barf on \n commit67f836e886
Author: Asbelos <asbelos@btinternet.com> Date: Wed Jan 15 09:44:04 2025 +0000 5.5.5 + websockets Ethernet not yet included
This commit is contained in:
parent
d14aa46d51
commit
07691e3985
@ -31,6 +31,7 @@
|
||||
#include "DCC.h"
|
||||
#include "TrackManager.h"
|
||||
#include "StringFormatter.h"
|
||||
#include "Websockets.h"
|
||||
|
||||
// variables to hold clock time
|
||||
int16_t lastclocktime;
|
||||
@ -44,6 +45,7 @@ template<typename... Targs> void CommandDistributor::broadcastReply(clientType t
|
||||
broadcastBufferWriter->flush();
|
||||
StringFormatter::send(broadcastBufferWriter, msg...);
|
||||
broadcastToClients(type);
|
||||
if (type==COMMAND_TYPE) broadcastToClients(WEBSOCKET_TYPE);
|
||||
}
|
||||
#else
|
||||
// on a single USB connection config, write direct to Serial and ignore flush/shove
|
||||
@ -56,14 +58,22 @@ template<typename... Targs> void CommandDistributor::broadcastReply(clientType t
|
||||
#ifdef CD_HANDLE_RING
|
||||
// wifi or ethernet ring streams with multiple client types
|
||||
RingStream * CommandDistributor::ring=0;
|
||||
CommandDistributor::clientType CommandDistributor::clients[8]={
|
||||
NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE};
|
||||
CommandDistributor::clientType CommandDistributor::clients[20]={
|
||||
NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,
|
||||
NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,
|
||||
NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,
|
||||
NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,
|
||||
NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE};
|
||||
|
||||
// Parse is called by Withrottle or Ethernet interface to determine which
|
||||
// Parse is called by Wifi or Ethernet interface to determine which
|
||||
// protocol the client is using and call the appropriate part of dcc++Ex
|
||||
void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream) {
|
||||
if (Diag::WIFI && Diag::CMD)
|
||||
DIAG(F("Parse C=%d T=%d B=%s"),clientId, clients[clientId], buffer);
|
||||
if (clientId>=sizeof (clients)) {
|
||||
// Caution, diag dump of buffer could corrupt ringstream
|
||||
// if headed by websocket bytes.
|
||||
DIAG(F("::parse invalid client=%d"),clientId);
|
||||
return;
|
||||
}
|
||||
ring=stream;
|
||||
|
||||
// First check if the client is not known
|
||||
@ -72,22 +82,40 @@ 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;
|
||||
// websockets will have replied already
|
||||
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
|
||||
@ -131,7 +159,7 @@ void CommandDistributor::broadcastToClients(clientType type) {
|
||||
for (byte clientId=0; clientId<sizeof(clients); clientId++) {
|
||||
if (clients[clientId]==type) {
|
||||
//DIAG(F("CD mark client %d"), clientId);
|
||||
ring->mark(clientId);
|
||||
ring->mark(clientId | (type==WEBSOCKET_TYPE? Websockets::WEBSOCK_CLIENT_MARKER : 0));
|
||||
ring->print(broadcastBufferWriter->getString());
|
||||
//DIAG(F("CD commit client %d"), clientId);
|
||||
ring->commit();
|
||||
@ -191,7 +219,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;
|
||||
|
@ -37,13 +37,13 @@
|
||||
|
||||
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;
|
||||
#ifdef CD_HANDLE_RING
|
||||
static RingStream * ring;
|
||||
static clientType clients[8];
|
||||
static clientType clients[20];
|
||||
#endif
|
||||
public :
|
||||
static void parse(byte clientId,byte* buffer, RingStream * ring);
|
||||
|
@ -1255,6 +1255,10 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
case "LCN"_hk: // <D LCN ON/OFF>
|
||||
Diag::LCN = onOff;
|
||||
return true;
|
||||
|
||||
case "WEBSOCKET"_hk: // <D WEBSOCKET ON/OFF>
|
||||
Diag::WEBSOCKET = onOff;
|
||||
return true;
|
||||
#endif
|
||||
#ifndef DISABLE_EEPROM
|
||||
case "EEPROM"_hk: // <D EEPROM NumEntries>
|
||||
|
41
Release_Notes/websocketTester.html
Normal file
41
Release_Notes/websocketTester.html
Normal file
@ -0,0 +1,41 @@
|
||||
<html>
|
||||
<!-- Minimalist test page for the DCCEX websocket API.-->
|
||||
<head>
|
||||
<script>
|
||||
let socket = new WebSocket("ws://192.168.1.242:2560","DCCEX");
|
||||
|
||||
// send message from the form
|
||||
var sender = function() {
|
||||
var msg=document.getElementById('message').value;
|
||||
socket.send(msg);
|
||||
}
|
||||
// message received - show the message in div#messages
|
||||
socket.onmessage = function(event) {
|
||||
let message = event.data;
|
||||
|
||||
let messageElem = document.createElement('div');
|
||||
messageElem.textContent = message;
|
||||
document.getElementById('messages').prepend(messageElem);
|
||||
}
|
||||
socket.onerror = function(event) {
|
||||
let message = event.data;
|
||||
let messageElem = document.createElement('div');
|
||||
messageElem.textContent = message;
|
||||
document.getElementById('messages').prepend(messageElem);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
This is a minimalist test page for the DCCEX websocket API.
|
||||
It demonstrates the Websocket connection and how to send
|
||||
or receive websocket traffic.
|
||||
The connection string must be edited to address your command station
|
||||
correctly.<p>
|
||||
<!-- message form -->
|
||||
|
||||
<input type="text" id="message">
|
||||
<input type="button" value="Send" onclick="sender();">
|
||||
<!-- div with messages -->
|
||||
<div id="messages"></div>
|
||||
</body>
|
||||
</html>
|
@ -28,6 +28,7 @@ bool Diag::WITHROTTLE=false;
|
||||
bool Diag::ETHERNET=false;
|
||||
bool Diag::LCN=false;
|
||||
bool Diag::RAILCOM=false;
|
||||
bool Diag::WEBSOCKET=false;
|
||||
|
||||
|
||||
|
||||
|
@ -31,6 +31,7 @@ class Diag {
|
||||
static bool ETHERNET;
|
||||
static bool LCN;
|
||||
static bool RAILCOM;
|
||||
static bool WEBSOCKET;
|
||||
|
||||
};
|
||||
|
||||
|
211
Websockets.cpp
Normal file
211
Websockets.cpp
Normal file
@ -0,0 +1,211 @@
|
||||
/*
|
||||
* © 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.
|
||||
[Note that the WifiInboundHandler has a shortcut to detecting this so that
|
||||
it does not need to use up 500+ bytes of RAM just to get at the one parameter that
|
||||
actually means something.]
|
||||
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
|
||||
if (Diag::WEBSOCKET) DIAG(F("Websock check connection"));
|
||||
/* 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;
|
||||
|
||||
if (Diag::WEBSOCKET) 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));
|
||||
|
||||
if (Diag::WEBSOCKET) 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;
|
||||
if (Diag::WEBSOCKET) 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;
|
||||
}
|
||||
if (Diag::WEBSOCKET) 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("Websock 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("Websock unknown opcode 0x%x"),opcode);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
byte * payload=mask+4;
|
||||
for (int i=0;i<payloadLength;i++) {
|
||||
payload[i]^=mask[i%4];
|
||||
}
|
||||
|
||||
if (Diag::WEBSOCKET) DIAG(F("Websoc payload=%s"),payload);
|
||||
|
||||
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
34
Websockets.h
Normal 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
|
@ -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'
|
||||
for(int i=0;i<count;i++) {
|
||||
int c = outboundRing->read();
|
||||
if (c >= 0) // Panic check, should never be false
|
||||
buffer[i] = (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';
|
||||
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) {
|
||||
DIAG(F("Ringread fail at %d"), i);
|
||||
break;
|
||||
}
|
||||
// websocket implementations at browser end can barf at \n
|
||||
if (useWebsocket && (c == '\n')) c = '\r';
|
||||
buffer[i + wsHeaderLen] = (char)c;
|
||||
}
|
||||
// buffer filled, end with '\0' so we can use it as C string
|
||||
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);
|
||||
|
@ -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;
|
||||
}
|
||||
@ -80,7 +86,9 @@ void WifiInboundHandler::loop1() {
|
||||
int count=inboundRing->count();
|
||||
if (Diag::WIFI) DIAG(F("Wifi EXEC: %d %d:"),clientId,count);
|
||||
byte cmd[count+1];
|
||||
for (int i=0;i<count;i++) cmd[i]=inboundRing->read();
|
||||
// Copy raw bytes to avoid websocket masked data being
|
||||
// confused with a ram-saving flash insert marker.
|
||||
for (int i=0;i<count;i++) cmd[i]=inboundRing->readRawByte();
|
||||
cmd[count]=0;
|
||||
if (Diag::WIFI) DIAG(F("%e"),cmd);
|
||||
|
||||
@ -94,6 +102,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 +123,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 +209,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 +236,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 for websock 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) {
|
||||
if (Diag::WEBSOCKET) 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;
|
||||
|
@ -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.
|
||||
|
206
libsha1.cpp
Normal file
206
libsha1.cpp
Normal 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
26
libsha1.h
Normal 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
|
27
objdump.bat
27
objdump.bat
@ -1,16 +1,17 @@
|
||||
ECHO ON
|
||||
FOR /F "delims=" %%i IN ('dir %TMP%\arduino_build_* /b /ad-h /t:c /od') DO SET a=%%i
|
||||
echo Most recent subfolder: %a% >%TMP%\OBJDUMP_%a%.txt
|
||||
SET ELF=%TMP%\%a%\CommandStation-EX.ino.elf
|
||||
FOR /F "delims=" %%i IN ('dir %TMP%\arduino\sketches\CommandStation-EX.ino.elf /s /b /o-D') DO SET ELF=%%i
|
||||
SET DUMP=%TEMP%\OBJDUMP.txt
|
||||
echo Most recent subfolder: %ELF% >%DUMP%
|
||||
|
||||
set PATH="C:\Program Files (x86)\Arduino\hardware\tools\avr\bin\";%PATH%
|
||||
avr-objdump --private=mem-usage %ELF% >>%TMP%\OBJDUMP_%a%.txt
|
||||
ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt
|
||||
avr-objdump -x -C %ELF% | find ".text" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt
|
||||
ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt
|
||||
avr-objdump -x -C %ELF% | find ".data" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt
|
||||
ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt
|
||||
avr-objdump -x -C %ELF% | find ".bss" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt
|
||||
ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt
|
||||
avr-objdump -D -S %ELF% >>%TMP%\OBJDUMP_%a%.txt
|
||||
%TMP%\OBJDUMP_%a%.txt
|
||||
avr-objdump --private=mem-usage %ELF% >>%DUMP%
|
||||
ECHO ++++++++++++++++++++++++++++++++++ >>%DUMP%
|
||||
avr-objdump -x -C %ELF% | find ".text" | sort /+25 /R >>%DUMP%
|
||||
ECHO ++++++++++++++++++++++++++++++++++ >>%DUMP%
|
||||
avr-objdump -x -C %ELF% | find ".data" | sort /+25 /R >>%DUMP%
|
||||
ECHO ++++++++++++++++++++++++++++++++++ >>%DUMP%
|
||||
avr-objdump -x -C %ELF% | find ".bss" | sort /+25 /R >>%DUMP%
|
||||
ECHO ++++++++++++++++++++++++++++++++++ >>%DUMP%
|
||||
avr-objdump -D -S %ELF% >>%DUMP%
|
||||
%DUMP%
|
||||
EXIT
|
||||
|
Loading…
Reference in New Issue
Block a user