/* * Copyright (c) 2010, Swedish Institute of Computer Science. * Copyright (c) 2014, Texas Instruments Incorporated - http://www.ti.com/ * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the Institute nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ /** * \addtogroup cc26xx-web-demo * @{ * * \file * A simple web server which displays network and sensor information */ /*---------------------------------------------------------------------------*/ #include "contiki.h" #include "httpd-simple.h" #include "net/ipv6/uip-ds6-route.h" #include "batmon-sensor.h" #include "lib/sensors.h" #include "lib/list.h" #include "cc26xx-web-demo.h" #include "mqtt-client.h" #include "net-uart.h" #include #include #include #include #include #include #include #include /*---------------------------------------------------------------------------*/ #define SEND_STRING(s, str) PSOCK_SEND(s, (uint8_t *)str, strlen(str)) /*---------------------------------------------------------------------------*/ #define CONNS 2 #define CONTENT_LENGTH_MAX 256 #define STATE_WAITING 0 #define STATE_OUTPUT 1 #define IPADDR_BUF_LEN 64 /*---------------------------------------------------------------------------*/ #define RETURN_CODE_OK 0 #define RETURN_CODE_NF 1 /* Not Found */ #define RETURN_CODE_SU 2 /* Service Unavailable */ #define RETURN_CODE_BR 3 /* Bad Request */ #define RETURN_CODE_LR 4 /* Length Required */ #define RETURN_CODE_TL 5 /* Content Length too Large */ /*---------------------------------------------------------------------------*/ /* POST request machine states */ #define PARSE_POST_STATE_INIT 0 #define PARSE_POST_STATE_MORE 1 #define PARSE_POST_STATE_READING_KEY 2 #define PARSE_POST_STATE_READING_VAL 3 #define PARSE_POST_STATE_ERROR 0xFFFFFFFF /*---------------------------------------------------------------------------*/ #define PARSE_POST_BUF_SIZES 64 /* Last byte always used to null terminate */ #define PARSE_POST_MAX_POS (PARSE_POST_BUF_SIZES - 2) static char key[PARSE_POST_BUF_SIZES]; static char val_escaped[PARSE_POST_BUF_SIZES]; static char val[PARSE_POST_BUF_SIZES]; static int key_len; static int val_len; static int state; /*---------------------------------------------------------------------------*/ /* Stringified min/max intervals */ #define STRINGIFY(x) XSTR(x) #define XSTR(x) #x #define RSSI_INT_MAX STRINGIFY(CC26XX_WEB_DEMO_RSSI_MEASURE_INTERVAL_MAX) #define RSSI_INT_MIN STRINGIFY(CC26XX_WEB_DEMO_RSSI_MEASURE_INTERVAL_MIN) #define PUB_INT_MAX STRINGIFY(MQTT_CLIENT_PUBLISH_INTERVAL_MAX) #define PUB_INT_MIN STRINGIFY(MQTT_CLIENT_PUBLISH_INTERVAL_MIN) /*---------------------------------------------------------------------------*/ /* * We can only handle a single POST request at a time. Since a second POST * request cannot interrupt us while obtaining a lock, we don't really need * this lock to be atomic. * * An HTTP connection will first request a lock before it starts processing * a POST request. We maintain a global lock which is either NULL or points * to the http conn which currently has the lock */ static struct httpd_state *lock; /*---------------------------------------------------------------------------*/ PROCESS(httpd_simple_process, "CC26XX Web Server"); /*---------------------------------------------------------------------------*/ #define ISO_nl 0x0A #define ISO_space 0x20 #define ISO_slash 0x2F #define ISO_amp 0x26 #define ISO_column 0x3A #define ISO_equal 0x3D /*---------------------------------------------------------------------------*/ #define HTTP_200_OK "HTTP/1.0 200 OK\r\n" #define HTTP_302_FO "HTTP/1.0 302 Found\r\n" #define HTTP_400_BR "HTTP/1.0 400 Bad Request\r\n" #define HTTP_404_NF "HTTP/1.0 404 Not Found\r\n" #define HTTP_411_LR "HTTP/1.0 411 Length Required\r\n" #define HTTP_413_TL "HTTP/1.0 413 Request Entity Too Large\r\n" #define HTTP_503_SU "HTTP/1.0 503 Service Unavailable\r\n" #define CONN_CLOSE "Connection: close\r\n" /*---------------------------------------------------------------------------*/ #define SECTION_TAG "div" #define SECTION_OPEN "<" SECTION_TAG ">" #define SECTION_CLOSE "" #define CONTENT_OPEN "
"
#define CONTENT_CLOSE "
" /*---------------------------------------------------------------------------*/ #define REQUEST_TYPE_GET 1 #define REQUEST_TYPE_POST 2 /*---------------------------------------------------------------------------*/ /* Temporary buffer for holding escaped HTML used by html_escape_quotes */ #define HTML_ESCAPED_BUFFER_SIZE 128 static char html_escaped_buf[HTML_ESCAPED_BUFFER_SIZE]; /*---------------------------------------------------------------------------*/ static const char *NOT_FOUND = "" "
" "

