diff --git a/README.md b/README.md index 8fda91e..4ee6bc3 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,39 @@ -Simple HTTP server ------------------- +# Simple HTTP/HTTPS server -This is a very minimal HTTP server I use in some of the projects. +This is a very minimal HTTP server. Optionally supports HTTPS server based on mbedTLS server example. -Absolutely not ready for any kind of production use. - -How to use ----------- - -Really, please see above note. +# How to use This directory is an ESP-IDF component. Clone it (or add it as a submodule) into the component directory of the project. +Enable TLS/SSL HTTPS server by uncommenting the `#define HTTPS_SERVER` line at `https_server.h`, otherwise it will implement unsecured http server. Server certificate and private key are loaded at `https_server.c` directly into flash code as in the [`open_ssl_server`](https://github.com/espressif/esp-idf/tree/master/examples/protocols/openssl_server) example from ESP-IDF repository. Don't forget to insert [`cacert.pem`](https://github.com/espressif/esp-idf/blob/master/examples/protocols/openssl_server/main/cacert.pem) and [`prvtkey.pem`](https://github.com/espressif/esp-idf/blob/master/examples/protocols/openssl_server/main/prvtkey.pem) at your main application directory if you want to run GET/POST examples. - -Documentation -------------- +# Documentation None yet, but I tried to make the comments in the header file helpful. +# Examples -Example -------- +Examples functions at http server: -None yet. +## GET Method Example + +`simple_GET_method_example()` function: +* Add http_server.c and http_server.h as a component into your project. +* Server initialization added into the example function, simply call it and it should work! +* Receiving a GET request at /, http server response is a "Hello World, from ESP32!" html. -Debugging ---------- +## POST Method Example +`simple_POST_method_example()` function: +* As well as GET example, simply add http_server as a componente into your ESP-IDF project. +* Server initialization added into the POST example function, simply call it and don't worry. +* Post to / a pair of key-value where the key is 'key' and value some value you want to test. The example will show value content. If needed, increade log verbosity at `make menuconfig` to show all parsed key-value pairs. -Increasing log level to "Verbose" should produce lots of output related to request handling. +# Debugging +Increasing log level to "Verbose" should produce lots of output related to request handling. -License -------- -GPL, see [LICENSE](LICENSE) file. Mostly because this is a very early version. Will be relicensed as something more reasonable later. +# License +GPL, see [LICENSE](LICENSE) file. Mostly because this is a very early version. Will be relicensed as something more reasonable later. mbedTLS are Apache 2.0 licensed. diff --git a/http_server.c b/http_server.c index 6f2f24c..34fd48a 100644 --- a/http_server.c +++ b/http_server.c @@ -15,7 +15,7 @@ * along with this program. If not, see . */ - +#include #include #include #include @@ -24,6 +24,7 @@ #include "freertos/task.h" #include "freertos/event_groups.h" #include "rom/queue.h" +#include "driver/gpio.h" #include "esp_log.h" @@ -32,10 +33,24 @@ #include "lwip/api.h" #include "http_parser.h" -#include "http_server.h" +#include "mbedtls/platform.h" +#include "mbedtls/net_sockets.h" +#include "mbedtls/esp_debug.h" +#include "mbedtls/ssl.h" +#include "mbedtls/entropy.h" +#include "mbedtls/ctr_drbg.h" +#include "mbedtls/error.h" +#include "mbedtls/certs.h" +#include "mbedtls/ssl_ticket.h" + +#if defined(MBEDTLS_SSL_CACHE_C) +#include "mbedtls/ssl_cache.h" +#endif #define HTTP_PARSE_BUF_MAX_LEN 256 +#define MBEDTLS_EXAMPLE_RECV_BUF_LEN 1024 + typedef enum { HTTP_PARSING_URI, //!< HTTP_PARSING_URI HTTP_PARSING_HEADER_NAME, //!< HTTP_PARSING_HEADER_NAME @@ -77,7 +92,6 @@ struct http_context_ { char* uri; char parse_buffer[HTTP_PARSE_BUF_MAX_LEN]; char* request_header_tmp; - struct netconn *conn; http_parser parser; http_header_list_t request_headers; int response_code; @@ -88,22 +102,51 @@ struct http_context_ { const char* data_ptr; size_t data_size; http_header_list_t request_args; +#ifdef HTTPS_SERVER + mbedtls_ssl_context *ssl_conn; + mbedtls_net_context *client_fd; +#else + struct netconn *conn; +#endif }; - struct http_server_context_ { - int port; - err_t server_task_err; - struct netconn* server_conn; + int port; TaskHandle_t task; - EventGroupHandle_t start_done; + EventGroupHandle_t start_done; + err_t server_task_err; SLIST_HEAD(, http_handler_t) handlers; _lock_t handlers_lock; struct http_context_ connection_context; +#ifdef HTTPS_SERVER + mbedtls_net_context *listen_fd; + mbedtls_entropy_context *entropy; + mbedtls_ctr_drbg_context *ctr_drbg; + mbedtls_ssl_config *conf; + mbedtls_x509_crt *srvcert; + mbedtls_pk_context *pkey; + mbedtls_ssl_cache_context *cache; + mbedtls_ssl_ticket_context *ticket_ctx; +#else + struct netconn* server_conn; +#endif }; -#define SERVER_STARTED_BIT BIT(0) -#define SERVER_DONE_BIT BIT(1) +/** Return true if \p ret is a status code indicating that there is an + * operation in progress on an SSL connection, and false if it indicates + * success or a fatal error. + * + * The possible operations in progress are: + * + * - A read, when the SSL input buffer does not contain a full message. + * - A write, when the SSL output buffer contains some data that has not + * been sent over the network yet. + * - An asynchronous callback that has not completed yet. */ +static int mbedtls_status_is_ssl_in_progress( int ret ) +{ + return( ret == MBEDTLS_ERR_SSL_WANT_READ || + ret == MBEDTLS_ERR_SSL_WANT_WRITE ); +} static const char* http_response_code_to_str(int code); @@ -111,6 +154,7 @@ static esp_err_t add_keyval_pair(http_header_list_t *list, const char* name, con static const char* TAG = "http_server"; + esp_err_t http_register_handler(http_server_t server, const char* uri_pattern, int method, int events, http_handler_fn_t callback, void* callback_arg) @@ -177,7 +221,7 @@ static void header_value_done(http_context_t ctx) { const char* value = ctx->parse_buffer; const char* name = ctx->request_header_tmp; - ESP_LOGD(TAG, "Got header: '%s': '%s'", name, value); + ESP_LOGI(TAG, "Got header: '%s': '%s'", name, value); add_keyval_pair(&ctx->request_headers, name, value); free(ctx->request_header_tmp); ctx->request_header_tmp = NULL; @@ -186,6 +230,7 @@ static void header_value_done(http_context_t ctx) static int http_url_cb(http_parser* parser, const char *at, size_t length) { + ESP_LOGD(TAG, "Called %s", __func__); http_context_t ctx = (http_context_t) parser->data; return append_parse_buffer(ctx, at, length); } @@ -204,6 +249,7 @@ static bool invoke_handler(http_context_t ctx, int event) static int http_headers_done_cb(http_parser* parser) { + ESP_LOGD(TAG, "Called %s", __func__); http_context_t ctx = (http_context_t) parser->data; if (ctx->state == HTTP_PARSING_HEADER_VALUE) { header_value_done(ctx); @@ -274,7 +320,7 @@ static void parse_urlencoded_args(http_context_t ctx, const char* str, size_t le value = urldecode(token_start, pos - token_start); state = READING_KEY; token_start = pos + 1; - ESP_LOGD(TAG, "Got request argument, '%s': '%s'", key, value); + ESP_LOGI(TAG, "Got request argument, '%s': '%s'", key, value); add_keyval_pair(&ctx->request_args, key, value); free(key); key = NULL; @@ -284,7 +330,7 @@ static void parse_urlencoded_args(http_context_t ctx, const char* str, size_t le } if (state == READING_VAL) { value = urldecode(token_start, end - token_start); - ESP_LOGD(TAG, "Got request argument, '%s': '%s'", key, value); + ESP_LOGI(TAG, "Got request argument, '%s': '%s'", key, value); add_keyval_pair(&ctx->request_args, key, value); free(key); key = NULL; @@ -302,7 +348,7 @@ static void uri_done(http_context_t ctx) ++query_str; } ctx->uri = strdup(ctx->parse_buffer); - ESP_LOGD(TAG, "Got URI: '%s'", ctx->uri); + ESP_LOGI(TAG, "Got URI: '%s'", ctx->uri); if (query_str) { parse_urlencoded_args(ctx, query_str, strlen(query_str)); } @@ -315,7 +361,7 @@ static void uri_done(http_context_t ctx) static int http_header_name_cb(http_parser* parser, const char *at, size_t length) { - ESP_LOGV(TAG, "%s", __func__); + ESP_LOGD(TAG, "Called %s", __func__); http_context_t ctx = (http_context_t) parser->data; if (ctx->state == HTTP_PARSING_URI) { uri_done(ctx); @@ -329,7 +375,7 @@ static int http_header_name_cb(http_parser* parser, const char *at, size_t lengt static int http_header_value_cb(http_parser* parser, const char *at, size_t length) { - ESP_LOGV(TAG, "%s", __func__); + ESP_LOGD(TAG, "Called %s", __func__); http_context_t ctx = (http_context_t) parser->data; if (ctx->state == HTTP_PARSING_HEADER_NAME) { header_name_done(ctx); @@ -340,7 +386,7 @@ static int http_header_value_cb(http_parser* parser, const char *at, size_t leng static int http_body_cb(http_parser* parser, const char *at, size_t length) { - ESP_LOGV(TAG, "%s", __func__); + ESP_LOGD(TAG, "Called %s", __func__); http_context_t ctx = (http_context_t) parser->data; ctx->data_ptr = at; ctx->data_size = length; @@ -352,7 +398,7 @@ static int http_body_cb(http_parser* parser, const char *at, size_t length) static int http_message_done_cb(http_parser* parser) { - ESP_LOGV(TAG, "%s", __func__); + ESP_LOGD(TAG, "Called %s", __func__); http_context_t ctx = (http_context_t) parser->data; ctx->state = HTTP_REQUEST_DONE; return 0; @@ -388,6 +434,7 @@ const char* http_request_get_arg_value(http_context_t ctx, const char* name) { http_header_t* it; SLIST_FOREACH(it, &ctx->request_args, list_entry) { + ESP_LOGI(TAG, "Key %s: %s", it->name, it->value); if (strcasecmp(name, it->name) == 0) { return it->value; } @@ -415,7 +462,7 @@ static void form_data_handler_cb(http_context_t http_ctx, void* ctx) const char* str; size_t len; http_request_get_data(http_ctx, &str, &len); - parse_urlencoded_args(ctx, str, len); + parse_urlencoded_args(http_ctx, str, len); } } @@ -517,12 +564,49 @@ static esp_err_t http_send_response_headers(http_context_t http_ctx) headers_list_clear(&http_ctx->response_headers); +#ifdef HTTPS_SERVER + int ret; + int actual_len; + ESP_LOGI(TAG, "Writing response headers..." ); + + len = strlen(headers_buf); + actual_len = 0; + ret = 0; + do + { + len = len - ret; + ret = mbedtls_ssl_write( http_ctx->ssl_conn, ((const unsigned char *)headers_buf + ret), len); + if( ret == MBEDTLS_ERR_NET_CONN_RESET ) + { + ESP_LOGE(TAG, "ERROR: peer closed the connection\n\n" ); + //FIXME: reset connection + //goto reset; + } + + if( ret < 0 && ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE ) + { + ESP_LOGE(TAG, "ERROR: mbedtls_ssl_write returned %d\n\n", ret ); + break; + //FIXME: close connection + //goto exit; + } + if (ret > 0) + actual_len += ret; + }while( ret < 0 || ret < len ); + + ESP_LOGI(TAG, "%d bytes written:\n%s", actual_len, (char *)headers_buf); + free(headers_buf); + http_ctx->state = HTTP_SENDING_RESPONSE_BODY; + //FIXME: check return code from mbedTLS + return ESP_OK; +#else err_t err = netconn_write(http_ctx->conn, headers_buf, strlen(headers_buf), NETCONN_COPY); free(headers_buf); http_ctx->state = HTTP_SENDING_RESPONSE_BODY; return lwip_err_to_esp_err(err); +#endif } /* Common function called by http_response_begin and http_response_begin_multipart */ @@ -554,21 +638,54 @@ esp_err_t http_response_begin(http_context_t http_ctx, int code, const char* con esp_err_t http_response_write(http_context_t http_ctx, const http_buffer_t* buffer) { + size_t len; + int ret; + esp_err_t err; if (http_ctx->state == HTTP_COLLECTING_RESPONSE_HEADERS) { - esp_err_t err = http_send_response_headers(http_ctx); + err = http_send_response_headers(http_ctx); if (err != ESP_OK) { + ESP_LOGE(TAG, "ERROR: in http_send_response_headers function..."); return err; } } - const int flag = buffer->data_is_persistent ? NETCONN_NOCOPY : NETCONN_COPY; - size_t len = buffer->size ? buffer->size : strlen((const char*) buffer->data); - err_t rc = netconn_write(http_ctx->conn, buffer->data, len, flag); + len = buffer->size ? buffer->size : strlen((const char*) buffer->data); +#ifdef HTTPS_SERVER + ESP_LOGI(TAG, "Writing to client:" ); + ret = 0; + do + { + len = len - ret; + ret = mbedtls_ssl_write( http_ctx->ssl_conn, (buffer->data + ret), len); + if( ret == MBEDTLS_ERR_NET_CONN_RESET ) + { + ESP_LOGE(TAG, "ERROR: peer closed the connection\n\n" ); + //FIXME: reset connection + //goto reset; + } + + if( ret < 0 && ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE ) + { + ESP_LOGE(TAG, "ERROR: mbedtls_ssl_write returned %d\n\n", ret ); + break; + //FIXME: close connection + //goto exit; + } + if (ret > 0) + http_ctx->accumulated_response_size += ret; + }while( ret < 0 || ret < len ); + + ESP_LOGI(TAG, "%d bytes written:%s", http_ctx->accumulated_response_size, (char *)buffer->data); + return ret; +#else + const int flag = buffer->data_is_persistent ? NETCONN_NOCOPY : NETCONN_COPY; + err_t rc = netconn_write(http_ctx->conn, buffer->data, len, flag); if (rc != ESP_OK) { ESP_LOGD(TAG, "netconn_write rc=%d", rc); } else { http_ctx->accumulated_response_size += len; } return lwip_err_to_esp_err(rc); +#endif } @@ -636,12 +753,15 @@ esp_err_t http_response_set_header(http_context_t http_ctx, const char* name, co static void http_send_not_found_response(http_context_t http_ctx) { - http_response_begin(http_ctx, 404, "text/plain", HTTP_RESPONSE_SIZE_UNKNOWN); + ESP_LOGD(TAG, "Called %s", __func__); + http_response_begin(http_ctx, 404, "text/plain", HTTP_RESPONSE_SIZE_UNKNOWN); const http_buffer_t buf = { .data = "Not found", .data_is_persistent = true }; + ESP_LOGD(TAG, "Calling http_response_write function..."); http_response_write(http_ctx, &buf); + ESP_LOGD(TAG, "Calling http_response_end function..."); http_response_end(http_ctx); } @@ -662,19 +782,24 @@ static const char* http_response_code_to_str(int code) } -static void http_handle_connection(http_server_t server, struct netconn *conn) +static void http_handle_connection(http_server_t server, void *arg_conn) { - struct netbuf *inbuf = NULL; - char *buf; - u16_t buflen; - err_t err = ERR_OK; + unsigned char *buf; + unsigned char *larger_buf; + size_t len = 0; /* Single threaded server, one context only */ http_context_t ctx = &server->connection_context; /* Initialize context */ ctx->state = HTTP_PARSING_URI; - ctx->conn = conn; +#ifdef HTTPS_SERVER +#else + struct netbuf *inbuf = NULL; + u16_t buflen; + err_t err = ERR_OK; + ctx->conn = (struct netconn *)arg_conn; +#endif http_parser_init(&ctx->parser, HTTP_REQUEST); ctx->parser.data = ctx; ctx->server = server; @@ -688,87 +813,569 @@ static void http_handle_connection(http_server_t server, struct netconn *conn) .on_message_complete = &http_message_done_cb }; - while (ctx->state != HTTP_REQUEST_DONE) { - err = netconn_recv(conn, &inbuf); - if (err != ERR_OK) { - break; - } - - err = netbuf_data(inbuf, (void**) &buf, &buflen); - if (err != ERR_OK) { - break; - } - - size_t parsed_bytes = http_parser_execute(&ctx->parser, &parser_settings, buf, buflen); - if (parsed_bytes < buflen) { - break; - } - } +#ifdef HTTPS_SERVER + int ret; + size_t parsed_bytes = 0; + /* + * 6. Read the HTTP Request + */ + + ret = 0; + buf = malloc(sizeof(char)*MBEDTLS_EXAMPLE_RECV_BUF_LEN); + + while (ctx->state != HTTP_REQUEST_DONE) { + ESP_LOGV(TAG, "Reading from client..." ); +/* + memset( buf, 0, sizeof(char)*MBEDTLS_EXAMPLE_RECV_BUF_LEN); + //FIXME: add support for buffer > MBEDTLS_EXAMPLE_RECV_BUF_LEN + ret = mbedtls_ssl_read( server->connection_context.ssl_conn, buf, MBEDTLS_EXAMPLE_RECV_BUF_LEN); + + if( ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE ) + continue; + + if( ret <= 0 ) + { + switch( ret ) + { + case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY: + ESP_LOGW(TAG, "Error: connection was closed gracefully" ); + break; + + case MBEDTLS_ERR_NET_CONN_RESET: + ESP_LOGW(TAG, "Error: connection was reset by peer" ); + break; + + default: + ESP_LOGW(TAG, "Error: mbedtls_ssl_read returned -0x%x\n", -ret ); + break; + } + + break; + } +*/ + do + { + int terminated = 0; + len = sizeof(char)*MBEDTLS_EXAMPLE_RECV_BUF_LEN - 1; + memset( buf, 0, sizeof(char)*MBEDTLS_EXAMPLE_RECV_BUF_LEN); + ret = mbedtls_ssl_read( server->connection_context.ssl_conn, buf, len ); + + if( mbedtls_status_is_ssl_in_progress( ret ) ) + { + continue; + } + + if( ret <= 0 ) + { + switch( ret ) + { + case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY: + ESP_LOGW(TAG, "Error: connection was closed gracefully" ); + break; + + case MBEDTLS_ERR_NET_CONN_RESET: + ESP_LOGW(TAG, "Error: connection was reset by peer" ); + break; + + default: + ESP_LOGW(TAG, "Error: mbedtls_ssl_read returned -0x%x\n", -ret ); + break; + } + } + + if( mbedtls_ssl_get_bytes_avail( server->connection_context.ssl_conn ) == 0 ) + { + len = ret; + buf[len] = '\0'; + ESP_LOGI(TAG, "%d bytes read: \n%s", len, (char *) buf ); + } + else + { + size_t extra_len, ori_len; + + ori_len = ret; + extra_len = (size_t) mbedtls_ssl_get_bytes_avail( server->connection_context.ssl_conn ); + + larger_buf = mbedtls_calloc( 1, ori_len + extra_len + 1 ); + if( larger_buf == NULL ) + { + ESP_LOGE(TAG, "Failed to allocate larger_buf"); + len = 0; + break; + } + + memset( larger_buf, 0, ori_len + extra_len ); + memcpy( larger_buf, buf, ori_len ); + + if(buf) //Free original buffer; + free(buf); + + /* This read should never fail and get the whole cached data */ + ret = mbedtls_ssl_read( server->connection_context.ssl_conn, larger_buf + ori_len, extra_len ); + if( ret != extra_len || + mbedtls_ssl_get_bytes_avail( server->connection_context.ssl_conn ) != 0 ) + { + ESP_LOGE(TAG, "Failed on cached data"); + len = 0; + break; + } + + larger_buf[ori_len + extra_len] = '\0'; + + /* End of message should be detected according to the syntax of the + * application protocol (eg HTTP), just use a dummy test here. */ + buf = larger_buf; + len = ori_len + extra_len; + + ESP_LOGI(TAG, "%d bytes read (%d + %d): \n%s", len, + ori_len, extra_len, (char *) buf ); + } + + ESP_LOGI(TAG, "Calling http_parser_execute..."); + parsed_bytes = http_parser_execute(&ctx->parser, &parser_settings, (char *)buf, len); + //TODO: check content-length to properly terminate HTTP request + terminated = 1; + + if( terminated ) + { + break; + } + } + while( 1 ); + + if( len == 0 ) //If output buffer length is 0 (indicating a error), finishes processing of this request + break; + } + ESP_LOGD(TAG, "Read looping returned: %d", parsed_bytes); + +#else //HTPPS SERVER OFF + while (ctx->state != HTTP_REQUEST_DONE) { + err = netconn_recv(ctx->conn, &inbuf); + if (err != ERR_OK) { + break; + } + + err = netbuf_data(inbuf, (void**) &buf, &buflen); + if (err != ERR_OK) { + break; + } + + size_t parsed_bytes = http_parser_execute(&ctx->parser, &parser_settings, (char *)buf, buflen); + if (parsed_bytes < buflen) { + break; + } + } +#endif - if (err == ERR_OK) { +#ifdef HTTPS_SERVER + if (len > 0) { ctx->state = HTTP_COLLECTING_RESPONSE_HEADERS; if (ctx->handler == NULL) { - http_send_not_found_response(ctx); + ESP_LOGD(TAG, "No registered Handler!"); + http_send_not_found_response(ctx); + } else { - invoke_handler(ctx, HTTP_HANDLE_RESPONSE); + ESP_LOGD(TAG, "Registered Handler Found!"); + invoke_handler(ctx, HTTP_HANDLE_RESPONSE); } } +#else + if (err == ERR_OK) { + ctx->state = HTTP_COLLECTING_RESPONSE_HEADERS; + if (ctx->handler == NULL) { + ESP_LOGD(TAG, "No registered Handler!"); + http_send_not_found_response(ctx); + } else { + ESP_LOGD(TAG, "Registered Handler Found!"); + invoke_handler(ctx, HTTP_HANDLE_RESPONSE); + } + } +#endif headers_list_clear(&ctx->request_headers); headers_list_clear(&ctx->request_args); - free(ctx->uri); ctx->uri = NULL; ctx->handler = NULL; + +#ifdef HTTPS_SERVER + ESP_LOGI(TAG, "Closing the connection..." ); + while( ( ret = mbedtls_ssl_close_notify( server->connection_context.ssl_conn) ) < 0 ) + { + if( ret != MBEDTLS_ERR_SSL_WANT_READ && + ret != MBEDTLS_ERR_SSL_WANT_WRITE ) + { + ESP_LOGI(TAG, "ERROR: mbedtls_ssl_close_notify returned %d\n\n", ret ); + break; + //FIXME: Reset connection + //goto reset; + } + } + ESP_LOGI(TAG, "OK"); + if(buf) + free(buf); +#else if (err != ERR_CLSD) { - netconn_close(conn); + netconn_close(ctx->conn); } if (inbuf) { netbuf_delete(inbuf); } +#endif } static void http_server(void *arg) { + uint8_t bits; http_server_t ctx = (http_server_t) arg; - struct netconn *client_conn; - err_t err; - ctx->server_conn = netconn_new(NETCONN_TCP); - if (ctx->server_conn == NULL) { - err = ERR_MEM; - goto out; - } - - err = netconn_bind(ctx->server_conn, NULL, ctx->port); - if (err != ERR_OK) { - goto out; - } - - err = netconn_listen(ctx->server_conn); - if (err != ERR_OK) { - goto out; - } - xEventGroupSetBits(ctx->start_done, SERVER_STARTED_BIT); + do{ + ESP_LOGV(TAG, "Checking Server Status..."); + bits = xEventGroupWaitBits(ctx->start_done, SERVER_STARTED_BIT | SERVER_DONE_BIT, 0, pdTRUE, 1000 / portTICK_PERIOD_MS); + + //If server had already been successfully started but it has crashed, + if ((bits & SERVER_STARTED_BIT) && (bits & SERVER_DONE_BIT)) { + ESP_LOGE(TAG, "Server has closed. Restarting server..."); + xEventGroupClearBits(ctx->start_done, SERVER_STARTED_BIT | SERVER_DONE_BIT); + memset(&(ctx->connection_context), 0, sizeof(*ctx) - (size_t)((int)&(ctx->connection_context) - (int)ctx) ); + bits = pdFALSE; + } + + //If server has not successfully been started yet, + if (!(bits & SERVER_STARTED_BIT)) { + #ifdef HTTPS_SERVER + char *error_buf; + ESP_LOGV(TAG, "Declaring local mbedTLS context on task..."); + int ret; + mbedtls_net_context listen_fd; + mbedtls_net_context client_fd; + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; + mbedtls_ssl_context ssl_conn; + mbedtls_ssl_config conf; + mbedtls_x509_crt srvcert; + mbedtls_pk_context pkey; + #if defined(MBEDTLS_SSL_CACHE_C) + mbedtls_ssl_cache_context cache; + (ctx->cache) = &cache; + #endif + #if defined(MBEDTLS_SSL_SESSION_TICKETS) + mbedtls_ssl_ticket_context ticket_ctx; + (ctx->ticket_ctx) = &ticket_ctx; + #endif + (ctx->listen_fd) = &listen_fd; + (ctx->connection_context.client_fd) = &client_fd; + (ctx->entropy) = &entropy; + (ctx->ctr_drbg) = &ctr_drbg; + (ctx->connection_context.ssl_conn) = &ssl_conn; + (ctx->conf) = &conf; + (ctx->srvcert) = &srvcert; + (ctx->pkey) = &pkey; + + ESP_LOGV(TAG, "Reading Root CA certificate......"); + extern const unsigned char rootcacert_pem_start[] asm("_binary_rootCertificate_pem_start"); + extern const unsigned char rootcacert_pem_end[] asm("_binary_rootCertificate_pem_end"); + const unsigned int rootcacert_pem_bytes = rootcacert_pem_end - rootcacert_pem_start; + + /* + ESP_LOGV(TAG, "Reading Intermediate CA certificate......"); + extern const unsigned char intermediatecacert_pem_start[] asm("_binary_intermediatecacert_pem_start"); + extern const unsigned char intermediatecacert_pem_end[] asm("_binary_intermediatecacert_pem_end"); + const unsigned int intermediatecacert_pem_bytes = intermediatecacert_pem_end - intermediatecacert_pem_start; + */ + + ESP_LOGV(TAG, "Reading Server certificate......"); + extern const unsigned char servercert_pem_start[] asm("_binary_esp32Certificate_pem_start"); + extern const unsigned char servercert_pem_end[] asm("_binary_esp32Certificate_pem_end"); + const unsigned int servercert_pem_bytes = servercert_pem_end - servercert_pem_start; + + ESP_LOGV(TAG, "Reading Server Private Key......"); + extern const unsigned char serverprvtkey_pem_start[] asm("_binary_esp32_key_pem_start"); + extern const unsigned char serverprvtkey_pem_end[] asm("_binary_esp32_key_pem_end"); + const unsigned int serverprvtkey_pem_bytes = serverprvtkey_pem_end - serverprvtkey_pem_start; + + ESP_LOGV(TAG, "Setting server_fd......"); + mbedtls_net_init( ctx->listen_fd ); + ESP_LOGV(TAG, "OK"); + + ESP_LOGV(TAG, "Setting client fd......"); + mbedtls_net_init( ctx->connection_context.client_fd ); + ESP_LOGV(TAG, "OK"); + + ESP_LOGV(TAG, "SSL server context create ......"); + mbedtls_ssl_init( ctx->connection_context.ssl_conn ); + ESP_LOGV(TAG, "OK"); + + ESP_LOGV(TAG, "SSL conf context create ......"); + mbedtls_ssl_config_init( ctx->conf ); + ESP_LOGV(TAG, "OK"); + +#if defined(MBEDTLS_SSL_CACHE_C) + mbedtls_ssl_cache_init( ctx->cache ); +#endif +#if defined(MBEDTLS_SSL_SESSION_TICKETS) + mbedtls_ssl_ticket_init( ctx->ticket_ctx ); +#endif + mbedtls_x509_crt_init( ctx->srvcert ); + mbedtls_pk_init( ctx->pkey ); + mbedtls_entropy_init( ctx->entropy ); + mbedtls_ctr_drbg_init( ctx->ctr_drbg ); + + /* + * 1. Load the certificates and private RSA key + */ + ESP_LOGD(TAG, "Loading the server cert. and key..." ); + /* + * This demonstration program uses embedded test certificates. + * Instead, you may want to use mbedtls_x509_crt_parse_file() to read the + * server and CA certificates, as well as mbedtls_pk_parse_keyfile(). + */ + ESP_LOGV(TAG, "SSL server context set own certification......"); + ESP_LOGV(TAG, "Parsing test srv_crt......"); + ret = mbedtls_x509_crt_parse( ctx->srvcert, (const unsigned char *) servercert_pem_start, + servercert_pem_bytes ); + if( ret != ERR_OK ) + { + ESP_LOGE(TAG, "ERROR: mbedtls_x509_crt_parse returned %d", ret ); + goto exit; + } + ESP_LOGV(TAG, "OK"); + + /* + ESP_LOGV(TAG, "Parsing Intermediate CA crt......"); + ret = mbedtls_x509_crt_parse( ctx->srvcert, (const unsigned char *) intermediatecacert_pem_start, + intermediatecacert_pem_bytes ); + if( ret != ERR_OK ) + { + ESP_LOGE(TAG, "ERROR: mbedtls_x509_crt_parse returned %d", ret ); + goto exit; + } + ESP_LOGV(TAG, "OK"); + */ + + ESP_LOGV(TAG, "Parsing Root CA crt......"); + ret = mbedtls_x509_crt_parse( ctx->srvcert, (const unsigned char *) rootcacert_pem_start, + rootcacert_pem_bytes ); + if( ret != ERR_OK ) + { + ESP_LOGE(TAG, "ERROR: mbedtls_x509_crt_parse returned %d", ret ); + goto exit; + } + ESP_LOGV(TAG, "OK"); + + + ESP_LOGV(TAG, "SSL server context set private key......"); + ret = mbedtls_pk_parse_key( ctx->pkey, (const unsigned char *) serverprvtkey_pem_start, + serverprvtkey_pem_bytes, NULL, 0 ); + if( ret != ERR_OK ) + { + ESP_LOGE(TAG, "ERROR: mbedtls_pk_parse_key returned %d", ret ); + goto exit; + } + ESP_LOGV(TAG, "OK"); + + /* + * 3. Seed the RNG + */ + ESP_LOGV(TAG, "Seeding the random number generator..." ); + if( ( ret = mbedtls_ctr_drbg_seed( ctx->ctr_drbg, mbedtls_entropy_func, ctx->entropy, + (const unsigned char *) TAG, + strlen( TAG ) ) ) != 0 ) + { + ESP_LOGE(TAG, "ERROR: mbedtls_ctr_drbg_seed returned %d", ret ); + goto exit; + } + ESP_LOGV(TAG, "OK"); + + /* + * 2. Setup the listening TCP socket + */ + char *port = malloc(sizeof(char) * 6); + ESP_LOGV(TAG, "SSL server socket bind at localhost: %s ......", itoa(ctx->port, port,10)); + if( ( ret = mbedtls_net_bind( ctx->listen_fd, NULL, itoa(ctx->port, port,10), MBEDTLS_NET_PROTO_TCP ) ) != 0 ) + { + ESP_LOGE(TAG, "ERROR: mbedtls_net_bind returned %d", ret ); + goto exit; + } + free(port); + ESP_LOGV(TAG, "OK"); + + + /* + * 4. Setup stuff + */ + ESP_LOGV(TAG, "Setting up the SSL conf data...." ); + #ifdef CONFIG_MBEDTLS_DEBUG + mbedtls_esp_enable_debug_log(ctx->conf, 4); + #endif + if( ( ret = mbedtls_ssl_config_defaults( ctx->conf, + MBEDTLS_SSL_IS_SERVER, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT ) ) != 0 ) + { + ESP_LOGE(TAG, "ERROR: mbedtls_ssl_config_defaults returned %d", ret ); + goto exit; + } + + mbedtls_ssl_conf_rng( ctx->conf, mbedtls_ctr_drbg_random, ctx->ctr_drbg ); + +#if defined(MBEDTLS_SSL_CACHE_C) + mbedtls_ssl_conf_session_cache( ctx->conf, ctx->cache, + mbedtls_ssl_cache_get, + mbedtls_ssl_cache_set ); +#endif - do { - err = netconn_accept(ctx->server_conn, &client_conn); - if (err == ERR_OK) { - http_handle_connection(ctx, client_conn); - netconn_delete(client_conn); - } - } while (err == ERR_OK); + ESP_LOGV(TAG, "Setting up the SSL Session Tickets...." ); +#if defined(MBEDTLS_SSL_SESSION_TICKETS) + if( ( ret = mbedtls_ssl_ticket_setup( ctx->ticket_ctx , + mbedtls_ctr_drbg_random, &ctr_drbg, + MBEDTLS_CIPHER_AES_256_GCM, + 86400 ) ) != 0 ) + { + ESP_LOGE(TAG, "ERROR: mbedtls_ssl_ticket_setup returned %d", ret ); + goto exit; + } + + mbedtls_ssl_conf_session_tickets_cb( &conf, + mbedtls_ssl_ticket_write, + mbedtls_ssl_ticket_parse, + ctx->ticket_ctx ); +#endif -out: - if (ctx->server_conn) { - netconn_close(ctx->server_conn); - netconn_delete(ctx->server_conn); - } - if (err != ERR_OK) { - ctx->server_task_err = err; - xEventGroupSetBits(ctx->start_done, SERVER_DONE_BIT); - } - vTaskDelete(NULL); + mbedtls_ssl_conf_ca_chain( ctx->conf, (*ctx->srvcert).next, NULL ); + if( ( ret = mbedtls_ssl_conf_own_cert( ctx->conf, ctx->srvcert, ctx->pkey ) ) != 0 ) + { + ESP_LOGE(TAG, "ERROR: mbedtls_ssl_conf_own_cert returned %d", ret ); + goto exit; + } + + if( ( ret = mbedtls_ssl_setup( ctx->connection_context.ssl_conn, ctx->conf ) ) != 0 ) + { + ESP_LOGE(TAG, "ERROR: mbedtls_ssl_setup returned %d", ret ); + goto exit; + } + ESP_LOGV(TAG, "OK"); + + xEventGroupSetBits(ctx->start_done, SERVER_STARTED_BIT); +reset: + ESP_LOGI(TAG, "Clearing SERVER_PROCESSING_REQUEST bit..."); + xEventGroupClearBits(ctx->start_done, SERVER_PROCESSING_REQUEST); + ESP_LOGI(TAG, "mbedTLS HTTPS server is running! Waiting for new connection..."); + do { + mbedtls_net_free( ctx->connection_context.client_fd ); + + mbedtls_ssl_session_reset( ctx->connection_context.ssl_conn ); + /* + * 3. Wait until a client connects + */ + ESP_LOGV(TAG, "Wait until a client connects..." ); + if( ( ret = mbedtls_net_accept( ctx->listen_fd, ctx->connection_context.client_fd, + NULL, 0, NULL ) ) != 0 ) + { + ESP_LOGE(TAG, "ERROR: mbedtls_net_accept returned %d", ret ); + goto exit; + } + ESP_LOGI(TAG, "Setting SERVER_PROCESSING_REQUEST bit..."); + xEventGroupSetBits(ctx->start_done, SERVER_PROCESSING_REQUEST); + mbedtls_ssl_set_bio( ctx->connection_context.ssl_conn, ctx->connection_context.client_fd, mbedtls_net_send, mbedtls_net_recv, NULL ); + ESP_LOGV(TAG, "OK"); + + /* + * 5. Handshake + */ + ESP_LOGV(TAG, "Performing the SSL/TLS handshake..." ); + while( ( ret = mbedtls_ssl_handshake( ctx->connection_context.ssl_conn ) ) != ERR_OK ) + { + if( ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE ) + { + ESP_LOGE(TAG, "ERROR: bedtls_ssl_handshake returned %d", ret ); + goto reset; + } + } + ESP_LOGV(TAG, "OK"); + ESP_LOGV(TAG, "Handling connection..." ); + if (ret == ERR_OK) { + http_handle_connection(ctx, NULL); + } + ESP_LOGI(TAG, "Clearing SERVER_PROCESSING_REQUEST bit..."); + xEventGroupClearBits(ctx->start_done, SERVER_PROCESSING_REQUEST); + ESP_LOGV(TAG, "OK"); + } while (ret == ERR_OK); + +exit: + if (ret != ERR_OK) { + error_buf = malloc(sizeof(char)*ERROR_BUF_LENGTH); + mbedtls_strerror( ret, error_buf, sizeof(char)*ERROR_BUF_LENGTH ); + ESP_LOGE(TAG, "Error %d: %s", ret, error_buf ); + free(error_buf); + + //Set SERVER_DONE_BIT and save error at http_server_t struct + ctx->server_task_err = ret; + xEventGroupSetBits(ctx->start_done, SERVER_DONE_BIT); + ESP_LOGI(TAG, "Clearing SERVER_PROCESSING_REQUEST bit..."); + xEventGroupClearBits(ctx->start_done, SERVER_PROCESSING_REQUEST); + } + + mbedtls_net_free( ctx->connection_context.client_fd ); + mbedtls_net_free( ctx->listen_fd ); + mbedtls_x509_crt_free( ctx->srvcert ); + mbedtls_pk_free( ctx->pkey ); + mbedtls_ssl_free( ctx->connection_context.ssl_conn ); + mbedtls_ssl_config_free( ctx->conf ); +#if defined(MBEDTLS_SSL_CACHE_C) + mbedtls_ssl_cache_free( ctx->cache ); +#endif +#if defined(MBEDTLS_SSL_SESSION_TICKETS) + mbedtls_ssl_ticket_free( ctx->ticket_ctx ); +#endif + mbedtls_ctr_drbg_free( ctx->ctr_drbg ); + mbedtls_entropy_free( ctx->entropy ); +#else + struct netconn *client_conn; + err_t err; + ctx->server_conn = netconn_new(NETCONN_TCP); + if (ctx->server_conn == NULL) { + err = ERR_MEM; + goto out; + } + + err = netconn_bind(ctx->server_conn, NULL, ctx->port); + if (err != ERR_OK) { + goto out; + } + + err = netconn_listen(ctx->server_conn); + if (err != ERR_OK) { + goto out; + } + xEventGroupSetBits(ctx->start_done, SERVER_STARTED_BIT); + + do { + err = netconn_accept(ctx->server_conn, &client_conn); + if (err == ERR_OK) { + ESP_LOGI(TAG, "Setting SERVER_PROCESSING_REQUEST bit..."); + xEventGroupSetBits(ctx->start_done, SERVER_PROCESSING_REQUEST); + http_handle_connection(ctx, client_conn); + netconn_delete(client_conn); + ESP_LOGI(TAG, "Clearing SERVER_PROCESSING_REQUEST bit..."); + xEventGroupClearBits(ctx->start_done, SERVER_PROCESSING_REQUEST); + } + } while (err == ERR_OK); + out: + if (ctx->server_conn) { + netconn_close(ctx->server_conn); + netconn_delete(ctx->server_conn); + } + if (err != ERR_OK) { + ctx->server_task_err = err; + ESP_LOGI(TAG, "Clearing SERVER_PROCESSING_REQUEST bit..."); + xEventGroupClearBits(ctx->start_done, SERVER_PROCESSING_REQUEST); + xEventGroupSetBits(ctx->start_done, SERVER_DONE_BIT); + } + vTaskDelete(NULL); + #endif + } + }while(1); } esp_err_t http_server_start(const http_server_options_t* options, http_server_t* out_server) @@ -785,35 +1392,137 @@ esp_err_t http_server_start(const http_server_options_t* options, http_server_t* return ESP_ERR_NO_MEM; } - int ret = xTaskCreatePinnedToCore(&http_server, "httpd", - options->task_stack_size, ctx, - options->task_priority, - &ctx->task, - options->task_affinity); - if (ret != pdPASS) { - vEventGroupDelete(ctx->start_done); - free(ctx); - return ESP_ERR_NO_MEM; - } - - int bits = xEventGroupWaitBits(ctx->start_done, SERVER_STARTED_BIT | SERVER_DONE_BIT, 0, 0, portMAX_DELAY); - if (bits & SERVER_DONE_BIT) { - /* Error happened, task is deleted */ - esp_err_t err = lwip_err_to_esp_err(ctx->server_task_err); - vEventGroupDelete(ctx->start_done); - free(ctx); - return err; - } - - *out_server = ctx; - return ESP_OK; + //Start http_server task if it had not been started before + ESP_LOGV(TAG, "Creating http_server task..."); + int ret = xTaskCreatePinnedToCore(&http_server, "http_server", + options->task_stack_size, ctx, + options->task_priority, + &ctx->task, + options->task_affinity); + if (ret != pdPASS) { + vEventGroupDelete(ctx->start_done); + free(ctx); + return ESP_ERR_NO_MEM; + } + ESP_LOGI(TAG, "http_server task has been created!"); + + //Check server status by checking SERVER_STARTED_BIT (it server has been succesfully started) or SERVER_DONE_BIT (if it has crashed) + ESP_LOGV(TAG, "Checking server status..."); + xEventGroupWaitBits(ctx->start_done, SERVER_STARTED_BIT, 0, 0, portMAX_DELAY); + ESP_LOGI(TAG, "Server started!"); + *out_server = ctx; + return ESP_OK; } esp_err_t http_server_stop(http_server_t server) { /* FIXME: figure out a thread safe way to do this */ - netconn_close(server->server_conn); +#ifdef HTTPS_SERVER + /* FIXME: Add function to stop HTTPS */ +#else + netconn_close(server->server_conn); +#endif xEventGroupWaitBits(server->start_done, SERVER_DONE_BIT, 0, 0, portMAX_DELAY); free(server); return ESP_OK; } + +static void cb_GET_method(http_context_t http_ctx, void* ctx) +{ + size_t response_size = strlen(index_html); + http_response_begin(http_ctx, 200, "text/html", response_size); + http_buffer_t http_index_html = { .data = index_html }; + http_response_write(http_ctx, &http_index_html); + http_response_end(http_ctx); +} + +esp_err_t simple_GET_method_example(void) +{ + http_server_t server; +#ifdef HTTPS_SERVER + http_server_options_t http_options = HTTPS_SERVER_OPTIONS_DEFAULT(); +#else + http_server_options_t http_options = HTTP_SERVER_OPTIONS_DEFAULT(); +#endif + esp_err_t res; + + ESP_LOGI(TAG, "Creating Example Server!"); + ESP_ERROR_CHECK( res = http_server_start(&http_options, &server) ); + if (res != ESP_OK) { + return res; + } + ESP_LOGV(TAG, "OK"); + + ESP_LOGI(TAG, "Registering Handler!"); + ESP_ERROR_CHECK( res = http_register_handler(server, "/", HTTP_GET, HTTP_HANDLE_RESPONSE, &cb_GET_method, NULL) ); + if (res != ESP_OK) { + return res; + } + ESP_LOGV(TAG, "OK"); + + return res; +} + +static void cb_POST_method(http_context_t http_ctx, void* ctx) +{ + const char* post_data; + + ESP_LOGI(TAG, "Received data from POST method..."); + + /*Receiving key from POST*/ + post_data = http_request_get_arg_value(http_ctx, "key"); + if(post_data!=NULL){ + ESP_LOGI(TAG, "Received %d bytes corresponding to the 'key': %s", strlen(post_data), post_data); + }else{ + ESP_LOGI(TAG, "Received NULL from POST method"); + } + + size_t response_size = strlen(response_OK); + http_response_begin(http_ctx, 201, "text/plain", response_size); + http_buffer_t http_response_OK = { .data = response_OK }; + http_response_write(http_ctx, &http_response_OK); + http_response_end(http_ctx); +} + +esp_err_t simple_POST_method_example(void) +{ + http_server_t server; +#ifdef HTTPS_SERVER + http_server_options_t http_options = HTTPS_SERVER_OPTIONS_DEFAULT(); +#else + http_server_options_t http_options = HTTP_SERVER_OPTIONS_DEFAULT(); +#endif + esp_err_t res; + + ESP_ERROR_CHECK( res = http_server_start(&http_options, &server) ); + if (res != ESP_OK) { + return res; + } + + ESP_ERROR_CHECK( res = http_register_form_handler(server, "/", HTTP_POST, HTTP_HANDLE_RESPONSE, &cb_POST_method, NULL) ); + if (res != ESP_OK) { + return res; + } + + return res; +} + +/** + * @brief Check if a request is being attended and returns it + * + * @param Current HTTP(S) server context + * + * @return a uint8_t variable indicating if server is processing any request + */ +uint8_t check_processing_request(http_server_t server) +{ + if(xEventGroupWaitBits(server->start_done, SERVER_PROCESSING_REQUEST, 0, 0, 0) & SERVER_PROCESSING_REQUEST) //If a request was not finished properly, returns an error + { + ESP_LOGI(TAG, "Processing Server Request"); + return true; + }else + { + ESP_LOGI(TAG, "No Server Request"); + return false; + } +} diff --git a/http_server.h b/http_server.h index 9c109d8..6061f18 100644 --- a/http_server.h +++ b/http_server.h @@ -27,6 +27,9 @@ extern "C" { * @brief Simple HTTP server */ +#include "sdkconfig.h" +#include "esp_system.h" + /* Pull in the definitions of HTTP methods */ #include "http_parser.h" @@ -38,6 +41,20 @@ extern "C" { #define HTTP_HANDLE_DATA BIT(2) /*!< Called each time a fragment of request body is received */ #define HTTP_HANDLE_RESPONSE BIT(3) /*!< Called at the end of the request to produce the response */ +/** + * Bit masks for server event handler + */ +#define SERVER_STARTED_BIT BIT(0) +#define SERVER_DONE_BIT BIT(1) +#define SERVER_ERR_NO_MEM BIT(2) +#define SERVER_PROCESSING_REQUEST BIT(3) + +/** Error buffer length */ +#define ERROR_BUF_LENGTH 100 + +/** Uncomment to enable secure server */ +#define HTTPS_SERVER + /** Opaque type representing single HTTP connection */ typedef struct http_context_* http_context_t; @@ -65,6 +82,32 @@ typedef struct { .task_priority = 1, \ } +/** Default initializer for http_server_options_t */ +#define HTTPS_SERVER_OPTIONS_DEFAULT() {\ + .port = 443, \ + .task_affinity = tskNO_AFFINITY, \ + .task_stack_size = 10240, \ + .task_priority = 8, \ +} + +const static char index_html[] = "" + "\n" + "\n" + " \n" + " \n" + "HELLO ESP32\n" + "\n" + "\n" + "

Hello World, from ESP32!

\n" + "\n" + "\n"; + +const static char response_OK[] = + "OK!\n"; + /** * @brief initialize HTTP server, start listening * @param options pointer to http server options, can point to a temporary @@ -285,6 +328,33 @@ esp_err_t http_response_write(http_context_t http_ctx, const http_buffer_t* buff */ esp_err_t http_response_end(http_context_t http_ctx); +/** + * @brief Example of GET method. Responding a simple "Hello World" html. All initializations included. + * @param none + * @return + * - ESP_OK on success + * - other errors in the future? + */ +esp_err_t simple_GET_method_example(void); + +/** + * @brief Example of POST method. Send a application/x-www-form-urlencoded pair key-value where the key is 'key' and some value for it. The value is printed and the server responds a 201 code and a OK message. + * @param none + * @return + * - ESP_OK on success + * - other errors in the future? + */ +esp_err_t simple_POST_method_example(void); + +/** + * @brief Check if a request is being attended and returns it + * + * @param Current HTTP(S) server context + * + * @return a uint8_t variable indicating if server is processing any request + */ +uint8_t check_processing_request(http_server_t server); + #ifdef __cplusplus } #endif