diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f00ba8..4bc0982 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,21 +1,33 @@ cmake_minimum_required(VERSION 3.13) + set(PROGRAM_NAME pico_bmc) set(PICO_BOARD pico_w) include(pico_sdk_import.cmake) project(pico_bmc) pico_sdk_init() + +include(FetchContent) +FetchContent_Declare(llhttp URL "https://github.com/nodejs/llhttp/archive/refs/tags/release/v9.2.1.tar.gz") +set(BUILD_SHARED_LIBS OFF CACHE INTERNAL "") +set(BUILD_STATIC_LIBS ON CACHE INTERNAL "") +FetchContent_MakeAvailable(llhttp) + add_executable(${PROGRAM_NAME} main.c ) + target_include_directories(${PROGRAM_NAME} PRIVATE ${CMAKE_CURRENT_LIST_DIR} ) + target_link_libraries(${PROGRAM_NAME} pico_cyw43_arch_lwip_threadsafe_background pico_stdlib pico_multicore hardware_adc + llhttp_static ) + pico_enable_stdio_usb(${PROGRAM_NAME} TRUE) pico_enable_stdio_uart(${PROGRAM_NAME} FALSE) pico_add_extra_outputs(${PROGRAM_NAME}) \ No newline at end of file diff --git a/http_parser.h b/http_parser.h new file mode 100644 index 0000000..0f05d6f --- /dev/null +++ b/http_parser.h @@ -0,0 +1,98 @@ +#ifndef HTTP_PARSER_H +#define HTTP_PARSER_H + +#include "llhttp.h" + +typedef struct HTTP_REQUEST_DATA_T_ { + char protocol[8]; + char method[8]; + char url[128]; + char body[128]; +} HTTP_REQUEST_DATA_T; + +typedef struct HTTP_REQUEST_PARSER_WRAPPER_T_ { + llhttp_t parser; + llhttp_settings_t settings; +} HTTP_REQUEST_PARSER_WRAPPER_T; + +int request_parser_on_url(llhttp_t * parser, const char * start, size_t length) { + HTTP_REQUEST_DATA_T * http_request = parser->data; + strncat(http_request->url, start, length); + return 0; +} + +int request_parser_on_body(llhttp_t * parser, const char * start, size_t length) { + HTTP_REQUEST_DATA_T * http_request = parser->data; + strncat(http_request->body, start, length); + return 0; +} + +int parse_http_request (HTTP_REQUEST_PARSER_WRAPPER_T * wrapper, char * content, size_t len) { + llhttp_t parser = wrapper->parser; + + llhttp_errno_t err = llhttp_execute(&parser, content, len); + HTTP_REQUEST_DATA_T * http_request = parser.data; + + uint8_t protocol_major = llhttp_get_http_major(&parser); + uint8_t protocol_minor = llhttp_get_http_minor(&parser); + sprintf(http_request->protocol, "%d.%d", protocol_major, protocol_minor); + + uint8_t method = llhttp_get_method(&parser); + const char * method_name = llhttp_method_name(method); + strcpy(http_request->method, method_name); + + return 0; +} + +char * get_protocol (HTTP_REQUEST_PARSER_WRAPPER_T * wrapper) { + HTTP_REQUEST_DATA_T * http_request = wrapper->parser.data; + return http_request->protocol; +} + +char * get_method (HTTP_REQUEST_PARSER_WRAPPER_T * wrapper) { + HTTP_REQUEST_DATA_T * http_request = wrapper->parser.data; + return http_request->method; +} + +char * get_url (HTTP_REQUEST_PARSER_WRAPPER_T * wrapper) { + HTTP_REQUEST_DATA_T * http_request = wrapper->parser.data; + return http_request->url; +} + +char * get_body (HTTP_REQUEST_PARSER_WRAPPER_T * wrapper) { + HTTP_REQUEST_DATA_T * http_request = wrapper->parser.data; + return http_request->body; +} + +HTTP_REQUEST_PARSER_WRAPPER_T * new_request_parser () { + HTTP_REQUEST_PARSER_WRAPPER_T * wrapper = calloc(1, sizeof(HTTP_REQUEST_PARSER_WRAPPER_T)); + + // create a new request data struct + HTTP_REQUEST_DATA_T * http_request = calloc(1, sizeof(HTTP_REQUEST_DATA_T)); + + // init http parser settings + llhttp_settings_init(&wrapper->settings); + wrapper->settings.on_url = &request_parser_on_url; + wrapper->settings.on_body = &request_parser_on_body; + + // init http request parser parser + llhttp_init(&wrapper->parser, HTTP_REQUEST, &wrapper->settings); + wrapper->parser.data = http_request; + + return wrapper; +} + +void delete_request_parser (HTTP_REQUEST_PARSER_WRAPPER_T * wrapper) { + free(wrapper->parser.data); + free(wrapper); +} + +int http_parser_init () { + return 0; +} + +int http_parser_deinit () { + return 0; +} + +#endif \ No newline at end of file diff --git a/http_serv.h b/http_serv.h index d58e994..d371ae8 100644 --- a/http_serv.h +++ b/http_serv.h @@ -1,9 +1,9 @@ #ifndef HTTP_SERV_H #define HTTP_SERV_H -#include #include "lwip/pbuf.h" #include "lwip/tcp.h" +#include "http_parser.h" #define HTTP_PORT 80 #define POLL_TIME_S 5 @@ -22,13 +22,9 @@ typedef struct TCP_SERVER_T_ { typedef struct TCP_CONNECT_STATE_T_ { struct tcp_pcb * pcb; int sent_len; - char method[8]; - char request[128]; - char params[128]; - char protocol[8]; char headers[1024]; - char body[128]; char result[128]; + HTTP_REQUEST_PARSER_WRAPPER_T * request_parser; int header_len; int result_len; ip_addr_t * gw; @@ -51,8 +47,10 @@ static err_t tcp_close_client_connection(TCP_CONNECT_STATE_T * con_state, struct close_err = ERR_ABRT; } if (con_state) { + delete_request_parser(con_state->request_parser); free(con_state); } + DEBUG_printf("[HTTP] [OK ] Finished and closed connection to client\n"); } return close_err; } @@ -76,64 +74,6 @@ static err_t tcp_server_sent(void * arg, struct tcp_pcb * pcb, u16_t len) { return ERR_OK; } -static int handle_status_get (const char * request, const char * params, const char * body, char * result, size_t max_result_len) { - return snprintf(result, max_result_len, "{volt: %f, temp: %f, power: %d}", current_state.voltage, current_state.tempC, current_state.power_state); -} - -static int handle_power_post (const char * request, const char * params, const char * body, char * result, size_t max_result_len) { - if (strstr(body, "requested_state")) { - int requested_power_state_int; - int led_param = sscanf(body, "requested_state=%d", &requested_power_state_int); - if (led_param) { - if (requested_power_state_int == 0 || requested_power_state_int == 1) { - bmc_power_handler((bool) requested_power_state_int); - return snprintf(result, max_result_len, "{}"); - } - else { - return snprintf(result, max_result_len, "{error: true, description: \"invalid requested state, must be 0 or 1\"}"); - } - } - else { - return snprintf(result, max_result_len, "{error: true, description: \"invalid requested state, must be 0 or 1\"}"); - } - } - else { - return snprintf(result, max_result_len, "{error: true, description: \"missing required parameter requested_state\"}"); - } -} - -int parse_content (struct pbuf * p, TCP_CONNECT_STATE_T * con_state) { - char content[2048] = {0}; - pbuf_copy_partial(p, &content, 2048 - 1, 0); - - char * headers_start = content; // First header line containing method request?params HTTP/protocol - char * headers_other = strstr(content, "\r\n") + 2; // remaining headers follow the first line - char * body_start = strstr(content, "\r\n\r\n") + 4; // body begins at the end of headers - - *(headers_other - 2) = 0; - *(body_start - 4) = 0; - - char request_params_comb[256]; - if (sscanf(headers_start, "%s %s HTTP/%s", con_state->method, request_params_comb, con_state->protocol) != 3) { - return 0; - } - - char * params = strchr(request_params_comb, '?'); - if (params) { - *params++ = 0; - strcpy(con_state->request, request_params_comb); - strcpy(con_state->params, params); - } - else { - strcpy(con_state->request, request_params_comb); - } - - strcpy(con_state->headers, headers_other); - strcpy(con_state->body, body_start); - - return 1; -} - int send_response (TCP_CONNECT_STATE_T * con_state, struct tcp_pcb * pcb) { // Check result buffer size if (con_state->result_len > sizeof(con_state->result) - 1) { @@ -164,6 +104,32 @@ int send_response (TCP_CONNECT_STATE_T * con_state, struct tcp_pcb * pcb) { return 0; } +static int handle_status_get (const char * request, const char * params, const char * body, char * result, size_t max_result_len) { + return snprintf(result, max_result_len, "{volt: %f, temp: %f, power: %d}", current_state.voltage, current_state.tempC, current_state.power_state); +} + +static int handle_power_post (const char * request, const char * params, const char * body, char * result, size_t max_result_len) { + if (strstr(body, "requested_state")) { + int requested_power_state_int; + int led_param = sscanf(body, "requested_state=%d", &requested_power_state_int); + if (led_param) { + if (requested_power_state_int == 0 || requested_power_state_int == 1) { + bmc_power_handler((bool) requested_power_state_int); + return snprintf(result, max_result_len, "{}"); + } + else { + return snprintf(result, max_result_len, "{error: true, description: \"invalid requested state, must be 0 or 1\"}"); + } + } + else { + return snprintf(result, max_result_len, "{error: true, description: \"invalid requested state, must be 0 or 1\"}"); + } + } + else { + return snprintf(result, max_result_len, "{error: true, description: \"missing required parameter requested_state\"}"); + } +} + err_t tcp_server_recv(void * arg, struct tcp_pcb * pcb, struct pbuf * p, err_t err) { TCP_CONNECT_STATE_T * con_state = (TCP_CONNECT_STATE_T *)arg; if (!p) { @@ -173,40 +139,45 @@ err_t tcp_server_recv(void * arg, struct tcp_pcb * pcb, struct pbuf * p, err_t e assert(con_state && con_state->pcb == pcb); if (p->tot_len > 0) { DEBUG_printf("[HTTP] [OK ] Server recieved %d err %d\n", p->tot_len, err); - // Copy the request into the buffer - if (!parse_content(p, con_state)) { - DEBUG_printf("[HTTP] [ERR] Failed to parse header\n"); - return tcp_close_client_connection(con_state, pcb, ERR_OK); - } + char content[2048] = {0}; + pbuf_copy_partial(p, &content, 2048 - 1, 0); + con_state->request_parser = new_request_parser(); + parse_http_request(con_state->request_parser, content, strlen(content)); + + char * protocol = get_protocol(con_state->request_parser); + char * method = get_method(con_state->request_parser); + char * url = get_url(con_state->request_parser); + char * body = get_body(con_state->request_parser); // print request - DEBUG_printf("[HTTP] [OK ] Request: %s %s?%s %s\n", con_state->method, con_state->request, con_state->params, con_state->body); + DEBUG_printf("[HTTP] [OK ] Request: %s %s %s\n", method, url, body); int response_code; // parse request depending on method and request - if (strncmp(con_state->method, HTTP_GET, sizeof(HTTP_GET) - 1) == 0 && strncmp(con_state->request, "/status", sizeof("/status") - 1) == 0) { - con_state->result_len = handle_status_get(con_state->request, con_state->params, con_state->body, con_state->result, sizeof(con_state->result)); + if (strncmp(method, HTTP_GET, sizeof(HTTP_GET) - 1) == 0 && strncmp(url, "/status", sizeof("/status") - 1) == 0) { + con_state->result_len = handle_status_get(url, NULL, body, con_state->result, sizeof(con_state->result)); response_code = 200; con_state->header_len = snprintf(con_state->headers, sizeof(con_state->headers), HTTP_RESPONSE_HEADER, response_code, con_state->result_len); } - else if (strncmp(con_state->method, HTTP_POST, sizeof(HTTP_POST) - 1) == 0 && strncmp(con_state->request, "/power", sizeof("/power") - 1) == 0) { - con_state->result_len = handle_power_post(con_state->request, con_state->params, con_state->body, con_state->result, sizeof(con_state->result)); + else if (strncmp(method, HTTP_POST, sizeof(HTTP_POST) - 1) == 0 && strncmp(url, "/power", sizeof("/power") - 1) == 0) { + con_state->result_len = handle_power_post(url, NULL, body, con_state->result, sizeof(con_state->result)); response_code = 200; con_state->header_len = snprintf(con_state->headers, sizeof(con_state->headers), HTTP_RESPONSE_HEADER, response_code, con_state->result_len); } - else { + else { // if not a registered path, return HTTP 404 con_state->result_len = 0; - response_code = 501; + response_code = 404; con_state->header_len = snprintf(con_state->headers, sizeof(con_state->headers), HTTP_RESPONSE_HEADER, response_code, con_state->result_len); } // print result DEBUG_printf("[HTTP] [OK ] Result: %d %s\n", response_code, con_state->result); - if (send_response(con_state, pcb)) { - DEBUG_printf("[HTTP] [ERR] Failure in send\n"); + int err; + if (err = send_response(con_state, pcb)) { + DEBUG_printf("[HTTP] [ERR] Failure in send %d\n", err); } tcp_recved(pcb, p->tot_len); @@ -287,6 +258,11 @@ static bool tcp_server_open(void * arg) { } int http_serv_init () { + // init http parser + if (http_parser_init()) { + DEBUG_printf("[HTTP] [ERR] Failed to initialize http parser\n"); + } + http_serv_state = calloc(1, sizeof(TCP_SERVER_T)); if (!http_serv_state) { DEBUG_printf("[HTTP] [ERR] Failed to allocate state\n"); @@ -304,6 +280,7 @@ int http_serv_init () { } int http_serv_deinit () { + http_parser_deinit(); tcp_server_close(http_serv_state); free(http_serv_state); http_serv_state = NULL; diff --git a/network.h b/network.h index 09f62ca..f55e0fb 100644 --- a/network.h +++ b/network.h @@ -9,7 +9,7 @@ void set_host_name(const char * hostname) { cyw43_arch_lwip_end(); } -int network_init (char * hostname, char * wifi_ssid, char * wifi_pass) { +int network_init (const char * hostname, const char * wifi_ssid, const char * wifi_pass) { set_host_name(hostname); DEBUG_printf("[INIT] [OK ] Set hostname to %s\n", BMC_HOSTNAME);