404 - file not found

" "
" "" ""; /*---------------------------------------------------------------------------*/ /* Page template */ static const char http_doctype[] = ""; static const char http_header_200[] = HTTP_200_OK; static const char http_header_302[] = HTTP_302_FO; static const char http_header_400[] = HTTP_400_BR; static const char http_header_404[] = HTTP_404_NF; static const char http_header_411[] = HTTP_411_LR; static const char http_header_413[] = HTTP_413_TL; static const char http_header_503[] = HTTP_503_SU; static const char http_get[] = "GET "; static const char http_post[] = "POST "; static const char http_index_html[] = "/index.html"; static const char http_html_start[] = ""; static const char *http_header_srv_str[] = { "Server: Contiki, ", BOARD_STRING "\r\n", NULL }; static const char *http_header_con_close[] = { CONN_CLOSE, NULL }; static const char *http_config_css[] = { "", NULL }; static const char http_head_charset[] = ""; static const char http_title_start[] = ""; static const char http_title_end[] = ""; static const char http_head_end[] = ""; static const char http_body_start[] = ""; static const char http_bottom[] = ""; /*---------------------------------------------------------------------------*/ static const char http_content_type_html[] = "text/html"; static const char http_content_type_plain[] = "text/plain"; /*---------------------------------------------------------------------------*/ /* For the config page */ static const char config_div_left[] = "
"; static const char config_div_right[] = "
"; static const char config_div_close[] = "
"; /*---------------------------------------------------------------------------*/ static char generate_index(struct httpd_state *s); static char generate_config(struct httpd_state *s); /*---------------------------------------------------------------------------*/ typedef struct page { struct page *next; char *filename; char *title; char (*script)(struct httpd_state *s); } page_t; static page_t http_index_page = { NULL, "index.html", "Index", generate_index, }; static page_t http_dev_cfg_page = { NULL, "config.html", "Device Config", generate_config, }; #if CC26XX_WEB_DEMO_NET_UART static char generate_net_uart_config(struct httpd_state *s); static page_t http_net_cfg_page = { NULL, "netu.html", "Net-UART Config", generate_net_uart_config, }; #endif #if CC26XX_WEB_DEMO_MQTT_CLIENT static char generate_mqtt_config(struct httpd_state *s); static page_t http_mqtt_cfg_page = { NULL, "mqtt.html", "MQTT/IBM Cloud Config", generate_mqtt_config, }; #endif /*---------------------------------------------------------------------------*/ #define IBM_QUICKSTART_LINK_LEN 128 static char http_mqtt_a[IBM_QUICKSTART_LINK_LEN]; /*---------------------------------------------------------------------------*/ static uint16_t numtimes; static const httpd_simple_post_handler_t *handler; /*---------------------------------------------------------------------------*/ static uint8_t config_ok; process_event_t httpd_simple_event_new_config; /*---------------------------------------------------------------------------*/ struct httpd_state; typedef char (*httpd_simple_script_t)(struct httpd_state *s); struct httpd_state { char buf[HTTPD_SIMPLE_MAIN_BUF_SIZE]; char tmp_buf[TMP_BUF_SIZE]; struct timer timer; struct psock sin, sout; int blen; const char **ptr; const cc26xx_web_demo_sensor_reading_t *reading; const page_t *page; uip_ds6_route_t *r; uip_ds6_nbr_t *nbr; httpd_simple_script_t script; int content_length; int tmp_buf_len; int tmp_buf_copied; char filename[HTTPD_PATHLEN]; char inputbuf[HTTPD_INBUF_LEN]; struct pt outputpt; struct pt generate_pt; struct pt top_matter_pt; char state; char request_type; char return_code; }; /*---------------------------------------------------------------------------*/ LIST(post_handlers); LIST(pages_list); MEMB(conns, struct httpd_state, CONNS); /*---------------------------------------------------------------------------*/ #define HEX_TO_INT(x) (isdigit(x) ? x - '0' : x - 'W') static size_t url_unescape(const char *src, size_t srclen, char *dst, size_t dstlen) { size_t i, j; int a, b; for(i = j = 0; i < srclen && j < dstlen - 1; i++, j++) { if(src[i] == '%' && isxdigit(*(unsigned char *)(src + i + 1)) && isxdigit(*(unsigned char *)(src + i + 2))) { a = tolower(*(unsigned char *)(src + i + 1)); b = tolower(*(unsigned char *)(src + i + 2)); dst[j] = ((HEX_TO_INT(a) << 4) | HEX_TO_INT(b)) & 0xff; i += 2; } else if(src[i] == '+') { dst[j] = ' '; } else { dst[j] = src[i]; } } dst[j] = '\0'; return i == srclen; } /*---------------------------------------------------------------------------*/ static char* html_escape_quotes(const char *src) { memset(html_escaped_buf, 0, HTML_ESCAPED_BUFFER_SIZE); size_t dstpos = 0; for(size_t i = 0; i < HTML_ESCAPED_BUFFER_SIZE; i++) { if(src[i] == '\0') { break; } else if(src[i] == '"') { if(dstpos + 6 > HTML_ESCAPED_BUFFER_SIZE) { break; } strcpy(&html_escaped_buf[dstpos], """); dstpos += 6; } else { html_escaped_buf[dstpos++] = src[i]; } } html_escaped_buf[HTML_ESCAPED_BUFFER_SIZE - 1] = '\0'; return html_escaped_buf; } /*---------------------------------------------------------------------------*/ void httpd_simple_register_post_handler(httpd_simple_post_handler_t *h) { list_add(post_handlers, h); } /*---------------------------------------------------------------------------*/ static void get_neighbour_state_text(char *buf, uint8_t state) { switch(state) { case NBR_INCOMPLETE: memcpy(buf, "INCOMPLETE", strlen("INCOMPLETE")); break; case NBR_REACHABLE: memcpy(buf, "REACHABLE", strlen("REACHABLE")); break; case NBR_STALE: memcpy(buf, "STALE", strlen("STALE")); break; case NBR_DELAY: memcpy(buf, "DELAY", strlen("DELAY")); break; case NBR_PROBE: memcpy(buf, "NBR_PROBE", strlen("NBR_PROBE")); break; } } /*---------------------------------------------------------------------------*/ static PT_THREAD(enqueue_chunk(struct httpd_state *s, uint8_t immediate, const char *format, ...)) { va_list ap; PSOCK_BEGIN(&s->sout); va_start(ap, format); s->tmp_buf_len = vsnprintf(s->tmp_buf, TMP_BUF_SIZE, format, ap); va_end(ap); if(s->blen + s->tmp_buf_len < HTTPD_SIMPLE_MAIN_BUF_SIZE) { /* Enough space for the entire chunk. Copy over */ memcpy(&s->buf[s->blen], s->tmp_buf, s->tmp_buf_len); s->blen += s->tmp_buf_len; } else { memcpy(&s->buf[s->blen], s->tmp_buf, HTTPD_SIMPLE_MAIN_BUF_SIZE - s->blen); s->tmp_buf_copied = HTTPD_SIMPLE_MAIN_BUF_SIZE - s->blen; s->blen = HTTPD_SIMPLE_MAIN_BUF_SIZE; PSOCK_SEND(&s->sout, (uint8_t *)s->buf, s->blen); s->blen = 0; if(s->tmp_buf_copied < s->tmp_buf_len) { memcpy(s->buf, &s->tmp_buf[s->tmp_buf_copied], s->tmp_buf_len - s->tmp_buf_copied); s->blen += s->tmp_buf_len - s->tmp_buf_copied; } } if(immediate != 0 && s->blen > 0) { PSOCK_SEND(&s->sout, (uint8_t *)s->buf, s->blen); s->blen = 0; } PSOCK_END(&s->sout); } /*---------------------------------------------------------------------------*/ static PT_THREAD(generate_top_matter(struct httpd_state *s, const char *title, const char **css)) { PT_BEGIN(&s->top_matter_pt); PT_WAIT_THREAD(&s->top_matter_pt, enqueue_chunk(s, 0, http_doctype)); PT_WAIT_THREAD(&s->top_matter_pt, enqueue_chunk(s, 0, http_html_start)); PT_WAIT_THREAD(&s->top_matter_pt, enqueue_chunk(s, 0, http_title_start)); PT_WAIT_THREAD(&s->top_matter_pt, enqueue_chunk(s, 0, title)); PT_WAIT_THREAD(&s->top_matter_pt, enqueue_chunk(s, 0, http_title_end)); if(css != NULL) { for(s->ptr = css; *(s->ptr) != NULL; s->ptr++) { PT_WAIT_THREAD(&s->top_matter_pt, enqueue_chunk(s, 0, *(s->ptr))); } } PT_WAIT_THREAD(&s->top_matter_pt, enqueue_chunk(s, 0, http_head_charset)); PT_WAIT_THREAD(&s->top_matter_pt, enqueue_chunk(s, 0, http_head_end)); PT_WAIT_THREAD(&s->top_matter_pt, enqueue_chunk(s, 0, http_body_start)); /* Links */ PT_WAIT_THREAD(&s->top_matter_pt, enqueue_chunk(s, 0, SECTION_OPEN "

