2020-10-22 19:25:20 +02:00
/*
* © 2020 , Gregor Baues . All rights reserved .
*
* 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/>.
*/
# include <Arduino.h>
# include "DIAG.h"
# include "NetworkInterface.h"
# include "HttpRequest.h"
# include "TransportProcessor.h"
2020-10-23 21:30:56 +02:00
# ifdef DCCEX_ENABLED
2020-10-26 10:29:40 +01:00
# include "DCCEXParser.h"
# include "MemStream.h"
2020-10-23 21:30:56 +02:00
2020-10-26 10:29:40 +01:00
DCCEXParser ethParser ;
2020-10-22 19:25:20 +02:00
2020-10-26 10:29:40 +01:00
# endif
2020-10-22 19:25:20 +02:00
HttpRequest httpReq ;
uint16_t _rseq [ MAX_SOCK_NUM ] = { 0 } ;
uint16_t _sseq [ MAX_SOCK_NUM ] = { 0 } ;
2020-10-26 10:29:40 +01:00
char protocolName [ 4 ] [ 11 ] = { " JMRI " , " WITHROTTLE " , " HTTP " , " UNKNOWN " } ; // change for Progmem
2020-10-22 19:25:20 +02:00
bool diagNetwork = false ;
uint8_t diagNetworkClient = 0 ;
2020-10-23 21:30:56 +02:00
# ifdef DCCEX_ENABLED
/**
* @ brief Sending a reply by using the StringFormatter ( this will result in every byte send individually which may / will create an important Network overhead ) .
* Here we hook back into the DCC code for actually processing the command using a DCCParser . Alternatively we could use MemeStream in order to build the entiere reply
* before ending it .
*
* @ param stream Actually the Client to whom to send the reply . As Clients implement Print this is working
2020-10-26 10:29:40 +01:00
* @ param t TransportProcessor used for accessing the buffers to be send
2020-10-23 21:30:56 +02:00
* @ param blocking if set to true will instruct the DCC code to not use the async callback functions
*/
2020-10-26 10:29:40 +01:00
void sendToDCC ( Connection * c , TransportProcessor * t , bool blocking )
2020-10-23 21:30:56 +02:00
{
2020-10-26 10:29:40 +01:00
static MemStream * streamer = new MemStream ( ( byte * ) command , MAX_ETH_BUFFER , MAX_ETH_BUFFER , true ) ;
2020-10-23 21:30:56 +02:00
DIAG ( F ( " DCC parsing: [%e] \n " ) , command ) ;
2020-10-26 10:29:40 +01:00
// as we use buffer for recv and send we have to reset the write position
streamer - > setBufferContentPosition ( 0 , 0 ) ;
ethParser . parse ( streamer , ( byte * ) t - > command , true ) ; // set to true to that the execution in DCC is sync
if ( streamer - > available ( ) = = 0 )
{
DIAG ( F ( " No response \n " ) ) ;
}
else
{
command [ streamer - > available ( ) ] = ' \0 ' ; // mark end of buffer, so it can be used as a string later
DIAG ( F ( " Response: %s \n " ) , t - > command ) ;
if ( c - > client - > connected ( ) )
{
c - > client - > write ( ( byte * ) t - > command , streamer - > available ( ) ) ;
}
}
2020-10-23 21:30:56 +02:00
}
# else
/**
* @ brief Sending a reply without going through the StringFormatter . Sends the repy in one go
*
* @ param client Client who send the command to which the reply shall be send
* @ param command Command initaliy recieved to be echoed back
*/
2020-10-26 10:29:40 +01:00
void sendReply ( Connection * c , TransportProcessor * t )
{
byte reply [ MAX_ETH_BUFFER ] ;
byte * response ;
2020-10-23 21:30:56 +02:00
char * number ;
2020-10-26 10:29:40 +01:00
char * command = t - > command ;
2020-10-23 21:30:56 +02:00
char seqNumber [ 6 ] ;
int i = 0 ;
2020-10-26 10:29:40 +01:00
memset ( reply , 0 , MAX_ETH_BUFFER ) ; // reset reply
// This expects messages to be send with a trailing sequence number <R 1 1 1:0>
// as of my stress test program to verify the arrival of messages
number = strrchr ( command , ' : ' ) ; // replace the int after the last ':' if number != 0
if ( number ! = 0 )
{
while ( & command [ i ] ! = number )
{ // copy command into the reply upto the last ':'
reply [ i ] = command [ i ] ;
i + + ;
}
strcat ( ( char * ) reply , " : " ) ;
itoa ( _sseq [ c - > id ] , seqNumber , 10 ) ;
strcat ( ( char * ) reply , seqNumber ) ;
strcat ( ( char * ) reply , " > " ) ;
response = reply ;
} else {
response = ( byte * ) command ;
2020-10-23 21:30:56 +02:00
}
2020-10-26 10:29:40 +01:00
DIAG ( F ( " Response: [%e] " ) , ( char * ) response ) ;
2020-10-23 21:30:56 +02:00
if ( c - > client - > connected ( ) )
{
2020-10-26 10:29:40 +01:00
c - > client - > write ( response , strlen ( ( char * ) response ) ) ;
2020-10-23 21:30:56 +02:00
_sseq [ c - > id ] + + ;
DIAG ( F ( " send \n " ) ) ;
}
} ;
# endif
2020-10-22 19:25:20 +02:00
/**
* @ brief creates a HttpRequest object for the user callback . Some conditions apply esp reagrding the length of the items in the Request
* can be found in @ file HttpRequest . h
*
* @ param client Client object from whom we receievd the data
* @ param c id of the Client object
*/
2020-10-26 10:29:40 +01:00
void httpProcessor ( Connection * c , TransportProcessor * t )
2020-10-22 19:25:20 +02:00
{
2020-10-26 10:29:40 +01:00
if ( httpReq . callback = = 0 )
return ; // no callback i.e. nothing to do
2020-10-22 21:41:56 +02:00
/**
* @ todo look for jmri formatted uris and execute those if there is no callback . If no command found ignore and
* ev . send a 401 error back
*/
2020-10-22 19:25:20 +02:00
uint8_t i , l = 0 ;
ParsedRequest preq ;
2020-10-26 10:29:40 +01:00
l = strlen ( ( char * ) t - > buffer ) ;
2020-10-22 19:25:20 +02:00
for ( i = 0 ; i < l ; i + + )
{
2020-10-26 10:29:40 +01:00
httpReq . parseRequest ( ( char ) t - > buffer [ i ] ) ;
2020-10-22 19:25:20 +02:00
}
if ( httpReq . endOfRequest ( ) )
{
preq = httpReq . getParsedRequest ( ) ;
httpReq . callback ( & preq , c - > client ) ;
httpReq . resetRequest ( ) ;
} // else do nothing and continue with the next packet
}
/**
* @ brief Set the App Protocol . The detection id done upon the very first message recieved . The client will then be bound to that protocol . Its very brittle
* as e . g . The N message as first message for WiThrottle is not a requirement by the protocol ; If any client talking Withrottle doesn ' t implement this the detection
* will default to JMRI . For HTTP we base this only on a subset of th HTTP verbs which can be used .
*
* @ param a First character of the recieved buffer upon first connection
* @ param b Second character of the recieved buffer upon first connection
* @ return appProtocol
*/
appProtocol setAppProtocol ( char a , char b , Connection * c )
{
appProtocol p ;
switch ( a )
{
case ' G ' : // GET
case ' C ' : // CONNECT
case ' O ' : // OPTIONS
case ' T ' : // TRACE
{
p = HTTP ;
break ;
}
case ' D ' : // DELETE or D plux hex value
{
if ( b = = ' E ' )
{
p = HTTP ;
}
else
{
p = WITHROTTLE ;
}
break ;
}
case ' P ' :
{
if ( b = = ' T ' | | b = = ' R ' )
{
p = WITHROTTLE ;
}
else
{
p = HTTP ; // PUT / PATCH / POST
}
break ;
}
case ' H ' :
{
if ( b = = ' U ' )
{
p = WITHROTTLE ;
}
else
{
p = HTTP ; // HEAD
}
break ;
}
case ' M ' :
case ' * ' :
case ' R ' :
case ' Q ' : // That doesn't make sense as it's the Q or close on app level
case ' N ' :
{
p = WITHROTTLE ;
break ;
}
case ' < ' :
{
p = DCCEX ;
break ;
}
2020-10-26 10:29:40 +01:00
case ' # ' :
{
2020-10-22 19:25:20 +02:00
p = DCCEX ;
DIAG ( F ( " \n Diagnostics routed to network client \n " ) ) ;
StringFormatter : : setDiagOut ( c ) ;
diagNetwork = true ;
diagNetworkClient = c - > id ;
break ;
}
default :
{
// here we don't know
p = UNKNOWN_PROTOCOL ;
break ;
}
}
DIAG ( F ( " \n Client speaks: [%s] \n " ) , protocolName [ p ] ) ;
return p ;
}
/**
* @ brief Parses the buffer to extract commands to be executed
*
*/
2020-10-26 10:29:40 +01:00
void processStream ( Connection * c , TransportProcessor * t )
2020-10-22 19:25:20 +02:00
{
uint8_t i , j , k , l = 0 ;
2020-10-26 10:29:40 +01:00
uint8_t * _buffer = t - > buffer ;
DIAG ( F ( " \n Buffer: [%e] \n " ) , _buffer ) ;
memset ( t - > command , 0 , MAX_JMRI_CMD ) ; // clear out the command
2020-10-22 19:25:20 +02:00
// copy overflow into the command
if ( ( i = strlen ( c - > overflow ) ) ! = 0 )
{
// DIAG(F("\nCopy overflow to command: %e"), c->overflow);
2020-10-26 10:29:40 +01:00
strncpy ( t - > command , c - > overflow , i ) ;
2020-10-22 19:25:20 +02:00
k = i ;
}
// reset the overflow
memset ( c - > overflow , 0 , MAX_OVERFLOW ) ;
// check if there is again an overflow and copy if needed
2020-10-26 10:29:40 +01:00
if ( ( i = strlen ( ( char * ) _buffer ) ) = = MAX_ETH_BUFFER - 1 )
{
2020-10-23 21:30:56 +02:00
// DIAG(F("\nPossible overflow situation detected: %d "), i);
2020-10-22 19:25:20 +02:00
j = i ;
2020-10-26 10:29:40 +01:00
while ( _buffer [ i ] ! = c - > delimiter )
{
2020-10-22 19:25:20 +02:00
i - - ;
}
2020-10-23 21:30:56 +02:00
i + + ; // start of the buffer to copy
2020-10-22 19:25:20 +02:00
l = i ;
2020-10-23 21:30:56 +02:00
k = j - i ; // length to copy
2020-10-22 19:25:20 +02:00
for ( j = 0 ; j < k ; j + + , i + + )
{
2020-10-26 10:29:40 +01:00
c - > overflow [ j ] = _buffer [ i ] ;
2020-10-23 21:30:56 +02:00
// DIAG(F("\n%d %d %d %c"),k,j,i, buffer[i]); // c->overflow[j]);
2020-10-22 19:25:20 +02:00
}
2020-10-26 10:29:40 +01:00
_buffer [ l ] = ' \0 ' ; // terminate buffer just after the last '>'
2020-10-22 19:25:20 +02:00
// DIAG(F("\nNew buffer: [%s] New overflow: [%s]\n"), (char*) buffer, c->overflow );
}
// breakup the buffer using its changed length
i = 0 ;
2020-10-26 10:29:40 +01:00
k = strlen ( t - > command ) ; // current length of the command buffer telling us where to start copy in
l = strlen ( ( char * ) _buffer ) ;
2020-10-23 21:42:36 +02:00
// DIAG(F("\nCommand buffer: [%s]:[%d:%d:%d]\n"), command, i, l, k );
2020-10-22 19:25:20 +02:00
while ( i < l )
{
2020-10-23 21:30:56 +02:00
// DIAG(F("\nl: %d k: %d , i: %d"), l, k, i);
2020-10-26 10:29:40 +01:00
t - > command [ k ] = _buffer [ i ] ;
if ( _buffer [ i ] = = c - > delimiter )
2020-10-23 21:30:56 +02:00
{ // closing bracket need to fix if there is none before an opening bracket ?
2020-10-26 10:29:40 +01:00
t - > command [ k + 1 ] = ' \0 ' ;
2020-10-22 19:25:20 +02:00
2020-10-26 10:29:40 +01:00
DIAG ( F ( " Command: [%d:%e] \n " ) , _rseq [ c - > id ] , t - > command ) ;
2020-10-23 21:30:56 +02:00
# ifdef DCCEX_ENABLED
sendToDCC ( c , command , true ) ;
# else
2020-10-26 10:29:40 +01:00
sendReply ( c , t ) ;
2020-10-23 21:30:56 +02:00
# endif
2020-10-22 19:25:20 +02:00
_rseq [ c - > id ] + + ;
j = 0 ;
k = 0 ;
}
else
{
k + + ;
}
i + + ;
}
}
2020-10-26 10:29:40 +01:00
void echoProcessor ( Connection * c , TransportProcessor * t )
2020-10-22 19:25:20 +02:00
{
2020-10-26 10:29:40 +01:00
byte reply [ MAX_ETH_BUFFER ] ;
memset ( reply , 0 , MAX_ETH_BUFFER ) ;
sprintf ( ( char * ) reply , " ERROR: malformed content in [%s] " , t - > buffer ) ;
2020-10-22 19:25:20 +02:00
if ( c - > client - > connected ( ) )
{
c - > client - > write ( reply , strlen ( ( char * ) reply ) ) ;
_sseq [ c - > id ] + + ;
}
}
2020-10-26 10:29:40 +01:00
void jmriProcessor ( Connection * c , TransportProcessor * t )
2020-10-22 19:25:20 +02:00
{
2020-10-26 10:29:40 +01:00
DIAG ( F ( " Processing JMRI ... \n " ) ) ;
processStream ( c , t ) ;
2020-10-22 19:25:20 +02:00
}
2020-10-26 10:29:40 +01:00
void withrottleProcessor ( Connection * c , TransportProcessor * t )
2020-10-22 19:25:20 +02:00
{
2020-10-26 10:29:40 +01:00
processStream ( c , t ) ;
2020-10-22 19:25:20 +02:00
}
/**
* @ brief Reads what is available on the incomming TCP stream and hands it over to the protocol handler .
*
2020-10-23 21:30:56 +02:00
* @ param c Pointer to the connection struct contining relevant information handling the data from that connection
2020-10-22 19:25:20 +02:00
*/
void TransportProcessor : : readStream ( Connection * c )
{
2020-10-26 10:29:40 +01:00
2020-10-22 19:25:20 +02:00
// read bytes from a client
int count = c - > client - > read ( buffer , MAX_ETH_BUFFER - 1 ) ; // count is the amount of data ready for reading, -1 if there is no data, 0 is the connection has been closed
buffer [ count ] = 0 ;
// figure out which protocol
if ( ! c - > isProtocolDefined )
{
c - > p = setAppProtocol ( buffer [ 0 ] , buffer [ 1 ] , c ) ;
c - > isProtocolDefined = true ;
switch ( c - > p )
{
case N_DIAG :
case DCCEX :
{
c - > delimiter = ' > ' ;
c - > appProtocolHandler = ( appProtocolCallback ) jmriProcessor ;
break ;
}
case WITHROTTLE :
{
2020-10-26 10:29:40 +01:00
c - > delimiter = ' \n ' ;
2020-10-22 19:25:20 +02:00
c - > appProtocolHandler = ( appProtocolCallback ) withrottleProcessor ;
break ;
}
case HTTP :
{
c - > appProtocolHandler = ( appProtocolCallback ) httpProcessor ;
2020-10-26 10:29:40 +01:00
httpReq . callback = nwi - > getHttpCallback ( ) ;
2020-10-22 19:25:20 +02:00
break ;
}
case UNKNOWN_PROTOCOL :
{
DIAG ( F ( " Requests will not be handeled and packet echoed back \n " ) ) ;
c - > appProtocolHandler = ( appProtocolCallback ) echoProcessor ;
break ;
}
}
}
2020-10-26 10:29:40 +01:00
IPAddress remote = c - > client - > remoteIP ( ) ;
2020-10-22 19:25:20 +02:00
buffer [ count ] = ' \0 ' ; // terminate the string properly
2020-10-26 10:29:40 +01:00
DIAG ( F ( " \n Received packet of size:[%d] from [%d.%d.%d.%d] \n " ) , count , remote [ 0 ] , remote [ 1 ] , remote [ 2 ] , remote [ 3 ] ) ;
2020-10-22 19:25:20 +02:00
DIAG ( F ( " Client #: [%d] \n " ) , c - > id ) ;
DIAG ( F ( " Packet: [%e] \n " ) , buffer ) ;
// chop the buffer into CS / WiThrottle commands || assemble command across buffer read boundaries
2020-10-26 10:29:40 +01:00
c - > appProtocolHandler ( c , this ) ;
2020-10-22 19:25:20 +02:00
}
/**
* @ brief Sending a reply by using the StringFormatter ( this will result in every byte send individually which may / will create an important Network overhead ) .
* Here we hook back into the DCC code for actually processing the command using a DCCParser . Alternatively we could use MemeStream in order to build the entiere reply
2020-10-23 21:30:56 +02:00
* before ending it ( cf . Scratch pad below )
2020-10-22 19:25:20 +02:00
*
* @ param stream Actually the Client to whom to send the reply . As Clients implement Print this is working
* @ param command The reply to be send ( echo as in sendReply ( ) )
* @ param blocking if set to true will instruct the DCC code to not use the async callback functions
*/
2020-10-23 21:30:56 +02:00
void parse ( Print * stream , byte * command , bool blocking )
2020-10-22 19:25:20 +02:00
{
DIAG ( F ( " DCC parsing: [%e] \n " ) , command ) ;
// echo back (as mock parser )
2020-10-23 21:30:56 +02:00
StringFormatter : : send ( stream , F ( " reply to: %s " ) , command ) ;
2020-10-26 10:29:40 +01:00
}