| /* |
| * Copyright (c) 2017 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #if defined(CONFIG_NET_DEBUG_HTTP) |
| #if defined(CONFIG_HTTPS) |
| #define SYS_LOG_DOMAIN "https/server" |
| #else |
| #define SYS_LOG_DOMAIN "http/server" |
| #endif |
| #define NET_LOG_ENABLED 1 |
| #endif |
| |
| #define RX_EXTRA_DEBUG 0 |
| |
| #include <misc/printk.h> |
| |
| #include <net/net_core.h> |
| #include <net/net_pkt.h> |
| #include <net/net_context.h> |
| |
| #include <net/http.h> |
| |
| #if defined(CONFIG_HTTPS) |
| static void https_enable(struct http_server_ctx *ctx); |
| static void https_disable(struct http_server_ctx *ctx); |
| |
| #if defined(MBEDTLS_DEBUG_C) |
| #include <mbedtls/debug.h> |
| /* - Debug levels (from ext/lib/crypto/mbedtls/include/mbedtls/debug.h) |
| * - 0 No debug |
| * - 1 Error |
| * - 2 State change |
| * - 3 Informational |
| * - 4 Verbose |
| */ |
| #define DEBUG_THRESHOLD 0 |
| #endif |
| |
| #endif /* CONFIG_HTTPS */ |
| |
| #define HTTP_DEFAULT_PORT 80 |
| #define HTTPS_DEFAULT_PORT 443 |
| |
| #define RC_STR(rc) (rc == 0 ? "OK" : "ERROR") |
| |
| #define HTTP_STATUS_200_OK "HTTP/1.1 200 OK\r\n" \ |
| "Content-Type: text/html\r\n" \ |
| "Transfer-Encoding: chunked\r\n" \ |
| "\r\n" |
| |
| #define HTTP_STATUS_400_BR "HTTP/1.1 400 Bad Request\r\n" \ |
| "\r\n" |
| |
| #define HTTP_STATUS_403_FBD "HTTP/1.1 403 Forbidden\r\n" \ |
| "\r\n" |
| |
| #define HTTP_STATUS_404_NF "HTTP/1.1 404 Not Found\r\n" \ |
| "\r\n" |
| |
| #if defined(CONFIG_NET_DEBUG_HTTP_CONN) |
| /** List of HTTP connections */ |
| static sys_slist_t http_conn; |
| |
| static http_server_cb_t ctx_mon; |
| static void *mon_user_data; |
| |
| static void http_server_conn_add(struct http_server_ctx *ctx) |
| { |
| sys_slist_prepend(&http_conn, &ctx->node); |
| |
| if (ctx_mon) { |
| ctx_mon(ctx, mon_user_data); |
| } |
| } |
| |
| static void http_server_conn_del(struct http_server_ctx *ctx) |
| { |
| sys_slist_find_and_remove(&http_conn, &ctx->node); |
| } |
| |
| void http_server_conn_foreach(http_server_cb_t cb, void *user_data) |
| { |
| struct http_server_ctx *ctx; |
| |
| SYS_SLIST_FOR_EACH_CONTAINER(&http_conn, ctx, node) { |
| cb(ctx, user_data); |
| } |
| } |
| |
| void http_server_conn_monitor(http_server_cb_t cb, void *user_data) |
| { |
| ctx_mon = cb; |
| mon_user_data = user_data; |
| } |
| #else |
| #define http_server_conn_add(...) |
| #define http_server_conn_del(...) |
| #endif /* CONFIG_NET_DEBUG_HTTP_CONN */ |
| |
| static inline u16_t http_strlen(const char *str) |
| { |
| if (str) { |
| return strlen(str); |
| } |
| |
| return 0; |
| } |
| |
| static int http_add_header(struct net_pkt *pkt, s32_t timeout, const char *str) |
| { |
| if (net_pkt_append_all(pkt, strlen(str), (u8_t *)str, timeout)) { |
| return 0; |
| } |
| |
| return -ENOMEM; |
| } |
| |
| static int http_add_chunk(struct net_pkt *pkt, s32_t timeout, const char *str) |
| { |
| char chunk_header[16]; |
| u16_t str_len; |
| bool ret; |
| |
| str_len = http_strlen(str); |
| |
| snprintk(chunk_header, sizeof(chunk_header), "%x\r\n", str_len); |
| |
| ret = net_pkt_append_all(pkt, strlen(chunk_header), chunk_header, |
| timeout); |
| if (!ret) { |
| return -ENOMEM; |
| } |
| |
| if (str_len > 0) { |
| ret = net_pkt_append_all(pkt, str_len, (u8_t *)str, timeout); |
| if (!ret) { |
| return -ENOMEM; |
| } |
| } |
| |
| ret = net_pkt_append_all(pkt, sizeof(HTTP_CRLF) - 1, HTTP_CRLF, |
| timeout); |
| if (!ret) { |
| return -ENOMEM; |
| } |
| |
| return 0; |
| } |
| |
| static void req_timer_cancel(struct http_server_ctx *ctx) |
| { |
| k_delayed_work_cancel(&ctx->req.timer); |
| |
| NET_DBG("Context %p request timer cancelled", ctx); |
| } |
| |
| static void req_timeout(struct k_work *work) |
| { |
| struct http_server_ctx *ctx = CONTAINER_OF(work, |
| struct http_server_ctx, |
| req.timer); |
| |
| NET_DBG("Context %p request timeout", ctx); |
| |
| net_context_put(ctx->req.net_ctx); |
| ctx->req.net_ctx = NULL; |
| |
| http_server_conn_del(ctx); |
| } |
| |
| static void pkt_sent(struct net_context *context, |
| int status, |
| void *token, |
| void *user_data) |
| { |
| s32_t timeout = POINTER_TO_INT(token); |
| struct http_server_ctx *ctx = user_data; |
| |
| req_timer_cancel(ctx); |
| |
| if (timeout == K_NO_WAIT) { |
| /* We can just close the context after the packet is sent. */ |
| net_context_put(context); |
| http_server_conn_del(ctx); |
| } else if (timeout > 0) { |
| NET_DBG("Context %p starting timer", ctx); |
| |
| k_delayed_work_submit(&ctx->req.timer, timeout); |
| } |
| |
| /* Note that if the timeout is K_FOREVER, we do not close |
| * the connection. |
| */ |
| } |
| |
| int http_response_wait(struct http_server_ctx *ctx, const char *http_header, |
| const char *html_payload, s32_t timeout) |
| { |
| struct net_pkt *pkt; |
| int ret = -EINVAL; |
| |
| pkt = net_pkt_get_tx(ctx->req.net_ctx, ctx->timeout); |
| if (!pkt) { |
| return ret; |
| } |
| |
| ret = http_add_header(pkt, ctx->timeout, http_header); |
| if (ret != 0) { |
| goto exit_routine; |
| } |
| |
| if (html_payload) { |
| ret = http_add_chunk(pkt, ctx->timeout, html_payload); |
| if (ret != 0) { |
| goto exit_routine; |
| } |
| |
| /* like EOF */ |
| ret = http_add_chunk(pkt, ctx->timeout, NULL); |
| if (ret != 0) { |
| goto exit_routine; |
| } |
| } |
| |
| net_pkt_set_appdatalen(pkt, net_buf_frags_len(pkt->frags)); |
| |
| ret = ctx->send_data(pkt, pkt_sent, 0, INT_TO_POINTER(timeout), ctx); |
| if (ret != 0) { |
| goto exit_routine; |
| } |
| |
| pkt = NULL; |
| |
| exit_routine: |
| if (pkt) { |
| net_pkt_unref(pkt); |
| } |
| |
| return ret; |
| } |
| |
| int http_response(struct http_server_ctx *ctx, const char *http_header, |
| const char *html_payload) |
| { |
| return http_response_wait(ctx, http_header, html_payload, K_NO_WAIT); |
| } |
| |
| int http_response_400(struct http_server_ctx *ctx, const char *html_payload) |
| { |
| return http_response(ctx, HTTP_STATUS_400_BR, html_payload); |
| } |
| |
| int http_response_403(struct http_server_ctx *ctx, const char *html_payload) |
| { |
| return http_response(ctx, HTTP_STATUS_403_FBD, html_payload); |
| } |
| |
| int http_response_404(struct http_server_ctx *ctx, const char *html_payload) |
| { |
| return http_response(ctx, HTTP_STATUS_404_NF, html_payload); |
| } |
| |
| int http_response_send_data(struct http_server_ctx *ctx, |
| const char *http_header, |
| const char *html_payload, |
| s32_t timeout) |
| { |
| struct net_pkt *pkt; |
| int ret = -EINVAL; |
| |
| pkt = net_pkt_get_tx(ctx->req.net_ctx, ctx->timeout); |
| if (!pkt) { |
| return ret; |
| } |
| |
| if (http_header) { |
| ret = http_add_header(pkt, ctx->timeout, http_header); |
| if (ret != 0) { |
| goto exit_routine; |
| } |
| } |
| |
| ret = http_add_chunk(pkt, ctx->timeout, html_payload); |
| if (ret != 0) { |
| goto exit_routine; |
| } |
| |
| net_pkt_set_appdatalen(pkt, net_buf_frags_len(pkt->frags)); |
| |
| ret = ctx->send_data(pkt, pkt_sent, 0, INT_TO_POINTER(timeout), ctx); |
| if (ret != 0) { |
| goto exit_routine; |
| } |
| |
| pkt = NULL; |
| |
| /* We must let the system to send the packet, otherwise TCP might |
| * timeout before the packet is actually sent. This is easily seen |
| * if the application calls this functions many times in a row. |
| */ |
| k_yield(); |
| |
| exit_routine: |
| if (pkt) { |
| net_pkt_unref(pkt); |
| } |
| |
| return ret; |
| } |
| |
| int http_server_set_local_addr(struct sockaddr *addr, const char *myaddr, |
| u16_t port) |
| { |
| if (!addr) { |
| return -EINVAL; |
| } |
| |
| if (myaddr) { |
| void *inaddr; |
| |
| if (addr->sa_family == AF_INET) { |
| #if defined(CONFIG_NET_IPV4) |
| inaddr = &net_sin(addr)->sin_addr; |
| net_sin(addr)->sin_port = htons(port); |
| #else |
| return -EPFNOSUPPORT; |
| #endif |
| } else if (addr->sa_family == AF_INET6) { |
| #if defined(CONFIG_NET_IPV6) |
| inaddr = &net_sin6(addr)->sin6_addr; |
| net_sin6(addr)->sin6_port = htons(port); |
| #else |
| return -EPFNOSUPPORT; |
| #endif |
| } else { |
| return -EAFNOSUPPORT; |
| } |
| |
| return net_addr_pton(addr->sa_family, myaddr, inaddr); |
| } |
| |
| /* If the caller did not supply the address where to bind, then |
| * try to figure it out ourselves. |
| */ |
| if (addr->sa_family == AF_INET6) { |
| #if defined(CONFIG_NET_IPV6) |
| net_ipaddr_copy(&net_sin6(addr)->sin6_addr, |
| net_if_ipv6_select_src_addr(NULL, |
| (struct in6_addr *) |
| net_ipv6_unspecified_address())); |
| #else |
| return -EPFNOSUPPORT; |
| #endif |
| } else if (addr->sa_family == AF_INET) { |
| #if defined(CONFIG_NET_IPV4) |
| struct net_if *iface = net_if_get_default(); |
| |
| /* For IPv4 we take the first address in the interface */ |
| net_ipaddr_copy(&net_sin(addr)->sin_addr, |
| &iface->ipv4.unicast[0].address.in_addr); |
| #else |
| return -EPFNOSUPPORT; |
| #endif |
| } |
| |
| return 0; |
| } |
| |
| struct http_root_url *http_server_add_url(struct http_server_urls *my, |
| const char *url, u8_t flags, |
| http_url_cb_t write_cb) |
| { |
| int i; |
| |
| for (i = 0; i < CONFIG_HTTP_SERVER_NUM_URLS; i++) { |
| if (my->urls[i].is_used) { |
| continue; |
| } |
| |
| my->urls[i].is_used = true; |
| my->urls[i].root = url; |
| |
| /* This will speed-up some future operations */ |
| my->urls[i].root_len = strlen(url); |
| my->urls[i].flags = flags; |
| my->urls[i].write_cb = write_cb; |
| |
| return &my->urls[i]; |
| } |
| |
| return NULL; |
| } |
| |
| int http_server_del_url(struct http_server_urls *my, const char *url) |
| { |
| int i; |
| |
| for (i = 0; i < CONFIG_HTTP_SERVER_NUM_URLS; i++) { |
| if (!my->urls[i].is_used) { |
| continue; |
| } |
| |
| if (strncmp(my->urls[i].root, url, my->urls[i].root_len)) { |
| continue; |
| } |
| |
| my->urls[i].is_used = false; |
| my->urls[i].root = NULL; |
| |
| return 0; |
| } |
| |
| return -ENOENT; |
| } |
| |
| struct http_root_url *http_server_add_default(struct http_server_urls *my, |
| http_url_cb_t write_cb) |
| { |
| if (my->default_url.is_used) { |
| return NULL; |
| } |
| |
| my->default_url.is_used = true; |
| my->default_url.root = NULL; |
| my->default_url.root_len = 0; |
| my->default_url.flags = 0; |
| my->default_url.write_cb = write_cb; |
| |
| return &my->default_url; |
| } |
| |
| int http_server_del_default(struct http_server_urls *my) |
| { |
| if (!my->default_url.is_used) { |
| return -ENOENT; |
| } |
| |
| my->default_url.is_used = false; |
| |
| return 0; |
| } |
| |
| #if defined(CONFIG_NET_DEBUG_HTTP) && (CONFIG_SYS_LOG_NET_LEVEL > 2) |
| static char *sprint_ipaddr(char *buf, int buflen, const struct sockaddr *addr) |
| { |
| if (addr->sa_family == AF_INET6) { |
| #if defined(CONFIG_NET_IPV6) |
| char ipaddr[NET_IPV6_ADDR_LEN]; |
| |
| net_addr_ntop(addr->sa_family, |
| &net_sin6(addr)->sin6_addr, |
| ipaddr, sizeof(ipaddr)); |
| snprintk(buf, buflen, "[%s]:%u", ipaddr, |
| ntohs(net_sin6(addr)->sin6_port)); |
| #endif |
| } else if (addr->sa_family == AF_INET) { |
| #if defined(CONFIG_NET_IPV4) |
| char ipaddr[NET_IPV4_ADDR_LEN]; |
| |
| net_addr_ntop(addr->sa_family, |
| &net_sin(addr)->sin_addr, |
| ipaddr, sizeof(ipaddr)); |
| snprintk(buf, buflen, "%s:%u", ipaddr, |
| ntohs(net_sin(addr)->sin_port)); |
| #endif |
| } |
| |
| return buf; |
| } |
| #endif /* CONFIG_NET_DEBUG_HTTP */ |
| |
| static inline void new_client(struct http_server_ctx *http_ctx, |
| struct net_context *net_ctx, |
| const struct sockaddr *addr) |
| { |
| #if defined(CONFIG_NET_DEBUG_HTTP) && (CONFIG_SYS_LOG_NET_LEVEL > 2) |
| #if defined(CONFIG_NET_IPV6) |
| #define PORT_STR sizeof("[]:xxxxx") |
| char buf[NET_IPV6_ADDR_LEN + PORT_STR]; |
| #elif defined(CONFIG_NET_IPV4) && !defined(CONFIG_NET_IPV6) |
| #define PORT_STR sizeof(":xxxxx") |
| char buf[NET_IPV4_ADDR_LEN + PORT_STR]; |
| #endif |
| |
| NET_INFO("%s connection from %s (%p)", |
| http_ctx->is_https ? "HTTPS" : "HTTP", |
| sprint_ipaddr(buf, sizeof(buf), addr), |
| net_ctx); |
| #endif /* CONFIG_NET_DEBUG_HTTP */ |
| } |
| |
| static inline void new_server(struct http_server_ctx *ctx, |
| const char *server_banner, |
| const struct sockaddr *addr) |
| { |
| #if defined(CONFIG_NET_DEBUG_HTTP) && (CONFIG_SYS_LOG_NET_LEVEL > 2) |
| #if defined(CONFIG_NET_IPV6) |
| #define PORT_STR sizeof("[]:xxxxx") |
| char buf[NET_IPV6_ADDR_LEN + PORT_STR]; |
| #elif defined(CONFIG_NET_IPV4) && !defined(CONFIG_NET_IPV6) |
| #define PORT_STR sizeof(":xxxxx") |
| char buf[NET_IPV4_ADDR_LEN + PORT_STR]; |
| #endif |
| |
| if (addr) { |
| NET_INFO("%s %s (%p)", server_banner, |
| sprint_ipaddr(buf, sizeof(buf), addr), ctx); |
| } else { |
| NET_INFO("%s (%p)", server_banner, ctx); |
| } |
| #endif /* CONFIG_NET_DEBUG_HTTP */ |
| } |
| |
| static int on_header_field(struct http_parser *parser, |
| const char *at, size_t length) |
| { |
| struct http_server_ctx *ctx = parser->data; |
| |
| if (ctx->req.field_values_ctr >= CONFIG_HTTP_HEADER_FIELD_ITEMS) { |
| return 0; |
| } |
| |
| ctx->req.field_values[ctx->req.field_values_ctr].key = at; |
| ctx->req.field_values[ctx->req.field_values_ctr].key_len = length; |
| |
| return 0; |
| } |
| |
| static int on_header_value(struct http_parser *parser, |
| const char *at, size_t length) |
| { |
| struct http_server_ctx *ctx = parser->data; |
| |
| if (ctx->req.field_values_ctr >= CONFIG_HTTP_HEADER_FIELD_ITEMS) { |
| return 0; |
| } |
| |
| ctx->req.field_values[ctx->req.field_values_ctr].value = at; |
| ctx->req.field_values[ctx->req.field_values_ctr].value_len = length; |
| |
| ctx->req.field_values_ctr++; |
| |
| return 0; |
| } |
| |
| static int on_url(struct http_parser *parser, const char *at, size_t length) |
| { |
| struct http_server_ctx *ctx = parser->data; |
| |
| ctx->req.url = at; |
| ctx->req.url_len = length; |
| |
| http_server_conn_add(ctx); |
| |
| return 0; |
| } |
| |
| static int parser_init(struct http_server_ctx *ctx) |
| { |
| memset(ctx->req.field_values, 0, sizeof(ctx->req.field_values)); |
| |
| ctx->req.settings.on_header_field = on_header_field; |
| ctx->req.settings.on_header_value = on_header_value; |
| ctx->req.settings.on_url = on_url; |
| |
| http_parser_init(&ctx->req.parser, HTTP_REQUEST); |
| |
| ctx->req.parser.data = ctx; |
| |
| return 0; |
| } |
| |
| static int http_url_cmp(const char *url, u16_t url_len, |
| const char *root_url, u16_t root_url_len) |
| { |
| if (url_len < root_url_len) { |
| return -EINVAL; |
| } |
| |
| if (memcmp(url, root_url, root_url_len) == 0) { |
| if (url_len == root_url_len) { |
| return 0; |
| } |
| |
| /* Here we evaluate the following conditions: |
| * root_url = /images, url = /images/ -> OK |
| * root_url = /images/, url = /images/img.png -> OK |
| * root_url = /images/, url = /images_and_docs -> ERROR |
| */ |
| if (url_len > root_url_len) { |
| if (root_url[root_url_len - 1] == '/') { |
| return 0; |
| } |
| |
| if (url[root_url_len] == '/') { |
| return 0; |
| } |
| } |
| } |
| |
| return -EINVAL; |
| } |
| |
| static struct http_root_url *http_url_find(struct http_server_ctx *http_ctx) |
| { |
| u16_t url_len = http_ctx->req.url_len; |
| const char *url = http_ctx->req.url; |
| struct http_root_url *root_url; |
| u8_t i; |
| int ret; |
| |
| for (i = 0; i < CONFIG_HTTP_SERVER_NUM_URLS; i++) { |
| root_url = &http_ctx->urls->urls[i]; |
| if (!root_url->is_used) { |
| continue; |
| } |
| |
| ret = http_url_cmp(url, url_len, |
| root_url->root, root_url->root_len); |
| if (!ret) { |
| return root_url; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static int http_process_recv(struct http_server_ctx *http_ctx) |
| { |
| struct http_root_url *root_url; |
| int ret; |
| |
| root_url = http_url_find(http_ctx); |
| if (!root_url) { |
| root_url = &http_ctx->urls->default_url; |
| if (!root_url->is_used) { |
| NET_DBG("No default handler found (%p)", http_ctx); |
| ret = -ENOENT; |
| goto out; |
| } |
| } |
| |
| if (root_url->write_cb) { |
| NET_DBG("Calling handler %p context %p", |
| root_url->write_cb, http_ctx); |
| ret = root_url->write_cb(http_ctx); |
| } else { |
| NET_ERR("No handler for %s", http_ctx->req.url); |
| ret = -ENOENT; |
| } |
| |
| out: |
| return ret; |
| } |
| |
| static void http_recv(struct net_context *net_ctx, |
| struct net_pkt *pkt, int status, |
| void *user_data) |
| { |
| struct http_server_ctx *http_ctx = user_data; |
| struct net_buf *frag; |
| size_t start = http_ctx->req.data_len; |
| int parsed_len; |
| int header_len; |
| u16_t len = 0, recv_len; |
| |
| if (!pkt) { |
| NET_DBG("Connection closed by peer"); |
| return; |
| } |
| |
| if (!http_ctx->enabled) { |
| goto quit; |
| } |
| |
| recv_len = net_pkt_appdatalen(pkt); |
| if (recv_len == 0) { |
| /* don't print info about zero-length app data buffers */ |
| goto quit; |
| } |
| |
| if (status) { |
| NET_DBG("Status %d <%s>", status, RC_STR(status)); |
| goto out; |
| } |
| |
| /* Get rid of possible IP headers in the first fragment. */ |
| frag = pkt->frags; |
| |
| header_len = net_pkt_appdata(pkt) - frag->data; |
| |
| NET_DBG("Received %d bytes data", net_pkt_appdatalen(pkt)); |
| |
| /* After this pull, the frag->data points directly to application data. |
| */ |
| net_buf_pull(frag, header_len); |
| |
| while (frag) { |
| /* If this fragment cannot be copied to result buf, |
| * then parse what we have which will cause the callback to be |
| * called in function on_body(), and continue copying. |
| */ |
| if (http_ctx->req.data_len + frag->len > |
| http_ctx->req.request_buf_len) { |
| |
| /* If the caller has not supplied a callback, then |
| * we cannot really continue if the request buffer |
| * overflows. Set the data_len to mark how many bytes |
| * should be needed in the response_buf. |
| */ |
| if (http_process_recv(http_ctx) < 0) { |
| http_ctx->req.data_len = net_pkt_get_len(pkt); |
| goto out; |
| } |
| |
| parsed_len = |
| http_parser_execute(&http_ctx->req.parser, |
| &http_ctx->req.settings, |
| http_ctx->req.request_buf + |
| start, |
| len); |
| if (parsed_len <= 0) { |
| goto fail; |
| } |
| |
| http_ctx->req.data_len = 0; |
| len = 0; |
| start = 0; |
| } |
| |
| memcpy(http_ctx->req.request_buf + http_ctx->req.data_len, |
| frag->data, frag->len); |
| |
| http_ctx->req.data_len += frag->len; |
| len += frag->len; |
| frag = frag->frags; |
| } |
| |
| out: |
| parsed_len = http_parser_execute(&http_ctx->req.parser, |
| &http_ctx->req.settings, |
| http_ctx->req.request_buf + start, |
| len); |
| if (parsed_len < 0) { |
| fail: |
| NET_DBG("Received %u bytes, only parsed %d bytes (%s %s)", |
| recv_len, parsed_len, |
| http_errno_name(http_ctx->req.parser.http_errno), |
| http_errno_description( |
| http_ctx->req.parser.http_errno)); |
| } |
| |
| if (http_ctx->req.parser.http_errno != HPE_OK) { |
| http_response_400(http_ctx, NULL); |
| } else { |
| http_process_recv(http_ctx); |
| } |
| |
| quit: |
| http_parser_init(&http_ctx->req.parser, HTTP_REQUEST); |
| http_ctx->req.field_values_ctr = 0; |
| http_ctx->req.data_len = 0; |
| net_pkt_unref(pkt); |
| } |
| |
| static void accept_cb(struct net_context *net_ctx, |
| struct sockaddr *addr, socklen_t addrlen, |
| int status, void *data) |
| { |
| struct http_server_ctx *http_ctx = data; |
| int ret; |
| |
| ARG_UNUSED(addr); |
| ARG_UNUSED(addrlen); |
| |
| if (status != 0) { |
| net_context_put(net_ctx); |
| return; |
| } |
| |
| /* If we receive a HTTP request and if the earlier context is still |
| * active and it is not the same as the new one, then close the earlier |
| * one. Otherwise it is possible that the context will be left into |
| * TCP ESTABLISHED state and would never be released. Example of this |
| * is that we had IPv4 connection active and then IPv6 connection is |
| * established, in this case we disconnect the IPv4 here. |
| */ |
| if (http_ctx->req.net_ctx && http_ctx->req.net_ctx != net_ctx && |
| net_context_get_state(http_ctx->req.net_ctx) == |
| NET_CONTEXT_CONNECTED) { |
| net_context_put(http_ctx->req.net_ctx); |
| } |
| |
| http_ctx->req.net_ctx = net_ctx; |
| |
| new_client(http_ctx, net_ctx, addr); |
| |
| ret = net_context_recv(net_ctx, http_ctx->recv_cb, K_NO_WAIT, |
| http_ctx); |
| if (ret < 0) { |
| NET_DBG("Cannot set recv_cb (%d)", ret); |
| } |
| } |
| |
| static int set_net_ctx(struct http_server_ctx *http_ctx, |
| struct net_context *ctx, |
| struct sockaddr *addr, |
| socklen_t socklen) |
| { |
| int ret; |
| |
| ret = net_context_bind(ctx, addr, socklen); |
| if (ret < 0) { |
| NET_ERR("Cannot bind context (%d)", ret); |
| goto out; |
| } |
| |
| ret = net_context_listen(ctx, 0); |
| if (ret < 0) { |
| NET_ERR("Cannot listen context (%d)", ret); |
| goto out; |
| } |
| |
| ret = net_context_accept(ctx, accept_cb, K_NO_WAIT, http_ctx); |
| if (ret < 0) { |
| NET_ERR("Cannot accept context (%d)", ret); |
| goto out; |
| } |
| |
| out: |
| return ret; |
| } |
| |
| #if defined(CONFIG_NET_IPV4) |
| static int setup_ipv4_ctx(struct http_server_ctx *http_ctx, |
| struct sockaddr *addr) |
| { |
| socklen_t socklen; |
| int ret; |
| |
| socklen = sizeof(struct sockaddr_in); |
| |
| ret = net_context_get(AF_INET, SOCK_STREAM, IPPROTO_TCP, |
| &http_ctx->net_ipv4_ctx); |
| if (ret < 0) { |
| NET_ERR("Cannot get network context (%d)", ret); |
| http_ctx->net_ipv4_ctx = NULL; |
| return ret; |
| } |
| |
| net_context_setup_pools(http_ctx->net_ipv4_ctx, http_ctx->tx_slab, |
| http_ctx->data_pool); |
| |
| if (addr->sa_family == AF_UNSPEC) { |
| addr->sa_family = AF_INET; |
| |
| http_server_set_local_addr(addr, NULL, |
| net_sin(addr)->sin_port); |
| } |
| |
| ret = set_net_ctx(http_ctx, http_ctx->net_ipv4_ctx, |
| addr, socklen); |
| if (ret < 0) { |
| net_context_put(http_ctx->net_ipv4_ctx); |
| http_ctx->net_ipv4_ctx = NULL; |
| } |
| |
| return ret; |
| } |
| #endif |
| |
| #if defined(CONFIG_NET_IPV6) |
| int setup_ipv6_ctx(struct http_server_ctx *http_ctx, struct sockaddr *addr) |
| { |
| socklen_t socklen; |
| int ret; |
| |
| socklen = sizeof(struct sockaddr_in6); |
| |
| ret = net_context_get(AF_INET6, SOCK_STREAM, IPPROTO_TCP, |
| &http_ctx->net_ipv6_ctx); |
| if (ret < 0) { |
| NET_ERR("Cannot get network context (%d)", ret); |
| http_ctx->net_ipv6_ctx = NULL; |
| return ret; |
| } |
| |
| net_context_setup_pools(http_ctx->net_ipv6_ctx, http_ctx->tx_slab, |
| http_ctx->data_pool); |
| |
| if (addr->sa_family == AF_UNSPEC) { |
| addr->sa_family = AF_INET6; |
| |
| http_server_set_local_addr(addr, NULL, |
| net_sin6(addr)->sin6_port); |
| } |
| |
| ret = set_net_ctx(http_ctx, http_ctx->net_ipv6_ctx, |
| addr, socklen); |
| if (ret < 0) { |
| net_context_put(http_ctx->net_ipv6_ctx); |
| http_ctx->net_ipv6_ctx = NULL; |
| } |
| |
| return ret; |
| } |
| #endif |
| |
| static int init_net(struct http_server_ctx *ctx, |
| struct sockaddr *server_addr, |
| u16_t port) |
| { |
| struct sockaddr addr; |
| int ret; |
| |
| memset(&addr, 0, sizeof(addr)); |
| |
| if (server_addr) { |
| memcpy(&addr, server_addr, sizeof(addr)); |
| } else { |
| addr.sa_family = AF_UNSPEC; |
| net_sin(&addr)->sin_port = htons(port); |
| } |
| |
| if (addr.sa_family == AF_INET6) { |
| #if defined(CONFIG_NET_IPV6) |
| ret = setup_ipv6_ctx(ctx, &addr); |
| #else |
| return -EPFNOSUPPORT; |
| #endif |
| } else if (addr.sa_family == AF_INET) { |
| #if defined(CONFIG_NET_IPV4) |
| ret = setup_ipv4_ctx(ctx, &addr); |
| #else |
| return -EPFNOSUPPORT; |
| #endif |
| } else if (addr.sa_family == AF_UNSPEC) { |
| #if defined(CONFIG_NET_IPV4) |
| ret = setup_ipv4_ctx(ctx, &addr); |
| #endif |
| /* We ignore the IPv4 error if IPv6 is enabled */ |
| #if defined(CONFIG_NET_IPV6) |
| memset(&addr, 0, sizeof(addr)); |
| addr.sa_family = AF_UNSPEC; |
| net_sin6(&addr)->sin6_port = htons(port); |
| |
| ret = setup_ipv6_ctx(ctx, &addr); |
| #endif |
| } else { |
| return -EINVAL; |
| } |
| |
| return ret; |
| } |
| |
| bool http_server_enable(struct http_server_ctx *http_ctx) |
| { |
| bool old; |
| |
| NET_ASSERT(http_ctx); |
| |
| old = http_ctx->enabled; |
| |
| http_ctx->enabled = true; |
| |
| #if defined(CONFIG_HTTPS) |
| if (http_ctx->is_https) { |
| https_enable(http_ctx); |
| } |
| #endif |
| return old; |
| } |
| |
| bool http_server_disable(struct http_server_ctx *http_ctx) |
| { |
| bool old; |
| |
| NET_ASSERT(http_ctx); |
| |
| req_timer_cancel(http_ctx); |
| |
| old = http_ctx->enabled; |
| |
| http_ctx->enabled = false; |
| |
| #if defined(CONFIG_HTTPS) |
| if (http_ctx->is_https) { |
| https_disable(http_ctx); |
| } |
| #endif |
| return old; |
| } |
| |
| int http_server_init(struct http_server_ctx *http_ctx, |
| struct http_server_urls *urls, |
| struct sockaddr *server_addr, |
| u8_t *request_buf, |
| size_t request_buf_len, |
| const char *server_banner) |
| { |
| int ret; |
| |
| if (http_ctx->urls) { |
| NET_ERR("Server context %p already initialized", http_ctx); |
| return -EINVAL; |
| } |
| |
| if (!request_buf || request_buf_len == 0) { |
| NET_ERR("Request buf must be set"); |
| return -EINVAL; |
| } |
| |
| ret = init_net(http_ctx, server_addr, HTTP_DEFAULT_PORT); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| if (server_banner) { |
| new_server(http_ctx, server_banner, server_addr); |
| } |
| |
| http_ctx->req.request_buf = request_buf; |
| http_ctx->req.request_buf_len = request_buf_len; |
| http_ctx->req.data_len = 0; |
| http_ctx->urls = urls; |
| http_ctx->recv_cb = http_recv; |
| http_ctx->send_data = net_context_send; |
| |
| k_delayed_work_init(&http_ctx->req.timer, req_timeout); |
| |
| parser_init(http_ctx); |
| |
| return 0; |
| } |
| |
| void http_server_release(struct http_server_ctx *http_ctx) |
| { |
| if (!http_ctx->urls) { |
| return; |
| } |
| |
| http_server_disable(http_ctx); |
| |
| #if defined(CONFIG_NET_IPV4) |
| if (http_ctx->net_ipv4_ctx) { |
| net_context_put(http_ctx->net_ipv4_ctx); |
| http_ctx->net_ipv4_ctx = NULL; |
| } |
| #endif |
| #if defined(CONFIG_NET_IPV6) |
| if (http_ctx->net_ipv6_ctx) { |
| net_context_put(http_ctx->net_ipv6_ctx); |
| http_ctx->net_ipv6_ctx = NULL; |
| } |
| #endif |
| |
| http_ctx->req.net_ctx = NULL; |
| http_ctx->urls = NULL; |
| } |
| |
| #if defined(CONFIG_HTTPS) |
| struct rx_fifo_block { |
| sys_snode_t snode; |
| struct k_mem_block block; |
| struct net_pkt *pkt; |
| }; |
| |
| #if defined(MBEDTLS_DEBUG_C) && defined(CONFIG_NET_DEBUG_HTTP) |
| static void my_debug(void *ctx, int level, |
| const char *file, int line, const char *str) |
| { |
| const char *p, *basename; |
| int len; |
| |
| ARG_UNUSED(ctx); |
| |
| /* Extract basename from file */ |
| for (p = basename = file; *p != '\0'; p++) { |
| if (*p == '/' || *p == '\\') { |
| basename = p + 1; |
| } |
| |
| } |
| |
| /* Avoid printing double newlines */ |
| len = strlen(str); |
| if (str[len - 1] == '\n') { |
| ((char *)str)[len - 1] = '\0'; |
| } |
| |
| NET_DBG("%s:%04d: |%d| %s", basename, line, level, str); |
| } |
| #endif /* MBEDTLS_DEBUG_C && CONFIG_NET_DEBUG_HTTP */ |
| |
| #if defined(MBEDTLS_ERROR_C) |
| #define print_error(fmt, ret) \ |
| do { \ |
| char error[64]; \ |
| \ |
| mbedtls_strerror(ret, error, sizeof(error)); \ |
| \ |
| NET_ERR(fmt " (%s)", -ret, error); \ |
| } while (0) |
| #else |
| #define print_error(fmt, ret) \ |
| do { \ |
| NET_ERR(fmt, -ret); \ |
| } while (0) |
| #endif |
| |
| #define BUF_ALLOC_TIMEOUT 100 |
| |
| /* Receive encrypted data from network. Put that data into fifo |
| * that will be read by https thread. |
| */ |
| static void ssl_received(struct net_context *context, |
| struct net_pkt *pkt, |
| int status, |
| void *user_data) |
| { |
| struct http_server_ctx *http_ctx = user_data; |
| struct rx_fifo_block *rx_data = NULL; |
| struct k_mem_block block; |
| int ret; |
| |
| ARG_UNUSED(context); |
| ARG_UNUSED(status); |
| |
| if (pkt && !net_pkt_appdatalen(pkt)) { |
| net_pkt_unref(pkt); |
| return; |
| } |
| |
| ret = k_mem_pool_alloc(http_ctx->https.pool, &block, |
| sizeof(struct rx_fifo_block), |
| BUF_ALLOC_TIMEOUT); |
| if (ret < 0) { |
| net_pkt_unref(pkt); |
| return; |
| } |
| |
| rx_data = block.data; |
| rx_data->pkt = pkt; |
| |
| /* For freeing memory later */ |
| memcpy(&rx_data->block, &block, sizeof(struct k_mem_block)); |
| |
| k_fifo_put(&http_ctx->https.mbedtls.ssl_ctx.rx_fifo, (void *)rx_data); |
| } |
| |
| /* This will copy data from received net_pkt buf into mbedtls internal buffers. |
| */ |
| static int ssl_rx(void *context, unsigned char *buf, size_t size) |
| { |
| struct http_server_ctx *ctx = context; |
| struct rx_fifo_block *rx_data; |
| u16_t read_bytes; |
| u8_t *ptr; |
| int pos; |
| int len; |
| int ret = 0; |
| |
| if (!ctx->https.mbedtls.ssl_ctx.frag) { |
| rx_data = k_fifo_get(&ctx->https.mbedtls.ssl_ctx.rx_fifo, |
| K_FOREVER); |
| if (!rx_data->pkt) { |
| NET_DBG("Closing %p connection", ctx); |
| k_mem_pool_free(&rx_data->block); |
| return -EIO; |
| } |
| |
| ctx->https.mbedtls.ssl_ctx.rx_pkt = rx_data->pkt; |
| |
| k_mem_pool_free(&rx_data->block); |
| |
| read_bytes = net_pkt_appdatalen( |
| ctx->https.mbedtls.ssl_ctx.rx_pkt); |
| |
| ctx->https.mbedtls.ssl_ctx.remaining = read_bytes; |
| ctx->https.mbedtls.ssl_ctx.frag = |
| ctx->https.mbedtls.ssl_ctx.rx_pkt->frags; |
| |
| ptr = net_pkt_appdata(ctx->https.mbedtls.ssl_ctx.rx_pkt); |
| len = ptr - ctx->https.mbedtls.ssl_ctx.frag->data; |
| |
| if (len > ctx->https.mbedtls.ssl_ctx.frag->size) { |
| NET_ERR("Buf overflow (%d > %u)", len, |
| ctx->https.mbedtls.ssl_ctx.frag->size); |
| return -EINVAL; |
| } else { |
| /* This will get rid of IP header */ |
| net_buf_pull(ctx->https.mbedtls.ssl_ctx.frag, len); |
| } |
| } else { |
| read_bytes = ctx->https.mbedtls.ssl_ctx.remaining; |
| ptr = ctx->https.mbedtls.ssl_ctx.frag->data; |
| } |
| |
| len = ctx->https.mbedtls.ssl_ctx.frag->len; |
| pos = 0; |
| if (read_bytes > size) { |
| while (ctx->https.mbedtls.ssl_ctx.frag) { |
| read_bytes = len < (size - pos) ? len : (size - pos); |
| |
| #if RX_EXTRA_DEBUG == 1 |
| NET_DBG("Copying %d bytes", read_bytes); |
| #endif |
| |
| memcpy(buf + pos, ptr, read_bytes); |
| |
| pos += read_bytes; |
| if (pos < size) { |
| ctx->https.mbedtls.ssl_ctx.frag = |
| ctx->https.mbedtls.ssl_ctx.frag->frags; |
| ptr = ctx->https.mbedtls.ssl_ctx.frag->data; |
| len = ctx->https.mbedtls.ssl_ctx.frag->len; |
| } else { |
| if (read_bytes == len) { |
| ctx->https.mbedtls.ssl_ctx.frag = |
| ctx->https.mbedtls.ssl_ctx.frag->frags; |
| } else { |
| net_buf_pull( |
| ctx->https.mbedtls.ssl_ctx.frag, |
| read_bytes); |
| } |
| |
| ctx->https.mbedtls.ssl_ctx.remaining -= size; |
| return size; |
| } |
| } |
| } else { |
| while (ctx->https.mbedtls.ssl_ctx.frag) { |
| #if RX_EXTRA_DEBUG == 1 |
| NET_DBG("Copying all %d bytes", len); |
| #endif |
| |
| memcpy(buf + pos, ptr, len); |
| |
| pos += len; |
| ctx->https.mbedtls.ssl_ctx.frag = |
| ctx->https.mbedtls.ssl_ctx.frag->frags; |
| if (!ctx->https.mbedtls.ssl_ctx.frag) { |
| break; |
| } |
| |
| ptr = ctx->https.mbedtls.ssl_ctx.frag->data; |
| len = ctx->https.mbedtls.ssl_ctx.frag->len; |
| } |
| |
| net_pkt_unref(ctx->https.mbedtls.ssl_ctx.rx_pkt); |
| ctx->https.mbedtls.ssl_ctx.rx_pkt = NULL; |
| ctx->https.mbedtls.ssl_ctx.frag = NULL; |
| ctx->https.mbedtls.ssl_ctx.remaining = 0; |
| |
| if (read_bytes != pos) { |
| return -EIO; |
| } |
| |
| ret = read_bytes; |
| } |
| |
| return ret; |
| } |
| |
| static void ssl_sent(struct net_context *context, |
| int status, void *token, void *user_data) |
| { |
| struct http_server_ctx *http_ctx = user_data; |
| |
| k_sem_give(&http_ctx->https.mbedtls.ssl_ctx.tx_sem); |
| } |
| |
| /* Send encrypted data */ |
| static int ssl_tx(void *context, const unsigned char *buf, size_t size) |
| { |
| struct http_server_ctx *ctx = context; |
| struct net_pkt *send_buf; |
| int ret, len; |
| |
| send_buf = net_pkt_get_tx(ctx->req.net_ctx, BUF_ALLOC_TIMEOUT); |
| if (!send_buf) { |
| return MBEDTLS_ERR_SSL_ALLOC_FAILED; |
| } |
| |
| ret = net_pkt_append_all(send_buf, size, (u8_t *)buf, |
| BUF_ALLOC_TIMEOUT); |
| if (!ret) { |
| /* Cannot append data */ |
| net_pkt_unref(send_buf); |
| return 0; |
| } |
| |
| len = size; |
| |
| ret = net_context_send(send_buf, ssl_sent, K_NO_WAIT, NULL, ctx); |
| if (ret < 0) { |
| net_pkt_unref(send_buf); |
| return MBEDTLS_ERR_SSL_INTERNAL_ERROR; |
| } |
| |
| k_sem_take(&ctx->https.mbedtls.ssl_ctx.tx_sem, K_FOREVER); |
| |
| return len; |
| } |
| |
| static int entropy_source(void *data, unsigned char *output, size_t len, |
| size_t *olen) |
| { |
| u32_t seed; |
| |
| ARG_UNUSED(data); |
| |
| seed = sys_rand32_get(); |
| |
| if (len > sizeof(seed)) { |
| len = sizeof(seed); |
| } |
| |
| memcpy(output, &seed, len); |
| |
| *olen = len; |
| return 0; |
| } |
| |
| /* This gets plain data and it sends encrypted one to peer */ |
| static int https_send(struct net_pkt *pkt, |
| net_context_send_cb_t cb, |
| s32_t timeout, |
| void *token, |
| void *user_data) |
| { |
| struct http_server_ctx *ctx = user_data; |
| int ret; |
| u16_t len; |
| |
| len = net_pkt_appdatalen(pkt); |
| |
| ret = net_frag_linearize(ctx->req.request_buf, |
| ctx->req.request_buf_len, |
| pkt, net_pkt_ip_hdr_len(pkt), |
| len); |
| if (ret < 0) { |
| NET_DBG("Cannot linearize send data (%d)", ret); |
| return ret; |
| } |
| |
| if (ret != len) { |
| NET_DBG("Linear copy error (%u vs %d)", len, ret); |
| return -EINVAL; |
| } |
| |
| do { |
| ret = mbedtls_ssl_write(&ctx->https.mbedtls.ssl, |
| ctx->req.request_buf, len); |
| if (ret == MBEDTLS_ERR_NET_CONN_RESET) { |
| print_error("peer closed the connection -0x%x", ret); |
| goto out; |
| } |
| |
| if (ret != MBEDTLS_ERR_SSL_WANT_READ && |
| ret != MBEDTLS_ERR_SSL_WANT_WRITE) { |
| if (ret < 0) { |
| print_error("mbedtls_ssl_write returned " |
| "-0x%x", ret); |
| goto out; |
| } |
| } |
| } while (ret <= 0); |
| |
| out: |
| if (cb) { |
| cb(net_pkt_context(pkt), ret, token, user_data); |
| } |
| |
| return ret; |
| } |
| |
| static void https_handler(struct http_server_ctx *ctx) |
| { |
| size_t len; |
| int ret; |
| |
| NET_DBG("HTTPS handler starting"); |
| |
| mbedtls_platform_set_printf(printk); |
| |
| #if defined(MBEDTLS_X509_CRT_PARSE_C) |
| mbedtls_x509_crt_init(&ctx->https.mbedtls.srvcert); |
| #endif |
| |
| mbedtls_pk_init(&ctx->https.mbedtls.pkey); |
| mbedtls_ssl_init(&ctx->https.mbedtls.ssl); |
| mbedtls_ssl_config_init(&ctx->https.mbedtls.conf); |
| mbedtls_entropy_init(&ctx->https.mbedtls.entropy); |
| mbedtls_ctr_drbg_init(&ctx->https.mbedtls.ctr_drbg); |
| |
| #if defined(MBEDTLS_DEBUG_C) && defined(CONFIG_NET_DEBUG_HTTP) |
| mbedtls_debug_set_threshold(DEBUG_THRESHOLD); |
| mbedtls_ssl_conf_dbg(&ctx->https.mbedtls.conf, my_debug, NULL); |
| #endif |
| |
| /* Load the certificates and private RSA key. This needs to be done |
| * by the user so we call a callback that user must have provided. |
| */ |
| ret = ctx->https.mbedtls.cert_cb(ctx, &ctx->https.mbedtls.srvcert, |
| &ctx->https.mbedtls.pkey); |
| if (ret != 0) { |
| goto exit; |
| } |
| |
| /* Seed the RNG */ |
| mbedtls_entropy_add_source(&ctx->https.mbedtls.entropy, |
| ctx->https.mbedtls.entropy_src_cb, |
| NULL, |
| MBEDTLS_ENTROPY_MAX_GATHER, |
| MBEDTLS_ENTROPY_SOURCE_STRONG); |
| |
| ret = mbedtls_ctr_drbg_seed( |
| &ctx->https.mbedtls.ctr_drbg, |
| mbedtls_entropy_func, |
| &ctx->https.mbedtls.entropy, |
| (const unsigned char *)ctx->https.mbedtls.personalization_data, |
| ctx->https.mbedtls.personalization_data_len); |
| if (ret != 0) { |
| print_error("mbedtls_ctr_drbg_seed returned -0x%x", ret); |
| goto exit; |
| } |
| |
| /* Setup SSL defaults etc. */ |
| ret = mbedtls_ssl_config_defaults(&ctx->https.mbedtls.conf, |
| MBEDTLS_SSL_IS_SERVER, |
| MBEDTLS_SSL_TRANSPORT_STREAM, |
| MBEDTLS_SSL_PRESET_DEFAULT); |
| if (ret != 0) { |
| print_error("mbedtls_ssl_config_defaults returned -0x%x", ret); |
| goto exit; |
| } |
| |
| mbedtls_ssl_conf_rng(&ctx->https.mbedtls.conf, |
| mbedtls_ctr_drbg_random, |
| &ctx->https.mbedtls.ctr_drbg); |
| |
| #if defined(MBEDTLS_X509_CRT_PARSE_C) |
| mbedtls_ssl_conf_ca_chain(&ctx->https.mbedtls.conf, |
| ctx->https.mbedtls.srvcert.next, |
| NULL); |
| |
| ret = mbedtls_ssl_conf_own_cert(&ctx->https.mbedtls.conf, |
| &ctx->https.mbedtls.srvcert, |
| &ctx->https.mbedtls.pkey); |
| if (ret != 0) { |
| print_error("mbedtls_ssl_conf_own_cert returned -0x%x", ret); |
| goto exit; |
| } |
| #endif /* MBEDTLS_X509_CRT_PARSE_C */ |
| |
| ret = mbedtls_ssl_setup(&ctx->https.mbedtls.ssl, |
| &ctx->https.mbedtls.conf); |
| if (ret != 0) { |
| print_error("mbedtls_ssl_setup returned -0x%x", ret); |
| goto exit; |
| } |
| |
| reset: |
| mbedtls_ssl_session_reset(&ctx->https.mbedtls.ssl); |
| mbedtls_ssl_set_bio(&ctx->https.mbedtls.ssl, ctx, ssl_tx, |
| ssl_rx, NULL); |
| |
| /* SSL handshake. The ssl_rx() function will be called next by |
| * mbedtls library. The ssl_rx() will block and wait that data is |
| * received by ssl_received() and passed to it via fifo. After |
| * receiving the data, this function will then proceed with secure |
| * connection establishment. |
| */ |
| /* Waiting SSL handshake */ |
| do { |
| ret = mbedtls_ssl_handshake(&ctx->https.mbedtls.ssl); |
| if (ret != MBEDTLS_ERR_SSL_WANT_READ && |
| ret != MBEDTLS_ERR_SSL_WANT_WRITE) { |
| if (ret < 0) { |
| goto reset; |
| } |
| } |
| } while (ret != 0); |
| |
| /* Read the HTTPS Request */ |
| NET_DBG("Read HTTPS request"); |
| do { |
| len = ctx->req.request_buf_len - 1; |
| memset(ctx->req.request_buf, 0, ctx->req.request_buf_len); |
| |
| ret = mbedtls_ssl_read(&ctx->https.mbedtls.ssl, |
| ctx->req.request_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: |
| NET_DBG("Connection was closed gracefully"); |
| goto close; |
| |
| case MBEDTLS_ERR_NET_CONN_RESET: |
| NET_DBG("Connection was reset by peer"); |
| break; |
| |
| default: |
| print_error("mbedtls_ssl_read returned " |
| "-0x%x", ret); |
| break; |
| } |
| |
| goto close; |
| } |
| |
| ret = http_parser_execute(&ctx->req.parser, |
| &ctx->req.settings, |
| ctx->req.request_buf, |
| ret); |
| } while (ret < 0); |
| |
| /* Write the Response */ |
| NET_DBG("Write HTTPS response"); |
| |
| if (ctx->req.parser.http_errno != HPE_OK) { |
| http_response_400(ctx, NULL); |
| } else { |
| http_process_recv(ctx); |
| } |
| |
| close: |
| http_parser_init(&ctx->req.parser, HTTP_REQUEST); |
| ctx->req.field_values_ctr = 0; |
| |
| mbedtls_ssl_close_notify(&ctx->https.mbedtls.ssl); |
| |
| goto reset; |
| |
| exit: |
| return; |
| } |
| |
| static void https_enable(struct http_server_ctx *ctx) |
| { |
| /* Start the thread that handles HTTPS traffic. */ |
| if (ctx->https.tid) { |
| return; |
| } |
| |
| ctx->https.tid = k_thread_create(&ctx->https.thread, |
| ctx->https.stack, |
| ctx->https.stack_size, |
| (k_thread_entry_t)https_handler, |
| ctx, NULL, NULL, |
| K_PRIO_COOP(7), 0, 0); |
| } |
| |
| static void https_disable(struct http_server_ctx *ctx) |
| { |
| if (!ctx->https.tid) { |
| return; |
| } |
| |
| mbedtls_ssl_free(&ctx->https.mbedtls.ssl); |
| mbedtls_ssl_config_free(&ctx->https.mbedtls.conf); |
| mbedtls_ctr_drbg_free(&ctx->https.mbedtls.ctr_drbg); |
| mbedtls_entropy_free(&ctx->https.mbedtls.entropy); |
| |
| /* Empty the fifo just in case there is any received packets |
| * still there. |
| */ |
| while (1) { |
| struct rx_fifo_block *rx_data; |
| |
| rx_data = k_fifo_get(&ctx->https.mbedtls.ssl_ctx.rx_fifo, |
| K_NO_WAIT); |
| if (!rx_data) { |
| break; |
| } |
| |
| net_pkt_unref(rx_data->pkt); |
| |
| k_mem_pool_free(&rx_data->block); |
| } |
| |
| NET_DBG("HTTPS thread %p stopped", ctx->https.tid); |
| |
| k_thread_abort(ctx->https.tid); |
| ctx->https.tid = 0; |
| } |
| |
| static int https_init(struct http_server_ctx *ctx) |
| { |
| k_sem_init(&ctx->https.mbedtls.ssl_ctx.tx_sem, 0, UINT_MAX); |
| k_fifo_init(&ctx->https.mbedtls.ssl_ctx.rx_fifo); |
| |
| /* Next we return to application which must then enable the HTTPS |
| * service. The enable function will then start the https thread and |
| * do what ever further configuration needed. |
| * |
| * We do the mbedtls initialization in its own thread because it uses |
| * of of stack and the main stack runs out of memory very easily. |
| * |
| * See https_handler() how the things proceed from now on. |
| */ |
| |
| return 0; |
| } |
| |
| int https_server_init(struct http_server_ctx *ctx, |
| struct http_server_urls *urls, |
| struct sockaddr *server_addr, |
| u8_t *request_buf, |
| size_t request_buf_len, |
| const char *server_banner, |
| u8_t *personalization_data, |
| size_t personalization_data_len, |
| https_server_cert_cb_t cert_cb, |
| https_entropy_src_cb_t entropy_src_cb, |
| struct k_mem_pool *pool, |
| k_thread_stack_t *https_stack, |
| size_t https_stack_size) |
| { |
| int ret; |
| |
| if (ctx->urls) { |
| NET_ERR("Server context %p already initialized", ctx); |
| return -EALREADY; |
| } |
| |
| if (!request_buf || request_buf_len == 0) { |
| NET_ERR("Request buf must be set"); |
| return -EINVAL; |
| } |
| |
| if (!cert_cb) { |
| NET_ERR("Cert callback must be set"); |
| return -EINVAL; |
| } |
| |
| ret = init_net(ctx, server_addr, HTTPS_DEFAULT_PORT); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| if (server_banner) { |
| new_server(ctx, server_banner, server_addr); |
| } |
| |
| ctx->req.request_buf = request_buf; |
| ctx->req.request_buf_len = request_buf_len; |
| ctx->req.data_len = 0; |
| ctx->urls = urls; |
| ctx->is_https = true; |
| ctx->https.stack = https_stack; |
| ctx->https.stack_size = https_stack_size; |
| ctx->https.mbedtls.cert_cb = cert_cb; |
| ctx->https.pool = pool; |
| |
| if (entropy_src_cb) { |
| ctx->https.mbedtls.entropy_src_cb = entropy_src_cb; |
| } else { |
| ctx->https.mbedtls.entropy_src_cb = entropy_source; |
| } |
| |
| ctx->https.mbedtls.personalization_data = personalization_data; |
| ctx->https.mbedtls.personalization_data_len = personalization_data_len; |
| ctx->send_data = https_send; |
| ctx->recv_cb = ssl_received; |
| |
| k_delayed_work_init(&ctx->req.timer, req_timeout); |
| |
| parser_init(ctx); |
| |
| /* Then mbedtls specific initialization */ |
| return https_init(ctx); |
| } |
| #endif /* CONFIG_HTTPS */ |
| |
| #if defined(CONFIG_NET_CONTEXT_NET_PKT_POOL) |
| int http_server_set_net_pkt_pool(struct http_server_ctx *ctx, |
| net_pkt_get_slab_func_t tx_slab, |
| net_pkt_get_pool_func_t data_pool) |
| { |
| ctx->tx_slab = tx_slab; |
| ctx->data_pool = data_pool; |
| |
| return 0; |
| } |
| #endif /* CONFIG_NET_CONTEXT_NET_PKT_POOL */ |