")); s->page = list_head(pages_list); PT_WAIT_THREAD(&s->top_matter_pt, enqueue_chunk(s, 0, "[ %s ]", s->page->filename, s->page->title)); for(s->page = s->page->next; s->page != NULL; s->page = s->page->next) { PT_WAIT_THREAD(&s->top_matter_pt, enqueue_chunk(s, 0, " | [ %s ]", s->page->filename, s->page->title)); } #if CC26XX_WEB_DEMO_MQTT_CLIENT PT_WAIT_THREAD(&s->top_matter_pt, enqueue_chunk(s, 0, " | %s", http_mqtt_a)); #endif PT_WAIT_THREAD(&s->top_matter_pt, enqueue_chunk(s, 0, "

" SECTION_CLOSE)); PT_END(&s->top_matter_pt); } /*---------------------------------------------------------------------------*/ static PT_THREAD(generate_index(struct httpd_state *s)) { char ipaddr_buf[IPADDR_BUF_LEN]; /* Intentionally on stack */ PT_BEGIN(&s->generate_pt); /* Generate top matter (doctype, title, nav links etc) */ PT_WAIT_THREAD(&s->generate_pt, generate_top_matter(s, http_index_page.title, NULL)); /* ND Cache */ PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, SECTION_OPEN "Neighbors" CONTENT_OPEN)); for(s->nbr = nbr_table_head(ds6_neighbors); s->nbr != NULL; s->nbr = nbr_table_next(ds6_neighbors, s->nbr)) { PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "\n")); memset(ipaddr_buf, 0, IPADDR_BUF_LEN); cc26xx_web_demo_ipaddr_sprintf(ipaddr_buf, IPADDR_BUF_LEN, &s->nbr->ipaddr); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%s", ipaddr_buf)); memset(ipaddr_buf, 0, IPADDR_BUF_LEN); get_neighbour_state_text(ipaddr_buf, s->nbr->state); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, " %s", ipaddr_buf)); } PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, CONTENT_CLOSE SECTION_CLOSE)); /* Default Route */ PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, SECTION_OPEN "Default Route" CONTENT_OPEN)); memset(ipaddr_buf, 0, IPADDR_BUF_LEN); cc26xx_web_demo_ipaddr_sprintf(ipaddr_buf, IPADDR_BUF_LEN, uip_ds6_defrt_choose()); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%s", ipaddr_buf)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, CONTENT_CLOSE SECTION_CLOSE)); /* Routes */ PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, SECTION_OPEN "Routes" CONTENT_OPEN)); for(s->r = uip_ds6_route_head(); s->r != NULL; s->r = uip_ds6_route_next(s->r)) { PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "\n")); memset(ipaddr_buf, 0, IPADDR_BUF_LEN); cc26xx_web_demo_ipaddr_sprintf(ipaddr_buf, IPADDR_BUF_LEN, &s->r->ipaddr); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%s", ipaddr_buf)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, " / %u via ", s->r->length)); memset(ipaddr_buf, 0, IPADDR_BUF_LEN); cc26xx_web_demo_ipaddr_sprintf(ipaddr_buf, IPADDR_BUF_LEN, uip_ds6_route_nexthop(s->r)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%s", ipaddr_buf)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, ", lifetime=%lus", s->r->state.lifetime)); } PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, CONTENT_CLOSE SECTION_CLOSE)); /* Sensors */ PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, SECTION_OPEN "Sensors" CONTENT_OPEN)); for(s->reading = cc26xx_web_demo_sensor_first(); s->reading != NULL; s->reading = s->reading->next) { PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "\n%s = %s %s", s->reading->descr, s->reading->publish ? s->reading->converted : "N/A", s->reading->units)); } PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, CONTENT_CLOSE SECTION_CLOSE)); /* Footer */ PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, SECTION_OPEN)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "Page hits: %u
", ++numtimes)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "Uptime: %lu secs
", clock_seconds())); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, SECTION_CLOSE)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 1, http_bottom)); PT_END(&s->generate_pt); } /*---------------------------------------------------------------------------*/ static PT_THREAD(generate_config(struct httpd_state *s)) { PT_BEGIN(&s->generate_pt); /* Generate top matter (doctype, title, nav links etc) */ PT_WAIT_THREAD(&s->generate_pt, generate_top_matter(s, http_dev_cfg_page.title, http_config_css)); /* Sensor Settings */ PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "

Sensors

")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "
generate_pt, enqueue_chunk(s, 0, "method=\"post\" enctype=\"")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "application/x-www-form-urlencoded\" ")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "accept-charset=\"UTF-8\">")); for(s->reading = cc26xx_web_demo_sensor_first(); s->reading != NULL; s->reading = s->reading->next) { PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%s%s:%s%s", config_div_left, s->reading->descr, config_div_close, config_div_right)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "generate_pt, enqueue_chunk(s, 0, "title=\"On\" name=\"%s\"%s>", s->reading->form_field, s->reading->publish ? " Checked" : "")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "generate_pt, enqueue_chunk(s, 0, "title=\"Off\" name=\"%s\"%s>%s", s->reading->form_field, s->reading->publish ? "" : " Checked", config_div_close)); } PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "
")); /* RSSI measurements */ #if CC26XX_WEB_DEMO_READ_PARENT_RSSI PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "

