// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
//
// Adapted from: github.com/tuanpmt/esp_bridge, Created on: Mar 4, 2015, Author: Minh
# include "esp8266.h"
# include "rest.h"
# include "cmd.h"
// Connection pool for REST clients. Attached MCU's just call REST_setup and this allocates
// a connection, They never call any 'free' and given that the attached MCU could restart at
// any time, we cannot really rely on the attached MCU to call 'free' ever, so better do without.
// Instead, we allocate a fixed pool of connections an round-robin. What this means is that the
// attached MCU should really use at most as many REST connections as there are slots in the pool.
# define MAX_REST 4
static RestClient restClient [ MAX_REST ] ;
static uint8_t restNum = 0xff ; // index into restClient for next slot to allocate
# define REST_CB 0xbeef0000 // fudge added to callback for arduino so we can detect problems
static void ICACHE_FLASH_ATTR
tcpclient_discon_cb ( void * arg ) {
struct espconn * pespconn = ( struct espconn * ) arg ;
RestClient * client = ( RestClient * ) pespconn - > reverse ;
// free the data buffer, if we have one
if ( client - > data ) os_free ( client - > data ) ;
client - > data = 0 ;
}
// Receive HTTP response - this hacky function assumes that the full response is received in
// one go. Sigh...
static void ICACHE_FLASH_ATTR
tcpclient_recv ( void * arg , char * pdata , unsigned short len ) {
struct espconn * pCon = ( struct espconn * ) arg ;
RestClient * client = ( RestClient * ) pCon - > reverse ;
// parse status line
int pi = 0 ;
int32_t code = - 1 ;
char statusCode [ 4 ] = " \0 \0 \0 \0 " ;
int statusLen = 0 ;
bool inStatus = false ;
while ( pi < len ) {
if ( pdata [ pi ] = = ' \n ' ) {
// end of status line
if ( code = = - 1 ) code = 502 ; // BAD GATEWAY
break ;
} else if ( pdata [ pi ] = = ' ' ) {
if ( inStatus ) code = atoi ( statusCode ) ;
inStatus = ! inStatus ;
} else if ( inStatus ) {
if ( statusLen < 3 ) statusCode [ statusLen ] = pdata [ pi ] ;
statusLen + + ;
}
pi + + ;
}
// parse header, all this does is look for the end of the header
bool currentLineIsBlank = false ;
while ( pi < len ) {
if ( pdata [ pi ] = = ' \n ' ) {
if ( currentLineIsBlank ) {
// body is starting
pi + + ;
break ;
}
currentLineIsBlank = true ;
} else if ( pdata [ pi ] ! = ' \r ' ) {
currentLineIsBlank = false ;
}
pi + + ;
}
//if (pi < len && pdata[pi] == '\r') pi++; // hacky!
// collect body and send it
uint16_t crc ;
int body_len = len - pi ;
os_printf ( " REST: status=%ld, body=%d \n " , code , body_len ) ;
if ( pi = = len ) {
crc = CMD_ResponseStart ( CMD_REST_EVENTS , client - > resp_cb , code , 0 ) ;
} else {
crc = CMD_ResponseStart ( CMD_REST_EVENTS , client - > resp_cb , code , 1 ) ;
crc = CMD_ResponseBody ( crc , ( uint8_t * ) ( pdata + pi ) , body_len ) ;
CMD_ResponseEnd ( crc ) ;
os_printf ( " REST: body= " ) ;
for ( int j = pi ; j < len ; j + + ) os_printf ( " %02x " , pdata [ j ] ) ;
os_printf ( " \n " ) ;
}
//if(client->security)
// espconn_secure_disconnect(client->pCon);
//else
espconn_disconnect ( client - > pCon ) ;
}
static void ICACHE_FLASH_ATTR
tcpclient_sent_cb ( void * arg ) {
struct espconn * pCon = ( struct espconn * ) arg ;
RestClient * client = ( RestClient * ) pCon - > reverse ;
os_printf ( " REST: Sent \n " ) ;
if ( client - > data_sent ! = client - > data_len ) {
// we only sent part of the buffer, send the rest
espconn_sent ( client - > pCon , ( uint8_t * ) ( client - > data + client - > data_sent ) ,
client - > data_len - client - > data_sent ) ;
client - > data_sent = client - > data_len ;
} else {
// we're done sending, free the memory
if ( client - > data ) os_free ( client - > data ) ;
client - > data = 0 ;
}
}
static void ICACHE_FLASH_ATTR
tcpclient_connect_cb ( void * arg ) {
struct espconn * pCon = ( struct espconn * ) arg ;
RestClient * client = ( RestClient * ) pCon - > reverse ;
os_printf ( " REST #%d: connected \n " , client - restClient ) ;
espconn_regist_disconcb ( client - > pCon , tcpclient_discon_cb ) ;
espconn_regist_recvcb ( client - > pCon , tcpclient_recv ) ;
espconn_regist_sentcb ( client - > pCon , tcpclient_sent_cb ) ;
client - > data_sent = client - > data_len < = 1400 ? client - > data_len : 1400 ;
os_printf ( " REST #%d: sending %d \n " , client - restClient , client - > data_sent ) ;
//if(client->security){
// espconn_secure_sent(client->pCon, client->data, client->data_sent);
//}
//else{
espconn_sent ( client - > pCon , ( uint8_t * ) client - > data , client - > data_sent ) ;
//}
}
static void ICACHE_FLASH_ATTR
tcpclient_recon_cb ( void * arg , sint8 errType ) {
struct espconn * pCon = ( struct espconn * ) arg ;
RestClient * client = ( RestClient * ) pCon - > reverse ;
os_printf ( " REST #%d: conn reset \n " , client - restClient ) ;
}
static void ICACHE_FLASH_ATTR
rest_dns_found ( const char * name , ip_addr_t * ipaddr , void * arg ) {
struct espconn * pConn = ( struct espconn * ) arg ;
RestClient * client = ( RestClient * ) pConn - > reverse ;
if ( ipaddr = = NULL ) {
os_printf ( " REST DNS: Got no ip, try to reconnect \n " ) ;
return ;
}
os_printf ( " REST DNS: found ip %d.%d.%d.%d \n " ,
* ( ( uint8 * ) & ipaddr - > addr ) ,
* ( ( uint8 * ) & ipaddr - > addr + 1 ) ,
* ( ( uint8 * ) & ipaddr - > addr + 2 ) ,
* ( ( uint8 * ) & ipaddr - > addr + 3 ) ) ;
if ( client - > ip . addr = = 0 & & ipaddr - > addr ! = 0 ) {
os_memcpy ( client - > pCon - > proto . tcp - > remote_ip , & ipaddr - > addr , 4 ) ;
//if(client->security){
// espconn_secure_connect(client->pCon);
//}
//else {
espconn_connect ( client - > pCon ) ;
//}
os_printf ( " REST: connecting... \n " ) ;
}
}
uint32_t ICACHE_FLASH_ATTR
REST_Setup ( CmdPacket * cmd ) {
CmdRequest req ;
uint32_t port , security ;
// start parsing the command
CMD_Request ( & req , cmd ) ;
if ( CMD_GetArgc ( & req ) ! = 3 ) return 0 ;
// get the hostname
uint16_t len = CMD_ArgLen ( & req ) ;
if ( len > 128 ) return 0 ; // safety check
uint8_t * rest_host = ( uint8_t * ) os_zalloc ( len + 1 ) ;
if ( CMD_PopArg ( & req , rest_host , len ) ) return 0 ;
rest_host [ len ] = 0 ;
// get the port
if ( CMD_PopArg ( & req , ( uint8_t * ) & port , 4 ) ) {
os_free ( rest_host ) ;
return 0 ;
}
// get the security mode
if ( CMD_PopArg ( & req , ( uint8_t * ) & security , 4 ) ) {
os_free ( rest_host ) ;
return 0 ;
}
// clear connection structures the first time
if ( restNum = = 0xff ) {
os_memset ( restClient , 0 , MAX_REST * sizeof ( RestClient ) ) ;
restNum = 0 ;
}
// allocate a connection structure
RestClient * client = restClient + restNum ;
uint8_t clientNum = restNum ;
restNum = ( restNum + 1 ) % MAX_REST ;
// free any data structure that may be left from a previous connection
if ( client - > header ) os_free ( client - > header ) ;
if ( client - > content_type ) os_free ( client - > content_type ) ;
if ( client - > user_agent ) os_free ( client - > user_agent ) ;
if ( client - > data ) os_free ( client - > data ) ;
if ( client - > pCon ) {
if ( client - > pCon - > proto . tcp ) os_free ( client - > pCon - > proto . tcp ) ;
os_free ( client - > pCon ) ;
}
os_memset ( client , 0 , sizeof ( RestClient ) ) ;
os_printf ( " REST: setup #%d host=%s port=%ld security=%ld \n " , clientNum , rest_host , port , security ) ;
client - > resp_cb = cmd - > callback ;
client - > host = ( char * ) rest_host ;
client - > port = port ;
client - > security = security ;
client - > header = ( char * ) os_zalloc ( 4 ) ;
client - > header [ 0 ] = 0 ;
client - > content_type = ( char * ) os_zalloc ( 22 ) ;
os_sprintf ( ( char * ) client - > content_type , " x-www-form-urlencoded " ) ;
client - > user_agent = ( char * ) os_zalloc ( 9 ) ;
os_sprintf ( ( char * ) client - > user_agent , " esp-link " ) ;
client - > pCon = ( struct espconn * ) os_zalloc ( sizeof ( struct espconn ) ) ;
client - > pCon - > proto . tcp = ( esp_tcp * ) os_zalloc ( sizeof ( esp_tcp ) ) ;
client - > pCon - > type = ESPCONN_TCP ;
client - > pCon - > state = ESPCONN_NONE ;
client - > pCon - > proto . tcp - > local_port = espconn_port ( ) ;
client - > pCon - > proto . tcp - > remote_port = client - > port ;
client - > pCon - > reverse = client ;
return REST_CB | ( uint32_t ) clientNum ;
}
uint32_t ICACHE_FLASH_ATTR
REST_SetHeader ( CmdPacket * cmd ) {
CmdRequest req ;
CMD_Request ( & req , cmd ) ;
if ( CMD_GetArgc ( & req ) ! = 3 )
return 0 ;
// Get client
uint32_t clientNum ;
if ( CMD_PopArg ( & req , ( uint8_t * ) & clientNum , 4 ) ) return 0 ;
if ( ( clientNum & 0xffff0000 ) ! = REST_CB ) return 0 ;
RestClient * client = restClient + ( ( clientNum & 0xffff ) % MAX_REST ) ;
// Get header selector
uint32_t header_index ;
if ( CMD_PopArg ( & req , ( uint8_t * ) & header_index , 4 ) ) return 0 ;
// Get header value
uint16_t len = CMD_ArgLen ( & req ) ;
if ( len > 256 ) return 0 ; //safety check
switch ( header_index ) {
case HEADER_GENERIC :
if ( client - > header ) os_free ( client - > header ) ;
client - > header = ( char * ) os_zalloc ( len + 3 ) ;
CMD_PopArg ( & req , ( uint8_t * ) client - > header , len ) ;
client - > header [ len ] = ' \r ' ;
client - > header [ len + 1 ] = ' \n ' ;
client - > header [ len + 2 ] = 0 ;
os_printf ( " REST: Set header: %s \r \n " , client - > header ) ;
break ;
case HEADER_CONTENT_TYPE :
if ( client - > content_type ) os_free ( client - > content_type ) ;
client - > content_type = ( char * ) os_zalloc ( len + 3 ) ;
CMD_PopArg ( & req , ( uint8_t * ) client - > content_type , len ) ;
client - > content_type [ len ] = ' \r ' ;
client - > content_type [ len + 1 ] = ' \n ' ;
client - > content_type [ len + 2 ] = 0 ;
os_printf ( " REST: Set content_type: %s \r \n " , client - > content_type ) ;
break ;
case HEADER_USER_AGENT :
if ( client - > user_agent ) os_free ( client - > user_agent ) ;
client - > user_agent = ( char * ) os_zalloc ( len + 3 ) ;
CMD_PopArg ( & req , ( uint8_t * ) client - > user_agent , len ) ;
client - > user_agent [ len ] = ' \r ' ;
client - > user_agent [ len + 1 ] = ' \n ' ;
client - > user_agent [ len + 2 ] = 0 ;
os_printf ( " REST: Set user_agent: %s \r \n " , client - > user_agent ) ;
break ;
}
return 1 ;
}
uint32_t ICACHE_FLASH_ATTR
REST_Request ( CmdPacket * cmd ) {
CmdRequest req ;
CMD_Request ( & req , cmd ) ;
os_printf ( " REST: request " ) ;
// Get client
uint32_t clientNum ;
if ( CMD_PopArg ( & req , ( uint8_t * ) & clientNum , 4 ) ) goto fail ;
if ( ( clientNum & 0xffff0000 ) ! = REST_CB ) goto fail ;
clientNum & = 0xffff ;
RestClient * client = restClient + clientNum % MAX_REST ;
os_printf ( " #%ld " , clientNum ) ;
// Get HTTP method
uint16_t len = CMD_ArgLen ( & req ) ;
if ( len > 15 ) goto fail ;
char method [ 16 ] ;
CMD_PopArg ( & req , method , len ) ;
method [ len ] = 0 ;
os_printf ( " method=%s " , method ) ;
// Get HTTP path
len = CMD_ArgLen ( & req ) ;
if ( len > 1023 ) goto fail ;
char path [ 1024 ] ;
CMD_PopArg ( & req , path , len ) ;
path [ len ] = 0 ;
os_printf ( " path=%s " , path ) ;
// Get HTTP body
uint32_t realLen = 0 ;
if ( CMD_GetArgc ( & req ) = = 3 ) {
realLen = 0 ;
len = 0 ;
} else {
CMD_PopArg ( & req , ( uint8_t * ) & realLen , 4 ) ;
len = CMD_ArgLen ( & req ) ;
if ( len > 2048 | | realLen > len ) goto fail ;
}
os_printf ( " bodyLen=%ld " , realLen ) ;
// we need to allocate memory for the header plus the body. First we count the length of the
// header (including some extra counted "%s" and then we add the body length. We allocate the
// whole shebang and copy everything into it.
// BTW, use http/1.0 to avoid responses with transfer-encoding: chunked
char * headerFmt = " %s %s HTTP/1.0 \r \n "
" Host: %s \r \n "
" %s "
" Content-Length: %d \r \n "
" Connection: close \r \n "
" Content-Type: %s \r \n "
" User-Agent: %s \r \n \r \n " ;
uint16_t headerLen = strlen ( headerFmt ) + strlen ( method ) + strlen ( path ) + strlen ( client - > host ) +
strlen ( client - > header ) + strlen ( client - > content_type ) + strlen ( client - > user_agent ) ;
os_printf ( " hdrLen=%d " , headerLen ) ;
if ( client - > data ) os_free ( client - > data ) ;
client - > data = ( char * ) os_zalloc ( headerLen + realLen ) ;
if ( client - > data = = NULL ) goto fail ;
os_printf ( " totLen=%ld data=%p " , headerLen + realLen , client - > data ) ;
client - > data_len = os_sprintf ( ( char * ) client - > data , headerFmt , method , path , client - > host ,
client - > header , realLen , client - > content_type , client - > user_agent ) ;
os_printf ( " hdrLen=%d " , client - > data_len ) ;
if ( realLen > 0 ) {
CMD_PopArg ( & req , client - > data + client - > data_len , realLen ) ;
client - > data_len + = realLen ;
}
os_printf ( " \n " ) ;
os_printf ( " REST: pCon state=%d \n " , client - > pCon - > state ) ;
client - > pCon - > state = ESPCONN_NONE ;
espconn_regist_connectcb ( client - > pCon , tcpclient_connect_cb ) ;
espconn_regist_reconcb ( client - > pCon , tcpclient_recon_cb ) ;
if ( UTILS_StrToIP ( ( char * ) client - > host , & client - > pCon - > proto . tcp - > remote_ip ) ) {
os_printf ( " REST: Connect to ip %s:%ld \n " , client - > host , client - > port ) ;
//if(client->security){
// espconn_secure_connect(client->pCon);
//}
//else {
espconn_connect ( client - > pCon ) ;
//}
} else {
os_printf ( " REST: Connect to host %s:%ld \n " , client - > host , client - > port ) ;
espconn_gethostbyname ( client - > pCon , ( char * ) client - > host , & client - > ip , rest_dns_found ) ;
}
return 1 ;
fail :
os_printf ( " \n " ) ;
return 0 ;
}
uint8_t ICACHE_FLASH_ATTR
UTILS_StrToIP ( const char * str , void * ip )
{
/* The count of the number of bytes processed. */
int i ;
/* A pointer to the next digit to process. */
const char * start ;
start = str ;
for ( i = 0 ; i < 4 ; i + + ) {
/* The digit being processed. */
char c ;
/* The value of this byte. */
int n = 0 ;
while ( 1 ) {
c = * start ;
start + + ;
if ( c > = ' 0 ' & & c < = ' 9 ' ) {
n * = 10 ;
n + = c - ' 0 ' ;
}
/* We insist on stopping at "." if we are still parsing
the first , second , or third numbers . If we have reached
the end of the numbers , we will allow any character . */
else if ( ( i < 3 & & c = = ' . ' ) | | i = = 3 ) {
break ;
}
else {
return 0 ;
}
}
if ( n > = 256 ) {
return 0 ;
}
( ( uint8_t * ) ip ) [ i ] = n ;
}
return 1 ;
}