2020-10-22 19:25:20 +02:00
/*
2020-11-10 10:58:01 +01:00
* © 2020 Gregor Baues . All rights reserved .
2020-10-22 19:25:20 +02:00
*
2020-11-10 10:58:01 +01:00
* 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 .
*
* THE SOFTWARE IS PROVIDED " AS IS " , WITHOUT WARRANTY OF ANY KIND , EXPRESS
* OR IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY ,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT . IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM , DAMAGES OR OTHER
* LIABILITY , WHETHER IN AN ACTION OF CONTRACT , TORT OR OTHERWISE , ARISING
* FROM , OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE .
*
* See the GNU General Public License for more details < https : //www.gnu.org/licenses/>
2020-10-22 19:25:20 +02:00
*/
# include <Arduino.h>
2020-11-09 15:41:17 +01:00
# include "NetworkDiag.h"
2020-10-22 19:25:20 +02:00
# 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"
2020-11-02 10:53:02 +01:00
# include "WiThrottle.h"
2020-10-26 10:29:40 +01:00
# include "MemStream.h"
2020-10-23 21:30:56 +02:00
2020-10-27 21:33:16 +01:00
DCCEXParser dccParser ;
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 ;
2020-11-09 15:41:17 +01:00
uint16_t _rseq [ MAX_SOCK_NUM ] = { 0 } ; // sequence number for packets recieved per connection
uint16_t _sseq [ MAX_SOCK_NUM ] = { 0 } ; // sequence number for replies send per connection
uint16_t _pNum = 0 ; // number of total packets recieved
uint64_t _tPayload = 0 ; // number of total bytes recieved
unsigned int _nCmds = 0 ; // total number of commands processed
2020-10-22 19:25:20 +02:00
2020-11-02 10:53:02 +01:00
char protocolName [ 5 ] [ 11 ] = { " JMRI " , " WITHROTTLE " , " HTTP " , " DIAG " , " UNKNOWN " } ; // change for Progmem
2020-11-09 15:41:17 +01:00
bool diagNetwork = false ; // if true diag data will be send to the connected telnet client
uint8_t diagNetworkClient = 0 ; // client id for diag output
2020-10-22 19:25:20 +02:00
2020-10-23 21:30:56 +02:00
# ifdef DCCEX_ENABLED
2020-11-02 10:53:02 +01:00
void dumpRingStreamBuffer ( byte * b , int len )
{
2020-11-09 15:41:17 +01:00
TRC ( F ( " RingStream buffer length [%d] out of [%d] bytes " ) , strlen ( ( char * ) b ) , len ) ;
TRC ( F ( " %e " ) , b ) ;
2020-11-02 10:53:02 +01:00
}
RingStream streamer ( 512 ) ; // buffer into which to feed the commands for handling; there will not be an immediate reply
// as this is async written to another RingStream i.e. we have to see where in the loop we
// generate the replies.
void sendWiThrottleToDCC ( Connection * c , TransportProcessor * t , bool blocking )
{
2020-11-09 18:13:11 +01:00
2020-11-02 10:53:02 +01:00
byte * _buffer = streamer . getBuffer ( ) ;
memset ( _buffer , 0 , 512 ) ; // clear out the _buffer
WiThrottle * wt = WiThrottle : : getThrottle ( c - > id ) ; // get a throttle for the Connection; will be created if it doesn't exist
2020-11-09 18:13:11 +01:00
TRC ( F ( " WiThrottle [%x:%x] parsing: [%e] " ) , wt , _buffer , t - > command ) ;
2020-11-02 10:53:02 +01:00
wt - > parse ( & streamer , ( byte * ) t - > command ) ; // get the response; not all commands will produce a reply
if ( streamer . count ( ) ! = - 1 )
{
dumpRingStreamBuffer ( _buffer , 512 ) ;
2020-11-09 18:13:11 +01:00
TRC ( F ( " UDP %x " ) , t - > udp ) ;
if ( t - > udp ! = 0 ) {
TRC ( F ( " Sending UDP WiThrottle response ... " ) ) ;
t - > udp - > beginPacket ( t - > udp - > remoteIP ( ) , t - > udp - > remotePort ( ) ) ;
t - > udp - > write ( _buffer , strlen ( ( char * ) _buffer ) ) ;
t - > udp - > endPacket ( ) ;
} else if ( c - > client - > connected ( ) ) {
2020-11-02 10:53:02 +01:00
c - > client - > write ( _buffer , strlen ( ( char * ) _buffer ) ) ;
}
}
2020-11-09 15:41:17 +01:00
streamer . resetStream ( ) ;
2020-11-02 10:53:02 +01:00
}
void sendJmriToDCC ( Connection * c , TransportProcessor * t , bool blocking )
2020-10-23 21:30:56 +02:00
{
2020-10-27 21:33:16 +01:00
MemStream streamer ( ( byte * ) t - > command , MAX_ETH_BUFFER , MAX_ETH_BUFFER , true ) ;
2020-10-23 21:30:56 +02:00
2020-11-09 15:41:17 +01:00
DBG ( F ( " DCC parsing: [%e] " ) , t - > command ) ;
2020-10-26 10:29:40 +01:00
// as we use buffer for recv and send we have to reset the write position
2020-10-27 21:33:16 +01:00
streamer . setBufferContentPosition ( 0 , 0 ) ;
dccParser . parse ( & streamer , ( byte * ) t - > command , true ) ; // set to true to that the execution in DCC is sync
2020-10-26 10:29:40 +01:00
2020-10-27 21:33:16 +01:00
if ( streamer . available ( ) = = 0 )
2020-10-26 10:29:40 +01:00
{
2020-11-09 15:41:17 +01:00
DBG ( F ( " No response " ) ) ;
2020-10-26 10:29:40 +01:00
}
else
{
2020-10-27 21:33:16 +01:00
t - > command [ streamer . available ( ) ] = ' \0 ' ; // mark end of buffer, so it can be used as a string later
2020-11-09 15:41:17 +01:00
DBG ( F ( " Response: %s " ) , t - > command ) ;
2020-10-26 10:29:40 +01:00
if ( c - > client - > connected ( ) )
{
2020-10-27 21:33:16 +01:00
c - > client - > write ( ( byte * ) t - > command , streamer . available ( ) ) ;
2020-10-26 10:29:40 +01:00
}
}
2020-10-23 21:30:56 +02:00
}
2020-11-02 10:53:02 +01: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
* before ending it .
*
* @ param stream Actually the Client to whom to send the reply . As Clients implement Print this is working
* @ param t TransportProcessor used for accessing the buffers to be send
* @ param blocking if set to true will instruct the DCC code to not use the async callback functions
*/
void sendToDCC ( Connection * c , TransportProcessor * t , bool blocking )
{
switch ( c - > p )
{
case WITHROTTLE :
{
sendWiThrottleToDCC ( c , t , blocking ) ;
break ;
}
case DCCEX :
{
sendJmriToDCC ( c , t , blocking ) ;
break ;
}
case N_DIAG :
case HTTP :
case UNKNOWN_PROTOCOL :
{
// we shall never get here they should have been caught before
break ;
}
}
}
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 ;
2020-11-02 10:53:02 +01:00
}
else
{
2020-10-26 10:29:40 +01:00
response = ( byte * ) command ;
2020-10-23 21:30:56 +02:00
}
2020-11-09 15:41:17 +01:00
DBG ( 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 ] + + ;
2020-11-09 15:41:17 +01:00
DBG ( F ( " Send " ) ) ;
2020-10-23 21:30:56 +02:00
}
} ;
# 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 ;
2020-11-09 15:41:17 +01:00
INFO ( F ( " \n Diagnostics routed to network client " ) ) ;
NetworkDiag : : setDiagOut ( c ) ;
2020-10-22 19:25:20 +02:00
diagNetwork = true ;
diagNetworkClient = c - > id ;
break ;
}
default :
{
// here we don't know
p = UNKNOWN_PROTOCOL ;
break ;
}
}
2020-11-09 15:41:17 +01:00
INFO ( F ( " Client speaks: [%s] " ) , protocolName [ p ] ) ;
2020-10-22 19:25:20 +02:00
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-27 09:52:06 +01:00
uint8_t * _buffer = t - > buffer ;
2020-10-26 10:29:40 +01:00
2020-11-09 15:41:17 +01:00
DBG ( F ( " Buffer: [%e] " ) , _buffer ) ;
2020-10-27 09:52:06 +01:00
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 )
{
2020-11-09 15:41:17 +01:00
// DBG(F("Copy overflow to command: %e"), c->overflow);
2020-10-27 09:52:06 +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-11-09 15:41:17 +01:00
// DBG(F("Possible 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-11-09 15:41:17 +01:00
// DBG(F("%d %d %d %c"),k,j,i, buffer[i]);
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-11-09 15:41:17 +01:00
// DBG(F("New buffer: [%s] New overflow: [%s]"), (char*) buffer, c->overflow );
2020-10-22 19:25:20 +02:00
}
// breakup the buffer using its changed length
i = 0 ;
2020-10-27 09:52:06 +01:00
k = strlen ( t - > command ) ; // current length of the command buffer telling us where to start copy in
2020-10-26 10:29:40 +01:00
l = strlen ( ( char * ) _buffer ) ;
2020-11-09 15:41:17 +01:00
// DBG(F("Command buffer cid[%d]: [%s]:[%d:%d:%d:%x]"), c->id, t->command, i, l, k, c->delimiter );
unsigned long _startT = micros ( ) ;
_nCmds = 0 ;
2020-10-22 19:25:20 +02:00
while ( i < l )
{
2020-11-09 15:41:17 +01:00
// DBG(F("l: %d - k: %d - i: %d - %c"), l, k, i, _buffer[i]);
2020-10-27 09:52:06 +01:00
t - > command [ k ] = _buffer [ i ] ;
2020-10-26 10:29:40 +01:00
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-27 09:52:06 +01:00
t - > command [ k + 1 ] = ' \0 ' ;
2020-10-22 19:25:20 +02:00
2020-11-09 15:41:17 +01:00
DBG ( F ( " Command: [%d:%e] " ) , _rseq [ c - > id ] , t - > command ) ;
2020-10-23 21:30:56 +02:00
# ifdef DCCEX_ENABLED
2020-11-09 15:41:17 +01:00
2020-11-02 10:53:02 +01:00
sendToDCC ( c , t , true ) ; // send the command into the parser and replies back to the client
2020-10-23 21:30:56 +02:00
# else
2020-11-02 10:53:02 +01:00
sendReply ( c , t ) ; // standalone version without CS-EX integration
2020-10-23 21:30:56 +02:00
# endif
2020-10-22 19:25:20 +02:00
_rseq [ c - > id ] + + ;
2020-11-09 15:41:17 +01:00
_nCmds + + ;
2020-10-22 19:25:20 +02:00
j = 0 ;
k = 0 ;
}
else
{
k + + ;
}
i + + ;
}
2020-11-09 15:41:17 +01:00
unsigned long _endT = micros ( ) ;
char time [ 10 ] = { 0 } ;
ultoa ( _endT - _startT , time , 10 ) ;
INFO ( F ( " [%d] Commands processed in [%s]uS \n " ) , _nCmds , time ) ;
2020-10-22 19:25:20 +02:00
}
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 ) ;
2020-10-28 13:20:04 +01:00
sprintf ( ( char * ) reply , " ERROR: malformed content in [%s] \n " , 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-11-02 10:53:02 +01:00
c - > isProtocolDefined = false ; // reset the protocol to not defined so that we can recover the next time
2020-10-22 19:25:20 +02:00
}
}
2020-10-26 10:29:40 +01:00
void jmriProcessor ( Connection * c , TransportProcessor * t )
2020-10-22 19:25:20 +02:00
{
2020-11-09 15:41:17 +01:00
DBG ( F ( " Processing JMRI ... " ) ) ;
2020-10-26 10:29:40 +01:00
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-11-09 15:41:17 +01:00
DBG ( F ( " Processing WiThrottle ... " ) ) ;
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
*/
2020-11-09 15:41:17 +01:00
void TransportProcessor : : readStream ( Connection * c , bool read )
2020-10-22 19:25:20 +02:00
{
2020-11-09 15:41:17 +01:00
int count = 0 ;
// read bytes from a TCP client if required
if ( read ) {
int len = 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 [ len ] = 0 ;
count = len ;
} else {
count = strlen ( ( char * ) buffer ) ;
}
2020-10-22 19:25:20 +02:00
// 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 :
{
2020-11-09 15:41:17 +01:00
INFO ( F ( " Requests will not be handeled and packet echoed back " ) ) ;
2020-10-22 19:25:20 +02:00
c - > appProtocolHandler = ( appProtocolCallback ) echoProcessor ;
break ;
}
2020-10-27 09:52:06 +01:00
}
2020-10-22 19:25:20 +02:00
}
2020-11-09 15:41:17 +01:00
_pNum + + ;
_tPayload = _tPayload + count ;
2020-11-02 10:53:02 +01:00
# ifdef DCCEX_ENABLED
2020-11-09 15:41:17 +01:00
INFO ( F ( " Client #[%d] received packet #[%d] of size:[%d/%d] " ) , c - > id , _pNum , count , _tPayload ) ;
2020-10-26 11:09:39 +01:00
# else
2020-10-26 10:29:40 +01:00
IPAddress remote = c - > client - > remoteIP ( ) ;
2020-11-09 15:41:17 +01:00
INFO ( F ( " Client #[%d] Received packet #[%d] of size:[%d] from [%d.%d.%d.%d] " ) , c - > id , _pNum , count , remote [ 0 ] , remote [ 1 ] , remote [ 2 ] , remote [ 3 ] ) ;
2020-10-26 11:09:39 +01:00
# endif
buffer [ count ] = ' \0 ' ; // terminate the string properly
2020-11-09 15:41:17 +01:00
INFO ( F ( " Packet: [%e] " ) , buffer ) ;
2020-10-22 19:25:20 +02:00
// 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
{
2020-11-09 15:41:17 +01:00
DBG ( F ( " DCC parsing: [%e] " ) , command ) ;
2020-10-22 19:25:20 +02:00
// echo back (as mock parser )
2020-10-23 21:30:56 +02:00
StringFormatter : : send ( stream , F ( " reply to: %s " ) , command ) ;
2020-11-09 15:41:17 +01:00
}