RSSI Probing

")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "
generate_pt, enqueue_chunk(s, 0, "method=\"post\" enctype=\"")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "application/x-www-form-urlencoded\" ")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "accept-charset=\"UTF-8\">")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%sPeriod (secs):%s", config_div_left, config_div_close)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%sgenerate_pt, enqueue_chunk(s, 0, "value=\"%lu\" ", (clock_time_t) (cc26xx_web_demo_config.def_rt_ping_interval / CLOCK_SECOND))); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "min=\"" RSSI_INT_MIN "\" " "max=\"" RSSI_INT_MAX "\" " "name=\"ping_interval\">%s", config_div_close)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "
")); #endif /* Actions */ PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "

Actions

")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "
generate_pt, enqueue_chunk(s, 0, "method=\"post\" enctype=\"")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "application/x-www-form-urlencoded\" ")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "accept-charset=\"UTF-8\">")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "
")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 1, http_bottom)); PT_END(&s->generate_pt); } /*---------------------------------------------------------------------------*/ #if CC26XX_WEB_DEMO_MQTT_CLIENT static PT_THREAD(generate_mqtt_config(struct httpd_state *s)) { PT_BEGIN(&s->generate_pt); /* Generate top matter (doctype, title, nav links etc) */ PT_WAIT_THREAD(&s->generate_pt, generate_top_matter(s, http_mqtt_cfg_page.title, http_config_css)); /* MQTT client settings */ PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "

%s

", http_mqtt_cfg_page.title)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "
generate_pt, enqueue_chunk(s, 0, "method=\"post\" enctype=\"")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "application/x-www-form-urlencoded\" ")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "accept-charset=\"UTF-8\">")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%sType ID:%s", config_div_left, config_div_close)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%sgenerate_pt, enqueue_chunk(s, 0, "value=\"%s\" ", html_escape_quotes( cc26xx_web_demo_config.mqtt_config.type_id))); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "name=\"type_id\">%s", config_div_close)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%sOrg ID:%s", config_div_left, config_div_close)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%sgenerate_pt, enqueue_chunk(s, 0, "value=\"%s\" ", html_escape_quotes( cc26xx_web_demo_config.mqtt_config.org_id))); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "name=\"org_id\">%s", config_div_close)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%sAuth Token:%s", config_div_left, config_div_close)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%sgenerate_pt, enqueue_chunk(s, 0, "value=\"\" ")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "name=\"auth_token\">%s", config_div_close)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%sCommand Type:%s", config_div_left, config_div_close)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%sgenerate_pt, enqueue_chunk(s, 0, "value=\"%s\" ", html_escape_quotes( cc26xx_web_demo_config.mqtt_config.cmd_type))); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "name=\"cmd_type\">%s", config_div_close)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%sEvent Type ID:%s", config_div_left, config_div_close)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%sgenerate_pt, enqueue_chunk(s, 0, "value=\"%s\" ", html_escape_quotes( cc26xx_web_demo_config.mqtt_config.event_type_id))); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "name=\"event_type_id\">%s", config_div_close)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%sInterval (secs):%s", config_div_left, config_div_close)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%sgenerate_pt, enqueue_chunk(s, 0, "value=\"%lu\" ", (clock_time_t) (cc26xx_web_demo_config.mqtt_config.pub_interval / CLOCK_SECOND))); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "min=\"" PUB_INT_MIN "\" " "max=\"" PUB_INT_MAX "\" " "name=\"interval\">%s", config_div_close)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%sBroker IP:%s", config_div_left, config_div_close)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%sgenerate_pt, enqueue_chunk(s, 0, "value=\"%s\" ", cc26xx_web_demo_config.mqtt_config.broker_ip)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "name=\"broker_ip\">%s", config_div_close)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%sBroker Port:%s", config_div_left, config_div_close)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%sgenerate_pt, enqueue_chunk(s, 0, "value=\"%d\" ", cc26xx_web_demo_config.mqtt_config.broker_port)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "min=\"1\" max=\"65535\" " "name=\"broker_port\">%s", config_div_close)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "
")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "
generate_pt, enqueue_chunk(s, 0, "method=\"post\" enctype=\"")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "application/x-www-form-urlencoded\" ")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "accept-charset=\"UTF-8\">")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "
")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 1, http_bottom)); PT_END(&s->generate_pt); } #endif /*---------------------------------------------------------------------------*/ #if CC26XX_WEB_DEMO_NET_UART static PT_THREAD(generate_net_uart_config(struct httpd_state *s)) { PT_BEGIN(&s->generate_pt); /* Generate top matter (doctype, title, nav links etc) */ PT_WAIT_THREAD(&s->generate_pt, generate_top_matter(s, http_net_cfg_page.title, http_config_css)); /* Net-UART settings */ PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "

%s

", http_net_cfg_page.title)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "
generate_pt, enqueue_chunk(s, 0, "method=\"post\" enctype=\"")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "application/x-www-form-urlencoded\" ")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "accept-charset=\"UTF-8\">")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%sRemote IPv6:%s", config_div_left, config_div_close)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%sgenerate_pt, enqueue_chunk(s, 0, "value=\"%s\" ", cc26xx_web_demo_config.net_uart.remote_address)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "name=\"net_uart_ip\">%s", config_div_close)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%sRemote Port:%s", config_div_left, config_div_close)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%sgenerate_pt, enqueue_chunk(s, 0, "value=\"%u\" ", cc26xx_web_demo_config.net_uart.remote_port)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "min=\"1\" max=\"65535\" " "name=\"net_uart_port\">%s", config_div_close)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%s%s:%s%s", config_div_left, "Enable", config_div_close, config_div_right)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "generate_pt, enqueue_chunk(s, 0, "title=\"On\" name=\"net_uart_on\"%s>", cc26xx_web_demo_config.net_uart.enable ? " Checked" : "")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "generate_pt, enqueue_chunk(s, 0, "title=\"Off\" name=\"net_uart_on\"" "%s>%s", cc26xx_web_demo_config.net_uart.enable ? "" : " Checked", config_div_close)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "
")); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 1, http_bottom)); PT_END(&s->generate_pt); } #endif /*---------------------------------------------------------------------------*/ static void lock_obtain(struct httpd_state *s) { if(lock == NULL) { lock = s; } } /*---------------------------------------------------------------------------*/ static void lock_release(struct httpd_state *s) { if(lock == s) { lock = NULL; } } /*---------------------------------------------------------------------------*/ static void parse_post_request_chunk(char *buf, int buf_len, int last_chunk) { int i; int finish; for(i = 0; i < buf_len; i++) { switch(state) { case PARSE_POST_STATE_INIT: state = PARSE_POST_STATE_MORE; /* continue */ case PARSE_POST_STATE_MORE: memset(key, 0, PARSE_POST_BUF_SIZES); memset(val, 0, PARSE_POST_BUF_SIZES); memset(val_escaped, 0, PARSE_POST_BUF_SIZES); key_len = 0; val_len = 0; state = PARSE_POST_STATE_READING_KEY; /* continue */ case PARSE_POST_STATE_READING_KEY: if(buf[i] == ISO_equal) { state = PARSE_POST_STATE_READING_VAL; } else if(buf[i] == ISO_amp) { /* Don't accept an amp while reading a key */ state = PARSE_POST_STATE_ERROR; } else { /* Make sure we don't overshoot key's boundary */ if(key_len <= PARSE_POST_MAX_POS) { key[key_len] = buf[i]; key_len++; } else { /* Not enough space for the key. Abort */ state = PARSE_POST_STATE_ERROR; } } break; case PARSE_POST_STATE_READING_VAL: finish = 0; if(buf[i] == ISO_amp) { finish = 1; } else if(buf[i] == ISO_equal) { /* Don't accept an '=' while reading a val */ state = PARSE_POST_STATE_ERROR; } else { /* Make sure we don't overshoot key's boundary */ if(val_len <= PARSE_POST_MAX_POS) { val[val_len] = buf[i]; val_len++; /* Last character of the last chunk */ if((i == buf_len - 1) && (last_chunk == 1)) { finish = 1; } } else { /* Not enough space for the value. Abort */ state = PARSE_POST_STATE_ERROR; } } if(finish == 1) { /* * Done reading a key=value pair, either because we encountered an amp * or because we reached the end of the message body. * * First, unescape the value. * * Then invoke handlers. We will bail out with PARSE_POST_STATE_ERROR, * unless the key-val gets correctly processed */ url_unescape(val, val_len, val_escaped, PARSE_POST_BUF_SIZES); val_len = strlen(val_escaped); for(handler = list_head(post_handlers); handler != NULL; handler = list_item_next((void *)handler)) { if(handler->handler != NULL) { finish = handler->handler(key, key_len, val_escaped, val_len); } if(finish == HTTPD_SIMPLE_POST_HANDLER_ERROR) { state = PARSE_POST_STATE_ERROR; break; } else if(finish == HTTPD_SIMPLE_POST_HANDLER_OK) { /* Restart the state machine to expect the next pair */ state = PARSE_POST_STATE_MORE; /* * At least one handler returned OK, therefore we must generate a * new config event when we're done. */ config_ok = 1; break; } /* Else, continue */ } } break; case PARSE_POST_STATE_ERROR: /* If we entered the error state earlier, do nothing */ return; default: break; } } } /*---------------------------------------------------------------------------*/ static httpd_simple_script_t get_script(const char *name) { page_t *page; for(page = list_head(pages_list); page != NULL; page = list_item_next(page)) { if(strncmp(name, page->filename, strlen(page->filename)) == 0) { return page->script; } } return NULL; } /*---------------------------------------------------------------------------*/ static PT_THREAD(send_string(struct httpd_state *s, const char *str)) { PSOCK_BEGIN(&s->sout); SEND_STRING(&s->sout, str); PSOCK_END(&s->sout); } /*---------------------------------------------------------------------------*/ static PT_THREAD(send_headers(struct httpd_state *s, const char *statushdr, const char *content_type, const char *redir, const char **additional)) { PT_BEGIN(&s->generate_pt); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, statushdr)); for(s->ptr = http_header_srv_str; *(s->ptr) != NULL; s->ptr++) { PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, *(s->ptr))); } if(redir) { PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "Location: %s\r\n", redir)); } if(additional) { for(s->ptr = additional; *(s->ptr) != NULL; s->ptr++) { PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, *(s->ptr))); } } PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "Content-type: %s; ", content_type)); PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 1, "charset=UTF-8\r\n\r\n")); PT_END(&s->generate_pt); } /*---------------------------------------------------------------------------*/ static PT_THREAD(handle_output(struct httpd_state *s)) { PT_BEGIN(&s->outputpt); s->script = NULL; PT_INIT(&s->generate_pt); PT_INIT(&s->top_matter_pt); if(s->request_type == REQUEST_TYPE_POST) { if(s->return_code == RETURN_CODE_OK) { PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_302, http_content_type_plain, s->filename, NULL)); } else if(s->return_code == RETURN_CODE_LR) { PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_411, http_content_type_plain, NULL, http_header_con_close)); PT_WAIT_THREAD(&s->outputpt, send_string(s, "Content-Length Required\n")); } else if(s->return_code == RETURN_CODE_TL) { PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_413, http_content_type_plain, NULL, http_header_con_close)); PT_WAIT_THREAD(&s->outputpt, send_string(s, "Content-Length too Large\n")); } else if(s->return_code == RETURN_CODE_SU) { PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_503, http_content_type_plain, NULL, http_header_con_close)); PT_WAIT_THREAD(&s->outputpt, send_string(s, "Service Unavailable\n")); } else { PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_400, http_content_type_plain, NULL, http_header_con_close)); PT_WAIT_THREAD(&s->outputpt, send_string(s, "Bad Request\n")); } } else if(s->request_type == REQUEST_TYPE_GET) { s->script = get_script(&s->filename[1]); if(s->script == NULL) { strncpy(s->filename, "/notfound.html", sizeof(s->filename)); PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_404, http_content_type_html, NULL, http_header_con_close)); PT_WAIT_THREAD(&s->outputpt, send_string(s, NOT_FOUND)); uip_close(); PT_EXIT(&s->outputpt); } else { PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_200, http_content_type_html, NULL, http_header_con_close)); PT_WAIT_THREAD(&s->outputpt, s->script(s)); } } s->script = NULL; PSOCK_CLOSE(&s->sout); PT_END(&s->outputpt); } /*---------------------------------------------------------------------------*/ static PT_THREAD(handle_input(struct httpd_state *s)) { PSOCK_BEGIN(&s->sin); PSOCK_READTO(&s->sin, ISO_space); if(strncasecmp(s->inputbuf, http_get, 4) == 0) { s->request_type = REQUEST_TYPE_GET; PSOCK_READTO(&s->sin, ISO_space); if(s->inputbuf[0] != ISO_slash) { PSOCK_CLOSE_EXIT(&s->sin); } if(s->inputbuf[1] == ISO_space) { strncpy(s->filename, http_index_html, sizeof(s->filename)); } else { s->inputbuf[PSOCK_DATALEN(&s->sin) - 1] = 0; strncpy(s->filename, s->inputbuf, sizeof(s->filename)); } } else if(strncasecmp(s->inputbuf, http_post, 5) == 0) { s->request_type = REQUEST_TYPE_POST; PSOCK_READTO(&s->sin, ISO_space); if(s->inputbuf[0] != ISO_slash) { PSOCK_CLOSE_EXIT(&s->sin); } s->inputbuf[PSOCK_DATALEN(&s->sin) - 1] = 0; strncpy(s->filename, s->inputbuf, sizeof(s->filename)); /* POST: Read out the rest of the line and ignore it */ PSOCK_READTO(&s->sin, ISO_nl); /* * Start parsing headers. We look for Content-Length and ignore everything * else until we hit the start of the message body. * * We will return 411 if the client doesn't send Content-Length and 413 * if Content-Length is too high */ s->content_length = 0; s->return_code = RETURN_CODE_LR; do { s->inputbuf[PSOCK_DATALEN(&s->sin)] = 0; /* We anticipate a content length */ if((PSOCK_DATALEN(&s->sin) > 14) && strncasecmp(s->inputbuf, "Content-Length:", 15) == 0) { char *val_start = &s->inputbuf[15]; s->content_length = atoi(val_start); /* So far so good */ s->return_code = RETURN_CODE_OK; } PSOCK_READTO(&s->sin, ISO_nl); } while(PSOCK_DATALEN(&s->sin) != 2); /* * Done reading headers. * Reject content length greater than CONTENT_LENGTH_MAX bytes */ if(s->content_length > CONTENT_LENGTH_MAX) { s->content_length = 0; s->return_code = RETURN_CODE_TL; } if(s->return_code == RETURN_CODE_OK) { /* Acceptable Content Length. Try to obtain a lock */ lock_obtain(s); if(lock == s) { state = PARSE_POST_STATE_INIT; } else { s->return_code = RETURN_CODE_SU; } } /* Parse the message body, unless we have detected an error. */ while(s->content_length > 0 && lock == s && s->return_code == RETURN_CODE_OK) { PSOCK_READBUF_LEN(&s->sin, s->content_length); s->content_length -= PSOCK_DATALEN(&s->sin); /* Parse the message body */ parse_post_request_chunk(s->inputbuf, PSOCK_DATALEN(&s->sin), (s->content_length == 0)); if(state == PARSE_POST_STATE_ERROR) { /* Could not parse: Bad Request and stop parsing */ s->return_code = RETURN_CODE_BR; } } /* * Done. If our return code is OK but the state machine is not in * STATE_MORE, it means that the message body ended half-way reading a key * or value. Set 'Bad Request' */ if(s->return_code == RETURN_CODE_OK && state != PARSE_POST_STATE_MORE) { s->return_code = RETURN_CODE_BR; } /* If the flag is set, we had at least 1 configuration value accepted */ if(config_ok) { process_post(PROCESS_BROADCAST, httpd_simple_event_new_config, NULL); } config_ok = 0; lock_release(s); } else { PSOCK_CLOSE_EXIT(&s->sin); } s->state = STATE_OUTPUT; while(1) { PSOCK_READTO(&s->sin, ISO_nl); } PSOCK_END(&s->sin); } /*---------------------------------------------------------------------------*/ static void handle_connection(struct httpd_state *s) { handle_input(s); if(s->state == STATE_OUTPUT) { handle_output(s); } } /*---------------------------------------------------------------------------*/ static void appcall(void *state) { struct httpd_state *s = (struct httpd_state *)state; if(uip_closed() || uip_aborted() || uip_timedout()) { if(s != NULL) { memset(s, 0, sizeof(struct httpd_state)); memb_free(&conns, s); } } else if(uip_connected()) { s = (struct httpd_state *)memb_alloc(&conns); if(s == NULL) { uip_abort(); return; } tcp_markconn(uip_conn, s); PSOCK_INIT(&s->sin, (uint8_t *)s->inputbuf, sizeof(s->inputbuf) - 1); PSOCK_INIT(&s->sout, (uint8_t *)s->inputbuf, sizeof(s->inputbuf) - 1); PT_INIT(&s->outputpt); s->script = NULL; s->state = STATE_WAITING; timer_set(&s->timer, CLOCK_SECOND * 10); handle_connection(s); } else if(s != NULL) { if(uip_poll()) { if(timer_expired(&s->timer)) { uip_abort(); memset(s, 0, sizeof(struct httpd_state)); memb_free(&conns, s); } } else { timer_restart(&s->timer); } handle_connection(s); } else { uip_abort(); } } /*---------------------------------------------------------------------------*/ static void init(void) { tcp_listen(UIP_HTONS(80)); memb_init(&conns); list_add(pages_list, &http_index_page); list_add(pages_list, &http_dev_cfg_page); #if CC26XX_WEB_DEMO_NET_UART list_add(pages_list, &http_net_cfg_page); #endif #if CC26XX_WEB_DEMO_MQTT_CLIENT list_add(pages_list, &http_mqtt_cfg_page); #endif } /*---------------------------------------------------------------------------*/ PROCESS_THREAD(httpd_simple_process, ev, data) { PROCESS_BEGIN(); printf("CC26XX Web Server\n"); httpd_simple_event_new_config = process_alloc_event(); init(); snprintf(http_mqtt_a, IBM_QUICKSTART_LINK_LEN, "[ IBM Quickstart ]", linkaddr_node_addr.u8[0], linkaddr_node_addr.u8[1], linkaddr_node_addr.u8[2], linkaddr_node_addr.u8[5], linkaddr_node_addr.u8[6], linkaddr_node_addr.u8[7]); while(1) { PROCESS_WAIT_EVENT_UNTIL(ev == tcpip_event); appcall(data); } PROCESS_END(); } /*---------------------------------------------------------------------------*/ /** * @} */