From fcab1ed1463755e411b1e705cee3da2488fd0166 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Sat, 28 Oct 2023 15:14:29 +0300 Subject: [PATCH 1/5] net: http: service: Allow user to set TLS security tag list Have separate macros to setup a HTTPS service. Signed-off-by: Jukka Rissanen --- include/zephyr/net/http/service.h | 70 ++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/include/zephyr/net/http/service.h b/include/zephyr/net/http/service.h index b6997334de46b..3e48147cd01f7 100644 --- a/include/zephyr/net/http/service.h +++ b/include/zephyr/net/http/service.h @@ -10,7 +10,9 @@ #include #include +#include #include +#include #ifdef __cplusplus extern "C" { @@ -53,10 +55,12 @@ struct http_service_desc { size_t backlog; struct http_resource_desc *res_begin; struct http_resource_desc *res_end; + const sec_tag_t *sec_tag_list; + size_t sec_tag_list_size; }; -#define __z_http_service_define(_name, _host, _port, _concurrent, _backlog, _detail, _res_begin, \ - _res_end) \ +#define __z_http_service_define(_name, _host, _port, _concurrent, _backlog, _detail, _res_begin, \ + _res_end, ...) \ static const STRUCT_SECTION_ITERABLE(http_service_desc, _name) = { \ .host = _host, \ .port = (uint16_t *)(_port), \ @@ -65,6 +69,10 @@ struct http_service_desc { .backlog = (_backlog), \ .res_begin = (_res_begin), \ .res_end = (_res_end), \ + .sec_tag_list = COND_CODE_0(NUM_VA_ARGS_LESS_1(__VA_ARGS__), (NULL), \ + (GET_ARG_N(1, __VA_ARGS__))), \ + .sec_tag_list_size = COND_CODE_0(NUM_VA_ARGS_LESS_1(__VA_ARGS__), (0), \ + (GET_ARG_N(1, GET_ARGS_LESS_N(1, __VA_ARGS__)))), \ } /** @@ -88,6 +96,33 @@ struct http_service_desc { #define HTTP_SERVICE_DEFINE_EMPTY(_name, _host, _port, _concurrent, _backlog, _detail) \ __z_http_service_define(_name, _host, _port, _concurrent, _backlog, _detail, NULL, NULL) +/** + * @brief Define an HTTPS service without static resources. + * + * @note The @p _host parameter must be non-`NULL`. It is used to specify an IP address either in + * IPv4 or IPv6 format a fully-qualified hostname or a virtual host. + * + * @note The @p _port parameter must be non-`NULL`. It points to a location that specifies the port + * number to use for the service. If the specified port number is zero, then an ephemeral port + * number will be used and the actual port number assigned will be written back to memory. For + * ephemeral port numbers, the memory pointed to by @p _port must be writeable. + * + * @param _name Name of the service. + * @param _host IP address or hostname associated with the service. + * @param[inout] _port Pointer to port associated with the service. + * @param _concurrent Maximum number of concurrent clients. + * @param _backlog Maximum number queued connections. + * @param _detail Implementation-specific detail associated with the service. + * @param _sec_tag_list TLS security tag list used to setup a HTTPS socket. + * @param _sec_tag_list_size TLS security tag list size used to setup a HTTPS socket. + */ +#define HTTPS_SERVICE_DEFINE_EMPTY(_name, _host, _port, _concurrent, _backlog, _detail, \ + _sec_tag_list, _sec_tag_list_size) \ + __z_http_service_define(_name, _host, _port, _concurrent, _backlog, _detail, NULL, NULL, \ + _sec_tag_list, _sec_tag_list_size); \ + BUILD_ASSERT(IS_ENABLED(CONFIG_NET_SOCKETS_SOCKOPT_TLS), \ + "TLS is required for HTTP secure (CONFIG_NET_SOCKETS_SOCKOPT_TLS)") + /** * @brief Define an HTTP service with static resources. * @@ -113,6 +148,37 @@ struct http_service_desc { &_CONCAT(_http_resource_desc_##_name, _list_start)[0], \ &_CONCAT(_http_resource_desc_##_name, _list_end)[0]) +/** + * @brief Define an HTTPS service with static resources. + * + * @note The @p _host parameter must be non-`NULL`. It is used to specify an IP address either in + * IPv4 or IPv6 format a fully-qualified hostname or a virtual host. + * + * @note The @p _port parameter must be non-`NULL`. It points to a location that specifies the port + * number to use for the service. If the specified port number is zero, then an ephemeral port + * number will be used and the actual port number assigned will be written back to memory. For + * ephemeral port numbers, the memory pointed to by @p _port must be writeable. + * + * @param _name Name of the service. + * @param _host IP address or hostname associated with the service. + * @param[inout] _port Pointer to port associated with the service. + * @param _concurrent Maximum number of concurrent clients. + * @param _backlog Maximum number queued connections. + * @param _detail Implementation-specific detail associated with the service. + * @param _sec_tag_list TLS security tag list used to setup a HTTPS socket. + * @param _sec_tag_list_size TLS security tag list size used to setup a HTTPS socket. + */ +#define HTTPS_SERVICE_DEFINE(_name, _host, _port, _concurrent, _backlog, _detail, \ + _sec_tag_list, _sec_tag_list_size) \ + extern struct http_resource_desc _CONCAT(_http_resource_desc_##_name, _list_start)[]; \ + extern struct http_resource_desc _CONCAT(_http_resource_desc_##_name, _list_end)[]; \ + __z_http_service_define(_name, _host, _port, _concurrent, _backlog, _detail, \ + &_CONCAT(_http_resource_desc_##_name, _list_start)[0], \ + &_CONCAT(_http_resource_desc_##_name, _list_end)[0], \ + _sec_tag_list, _sec_tag_list_size); \ + BUILD_ASSERT(IS_ENABLED(CONFIG_NET_SOCKETS_SOCKOPT_TLS), \ + "TLS is required for HTTP secure (CONFIG_NET_SOCKETS_SOCKOPT_TLS)") + /** * @brief Count the number of HTTP services. * From 619f350e51b071c2e8c1f44627b9e74db9c047e3 Mon Sep 17 00:00:00 2001 From: Robert Lubos Date: Fri, 26 Apr 2024 12:24:29 +0200 Subject: [PATCH 2/5] net: lib: http_server: Add HPACK and Huffman code encoder/decoder Add HTTP/2 helper libraries to encode and decode HPACK encoded headers, according to RFC7541. HPACK string encoding requires to support certain set of Huffman codes, therefore implement Huffman encoder/decoder as well. Signed-off-by: Robert Lubos --- include/zephyr/net/http/hpack.h | 135 ++++++ subsys/net/lib/http/http_hpack.c | 631 +++++++++++++++++++++++++++++ subsys/net/lib/http/http_huffman.c | 480 ++++++++++++++++++++++ 3 files changed, 1246 insertions(+) create mode 100644 include/zephyr/net/http/hpack.h create mode 100644 subsys/net/lib/http/http_hpack.c create mode 100644 subsys/net/lib/http/http_huffman.c diff --git a/include/zephyr/net/http/hpack.h b/include/zephyr/net/http/hpack.h new file mode 100644 index 0000000000000..374b083ce091a --- /dev/null +++ b/include/zephyr/net/http/hpack.h @@ -0,0 +1,135 @@ +/** @file + * @brief HTTP HPACK + */ + +/* + * Copyright (c) 2023 Emna Rekik + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_NET_HTTP_SERVER_HPACK_H_ +#define ZEPHYR_INCLUDE_NET_HTTP_SERVER_HPACK_H_ + +#include +#include + +/** + * @brief HTTP HPACK + * @defgroup http_hpack HTTP HPACK + * @ingroup networking + * @{ + */ + +#ifdef __cplusplus +extern "C" { +#endif + +enum http_hpack_static_key { + HTTP_SERVER_HPACK_INVALID = 0, + HTTP_SERVER_HPACK_AUTHORITY = 1, + HTTP_SERVER_HPACK_METHOD_GET = 2, + HTTP_SERVER_HPACK_METHOD_POST = 3, + HTTP_SERVER_HPACK_PATH_ROOT = 4, + HTTP_SERVER_HPACK_PATH_INDEX = 5, + HTTP_SERVER_HPACK_SCHEME_HTTP = 6, + HTTP_SERVER_HPACK_SCHEME_HTTPS = 7, + HTTP_SERVER_HPACK_STATUS_200 = 8, + HTTP_SERVER_HPACK_STATUS_204 = 9, + HTTP_SERVER_HPACK_STATUS_206 = 10, + HTTP_SERVER_HPACK_STATUS_304 = 11, + HTTP_SERVER_HPACK_STATUS_400 = 12, + HTTP_SERVER_HPACK_STATUS_404 = 13, + HTTP_SERVER_HPACK_STATUS_500 = 14, + HTTP_SERVER_HPACK_ACCEPT_CHARSET = 15, + HTTP_SERVER_HPACK_ACCEPT_ENCODING = 16, + HTTP_SERVER_HPACK_ACCEPT_LANGUAGE = 17, + HTTP_SERVER_HPACK_ACCEPT_RANGES = 18, + HTTP_SERVER_HPACK_ACCEPT = 19, + HTTP_SERVER_HPACK_ACCESS_CONTROL_ALLOW_ORIGIN = 20, + HTTP_SERVER_HPACK_AGE = 21, + HTTP_SERVER_HPACK_ALLOW = 22, + HTTP_SERVER_HPACK_AUTHORIZATION = 23, + HTTP_SERVER_HPACK_CACHE_CONTROL = 24, + HTTP_SERVER_HPACK_CONTENT_DISPOSITION = 25, + HTTP_SERVER_HPACK_CONTENT_ENCODING = 26, + HTTP_SERVER_HPACK_CONTENT_LANGUAGE = 27, + HTTP_SERVER_HPACK_CONTENT_LENGTH = 28, + HTTP_SERVER_HPACK_CONTENT_LOCATION = 29, + HTTP_SERVER_HPACK_CONTENT_RANGE = 30, + HTTP_SERVER_HPACK_CONTENT_TYPE = 31, + HTTP_SERVER_HPACK_COOKIE = 32, + HTTP_SERVER_HPACK_DATE = 33, + HTTP_SERVER_HPACK_ETAG = 34, + HTTP_SERVER_HPACK_EXPECT = 35, + HTTP_SERVER_HPACK_EXPIRES = 36, + HTTP_SERVER_HPACK_FROM = 37, + HTTP_SERVER_HPACK_HOST = 38, + HTTP_SERVER_HPACK_IF_MATCH = 39, + HTTP_SERVER_HPACK_IF_MODIFIED_SINCE = 40, + HTTP_SERVER_HPACK_IF_NONE_MATCH = 41, + HTTP_SERVER_HPACK_IF_RANGE = 42, + HTTP_SERVER_HPACK_IF_UNMODIFIED_SINCE = 43, + HTTP_SERVER_HPACK_LAST_MODIFIED = 44, + HTTP_SERVER_HPACK_LINK = 45, + HTTP_SERVER_HPACK_LOCATION = 46, + HTTP_SERVER_HPACK_MAX_FORWARDS = 47, + HTTP_SERVER_HPACK_PROXY_AUTHENTICATE = 48, + HTTP_SERVER_HPACK_PROXY_AUTHORIZATION = 49, + HTTP_SERVER_HPACK_RANGE = 50, + HTTP_SERVER_HPACK_REFERER = 51, + HTTP_SERVER_HPACK_REFRESH = 52, + HTTP_SERVER_HPACK_RETRY_AFTER = 53, + HTTP_SERVER_HPACK_SERVER = 54, + HTTP_SERVER_HPACK_SET_COOKIE = 55, + HTTP_SERVER_HPACK_STRICT_TRANSPORT_SECURITY = 56, + HTTP_SERVER_HPACK_TRANSFER_ENCODING = 57, + HTTP_SERVER_HPACK_USER_AGENT = 58, + HTTP_SERVER_HPACK_VARY = 59, + HTTP_SERVER_HPACK_VIA = 60, + HTTP_SERVER_HPACK_WWW_AUTHENTICATE = 61, +}; + +/* TODO Kconfig */ +#define HTTP2_HEADER_FIELD_MAX_LEN 256 + +/** HTTP2 header field with decoding buffer. */ +struct http_hpack_header_buf { + /** A pointer to the decoded header field name. */ + const char *name; + + /** A pointer to the decoded header field value. */ + const char *value; + + /** Length of the decoded header field name. */ + size_t name_len; + + /** Length of the decoded header field value. */ + size_t value_len; + + /** Encoding/Decoding buffer. Used with Huffman encoding/decoding. */ + uint8_t buf[CONFIG_HTTP_SERVER_HUFFMAN_DECODE_BUFFER_SIZE]; + + /** Length of the data in the decoding buffer. */ + size_t datalen; +}; + +int http_hpack_huffman_decode(const uint8_t *encoded_buf, size_t encoded_len, + uint8_t *buf, size_t buflen); +int http_hpack_huffman_encode(const uint8_t *str, size_t str_len, + uint8_t *buf, size_t buflen); +int http_hpack_decode_header(const uint8_t *buf, size_t datalen, + struct http_hpack_header_buf *header); +int http_hpack_encode_header(uint8_t *buf, size_t buflen, + struct http_hpack_header_buf *header); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif diff --git a/subsys/net/lib/http/http_hpack.c b/subsys/net/lib/http/http_hpack.c new file mode 100644 index 0000000000000..bfbad52f4232e --- /dev/null +++ b/subsys/net/lib/http/http_hpack.c @@ -0,0 +1,631 @@ +/* + * Copyright (c) 2023, Emna Rekik + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include + +#include +#include +#include + +LOG_MODULE_DECLARE(net_http_server, CONFIG_NET_HTTP_SERVER_LOG_LEVEL); + +static inline bool http_hpack_key_is_static(uint32_t key) +{ + return key > HTTP_SERVER_HPACK_INVALID && key <= HTTP_SERVER_HPACK_WWW_AUTHENTICATE; +} + +static inline bool http_hpack_key_is_dynamic(uint32_t key) +{ + return key > HTTP_SERVER_HPACK_WWW_AUTHENTICATE; +} + +struct hpack_table_entry { + const char *name; + const char *value; +}; + +static const struct hpack_table_entry http_hpack_table_static[] = { + [HTTP_SERVER_HPACK_AUTHORITY] = { ":authority", NULL }, + [HTTP_SERVER_HPACK_METHOD_GET] = { ":method", "GET" }, + [HTTP_SERVER_HPACK_METHOD_POST] = { ":method", "POST" }, + [HTTP_SERVER_HPACK_PATH_ROOT] = { ":path", "/" }, + [HTTP_SERVER_HPACK_PATH_INDEX] = { ":path", "/index.html" }, + [HTTP_SERVER_HPACK_SCHEME_HTTP] = { ":scheme", "http" }, + [HTTP_SERVER_HPACK_SCHEME_HTTPS] = { ":scheme", "https" }, + [HTTP_SERVER_HPACK_STATUS_200] = { ":status", "200" }, + [HTTP_SERVER_HPACK_STATUS_204] = { ":status", "204" }, + [HTTP_SERVER_HPACK_STATUS_206] = { ":status", "206" }, + [HTTP_SERVER_HPACK_STATUS_304] = { ":status", "304" }, + [HTTP_SERVER_HPACK_STATUS_400] = { ":status", "400" }, + [HTTP_SERVER_HPACK_STATUS_404] = { ":status", "404" }, + [HTTP_SERVER_HPACK_STATUS_500] = { ":status", "500" }, + [HTTP_SERVER_HPACK_ACCEPT_CHARSET] = { "accept-charset", NULL }, + [HTTP_SERVER_HPACK_ACCEPT_ENCODING] = { "accept-encoding", "gzip, deflate" }, + [HTTP_SERVER_HPACK_ACCEPT_LANGUAGE] = { "accept-language", NULL }, + [HTTP_SERVER_HPACK_ACCEPT_RANGES] = { "accept-ranges", NULL }, + [HTTP_SERVER_HPACK_ACCEPT] = { "accept", NULL }, + [HTTP_SERVER_HPACK_ACCESS_CONTROL_ALLOW_ORIGIN] = { "access-control-allow-origin", NULL }, + [HTTP_SERVER_HPACK_AGE] = { "age", NULL }, + [HTTP_SERVER_HPACK_ALLOW] = { "allow", NULL }, + [HTTP_SERVER_HPACK_AUTHORIZATION] = { "authorization", NULL }, + [HTTP_SERVER_HPACK_CACHE_CONTROL] = { "cache-control", NULL }, + [HTTP_SERVER_HPACK_CONTENT_DISPOSITION] = { "content-disposition", NULL }, + [HTTP_SERVER_HPACK_CONTENT_ENCODING] = { "content-encoding", NULL }, + [HTTP_SERVER_HPACK_CONTENT_LANGUAGE] = { "content-language", NULL }, + [HTTP_SERVER_HPACK_CONTENT_LENGTH] = { "content-length", NULL }, + [HTTP_SERVER_HPACK_CONTENT_LOCATION] = { "content-location", NULL }, + [HTTP_SERVER_HPACK_CONTENT_RANGE] = { "content-range", NULL }, + [HTTP_SERVER_HPACK_CONTENT_TYPE] = { "content-type", NULL }, + [HTTP_SERVER_HPACK_COOKIE] = { "cookie", NULL }, + [HTTP_SERVER_HPACK_DATE] = { "date", NULL }, + [HTTP_SERVER_HPACK_ETAG] = { "etag", NULL }, + [HTTP_SERVER_HPACK_EXPECT] = { "expect", NULL }, + [HTTP_SERVER_HPACK_EXPIRES] = { "expires", NULL }, + [HTTP_SERVER_HPACK_FROM] = { "from", NULL }, + [HTTP_SERVER_HPACK_HOST] = { "host", NULL }, + [HTTP_SERVER_HPACK_IF_MATCH] = { "if-match", NULL }, + [HTTP_SERVER_HPACK_IF_MODIFIED_SINCE] = { "if-modified-since", NULL }, + [HTTP_SERVER_HPACK_IF_NONE_MATCH] = { "if-none-match", NULL }, + [HTTP_SERVER_HPACK_IF_RANGE] = { "if-range", NULL }, + [HTTP_SERVER_HPACK_IF_UNMODIFIED_SINCE] = { "if-unmodified-since", NULL }, + [HTTP_SERVER_HPACK_LAST_MODIFIED] = { "last-modified", NULL }, + [HTTP_SERVER_HPACK_LINK] = { "link", NULL }, + [HTTP_SERVER_HPACK_LOCATION] = { "location", NULL }, + [HTTP_SERVER_HPACK_MAX_FORWARDS] = { "max-forwards", NULL }, + [HTTP_SERVER_HPACK_PROXY_AUTHENTICATE] = { "proxy-authenticate", NULL }, + [HTTP_SERVER_HPACK_PROXY_AUTHORIZATION] = { "proxy-authorization", NULL }, + [HTTP_SERVER_HPACK_RANGE] = { "range", NULL }, + [HTTP_SERVER_HPACK_REFERER] = { "referer", NULL }, + [HTTP_SERVER_HPACK_REFRESH] = { "refresh", NULL }, + [HTTP_SERVER_HPACK_RETRY_AFTER] = { "retry-after", NULL }, + [HTTP_SERVER_HPACK_SERVER] = { "server", NULL }, + [HTTP_SERVER_HPACK_SET_COOKIE] = { "set-cookie", NULL }, + [HTTP_SERVER_HPACK_STRICT_TRANSPORT_SECURITY] = { "strict-transport-security", NULL }, + [HTTP_SERVER_HPACK_TRANSFER_ENCODING] = { "transfer-encoding", NULL }, + [HTTP_SERVER_HPACK_USER_AGENT] = { "user-agent", NULL }, + [HTTP_SERVER_HPACK_VARY] = { "vary", NULL }, + [HTTP_SERVER_HPACK_VIA] = { "via", NULL }, + [HTTP_SERVER_HPACK_WWW_AUTHENTICATE] = { "www-authenticate", NULL }, +}; + +const struct hpack_table_entry *http_hpack_table_get(uint32_t key) +{ + if (!http_hpack_key_is_static(key)) { + return NULL; + } + + return &http_hpack_table_static[key]; +} + +static int http_hpack_find_index(struct http_hpack_header_buf *header, + bool *name_only) +{ + const struct hpack_table_entry *entry; + int candidate = -1; + + for (int i = HTTP_SERVER_HPACK_AUTHORITY; + i <= HTTP_SERVER_HPACK_WWW_AUTHENTICATE; i++) { + entry = &http_hpack_table_static[i]; + + if (entry->name != NULL && + strlen(entry->name) == header->name_len && + memcmp(entry->name, header->name, header->name_len) == 0) { + if (entry->value != NULL && + strlen(entry->value) == header->value_len && + memcmp(entry->value, header->value, header->value_len) == 0) { + /* Got exact match. */ + *name_only = false; + return i; + } + + if (candidate < 0) { + candidate = i; + } + } + } + + if (candidate > 0) { + /* Matched name only. */ + *name_only = true; + return candidate; + } + + return -ENOENT; +} + +#define HPACK_INTEGER_CONTINUATION_FLAG 0x80 +#define HPACK_STRING_HUFFMAN_FLAG 0x80 +#define HPACK_STRING_PREFIX_LEN 7 + +#define HPACK_PREFIX_INDEXED_MASK 0x80 +#define HPACK_PREFIX_INDEXED 0x80 +#define HPACK_PREFIX_LEN_INDEXED 7 + +#define HPACK_PREFIX_LITERAL_INDEXING_MASK 0xC0 +#define HPACK_PREFIX_LITERAL_INDEXING 0x40 +#define HPACK_PREFIX_LEN_LITERAL_INDEXING 6 + +#define HPACK_PREFIX_LITERAL_NO_INDEXING_MASK 0xF0 +#define HPACK_PREFIX_LITERAL_NO_INDEXING 0x00 +#define HPACK_PREFIX_LEN_LITERAL_NO_INDEXING 4 + +#define HPACK_PREFIX_LITERAL_NEVER_INDEXED_MASK 0xF0 +#define HPACK_PREFIX_LITERAL_NEVER_INDEXED 0x10 +#define HPACK_PREFIX_LEN_LITERAL_NEVER_INDEXED 4 + +#define HPACK_PREFIX_DYNAMIC_TABLE_SIZE_MASK 0xE0 +#define HPACK_PREFIX_DYNAMIC_TABLE_SIZE_UPDATE 0x20 +#define HPACK_PREFIX_LEN_DYNAMIC_TABLE_SIZE_UPDATE 5 + +static int hpack_integer_decode(const uint8_t *buf, size_t datalen, + uint8_t n, uint32_t *value) +{ + int len = 0; + uint8_t m = 0; + uint8_t value_mask = (1 << n) - 1; + + NET_ASSERT(n < 8); + + if (datalen == 0) { + return -EAGAIN; + } + + /* Based on RFC7541, ch 5.1. */ + len++; + *value = *buf & value_mask; + if (*value < value_mask) { + return len; + } + + do { + buf++; + len++; + + if (--datalen == 0) { + return -EAGAIN; + } + + if (m > sizeof(uint32_t) * 8) { + /* Can't handle integer that large. */ + return -EBADMSG; + } + + *value += (*buf & ~HPACK_INTEGER_CONTINUATION_FLAG) * (1 << m); + m += 7; + + } while (*buf & HPACK_INTEGER_CONTINUATION_FLAG); + + return len; +} + +enum hpack_string_type { + HPACK_HEADER_NAME, + HPACK_HEADER_VALUE, +}; + +static int hpack_huffman_decode(const uint8_t *encoded_buf, size_t encoded_len, + enum hpack_string_type type, + struct http_hpack_header_buf *header) +{ + uint8_t *buf = header->buf + header->datalen; + size_t buflen = sizeof(header->buf) - header->datalen; + int ret; + + NET_ASSERT(type == HPACK_HEADER_NAME || type == HPACK_HEADER_VALUE); + + ret = http_hpack_huffman_decode(encoded_buf, encoded_len, buf, buflen); + if (ret < 0) { + return ret; + } + + if (type == HPACK_HEADER_NAME) { + header->name = buf; + header->name_len = ret; + } else if (type == HPACK_HEADER_VALUE) { + header->value = buf; + header->value_len = ret; + } + + header->datalen += ret; + + return 0; +} + +static int hpack_string_decode(const uint8_t *buf, size_t datalen, + enum hpack_string_type type, + struct http_hpack_header_buf *header) +{ + uint32_t str_len; + bool huffman; + int len = 0; + int ret; + + NET_ASSERT(type == HPACK_HEADER_NAME || type == HPACK_HEADER_VALUE); + + if (datalen == 0) { + return -EAGAIN; + } + + huffman = *buf & HPACK_STRING_HUFFMAN_FLAG; + + ret = hpack_integer_decode(buf, datalen, HPACK_STRING_PREFIX_LEN, + &str_len); + if (ret < 0) { + return ret; + } + + len += ret; + datalen -= ret; + buf += ret; + + if (str_len > datalen) { + return -EAGAIN; + } + + if (huffman) { + ret = hpack_huffman_decode(buf, str_len, type, header); + if (ret < 0) { + return ret; + } + } else { + if (type == HPACK_HEADER_NAME) { + header->name = buf; + header->name_len = str_len; + } else if (type == HPACK_HEADER_VALUE) { + header->value = buf; + header->value_len = str_len; + } + } + + len += str_len; + + return len; +} + +static int hpack_handle_indexed(const uint8_t *buf, size_t datalen, + struct http_hpack_header_buf *header) +{ + const struct hpack_table_entry *entry; + uint32_t index; + int ret; + + ret = hpack_integer_decode(buf, datalen, HPACK_PREFIX_LEN_INDEXED, + &index); + if (ret < 0) { + return ret; + } + + if (index == 0) { + return -EBADMSG; + } + + entry = http_hpack_table_get(index); + if (entry == NULL) { + return -EBADMSG; + } + + if (entry->name == NULL || entry->value == NULL) { + return -EBADMSG; + } + + header->name = entry->name; + header->name_len = strlen(entry->name); + header->value = entry->value; + header->value_len = strlen(entry->value); + + return ret; +} + +static int hpack_handle_literal(const uint8_t *buf, size_t datalen, + struct http_hpack_header_buf *header, + uint8_t prefix_len) +{ + uint32_t index; + int ret, len; + + header->datalen = 0; + + ret = hpack_integer_decode(buf, datalen, prefix_len, &index); + if (ret < 0) { + return ret; + } + + len = ret; + buf += ret; + datalen -= ret; + + if (index == 0) { + /* Literal name. */ + ret = hpack_string_decode(buf, datalen, HPACK_HEADER_NAME, + header); + if (ret < 0) { + return ret; + } + + len += ret; + buf += ret; + datalen -= ret; + } else { + /* Indexed name. */ + const struct hpack_table_entry *entry; + + entry = http_hpack_table_get(index); + if (entry == NULL) { + return -EBADMSG; + } + + if (entry->name == NULL) { + return -EBADMSG; + } + + header->name = entry->name; + header->name_len = strlen(entry->name); + } + + ret = hpack_string_decode(buf, datalen, HPACK_HEADER_VALUE, header); + if (ret < 0) { + return ret; + } + + len += ret; + + return len; +} + +static int hpack_handle_literal_index(const uint8_t *buf, size_t datalen, + struct http_hpack_header_buf *header) +{ + /* TODO Add dynamic table support, if needed. */ + + return hpack_handle_literal(buf, datalen, header, + HPACK_PREFIX_LEN_LITERAL_INDEXING); +} + +static int hpack_handle_literal_no_index(const uint8_t *buf, size_t datalen, + struct http_hpack_header_buf *header) +{ + return hpack_handle_literal(buf, datalen, header, + HPACK_PREFIX_LEN_LITERAL_NO_INDEXING); +} + +static int hpack_handle_dynamic_size_update(const uint8_t *buf, size_t datalen) +{ + uint32_t max_size; + int ret; + + ret = hpack_integer_decode( + buf, datalen, HPACK_PREFIX_LEN_DYNAMIC_TABLE_SIZE_UPDATE, &max_size); + if (ret < 0) { + return ret; + } + + /* TODO Add dynamic table support, if needed. */ + + return ret; +} + +int http_hpack_decode_header(const uint8_t *buf, size_t datalen, + struct http_hpack_header_buf *header) +{ + uint8_t prefix = *buf; + int ret; + + if (buf == NULL || header == NULL) { + return -EINVAL; + } + + if (datalen == 0) { + return -EAGAIN; + } + + if ((prefix & HPACK_PREFIX_INDEXED_MASK) == HPACK_PREFIX_INDEXED) { + ret = hpack_handle_indexed(buf, datalen, header); + } else if ((prefix & HPACK_PREFIX_LITERAL_INDEXING_MASK) == + HPACK_PREFIX_LITERAL_INDEXING) { + ret = hpack_handle_literal_index(buf, datalen, header); + } else if (((prefix & HPACK_PREFIX_LITERAL_NO_INDEXING_MASK) == + HPACK_PREFIX_LITERAL_NO_INDEXING) || + ((prefix & HPACK_PREFIX_LITERAL_NEVER_INDEXED_MASK) == + HPACK_PREFIX_LITERAL_NEVER_INDEXED)) { + ret = hpack_handle_literal_no_index(buf, datalen, header); + } else if ((prefix & HPACK_PREFIX_DYNAMIC_TABLE_SIZE_MASK) == + HPACK_PREFIX_DYNAMIC_TABLE_SIZE_UPDATE) { + ret = hpack_handle_dynamic_size_update(buf, datalen); + } else { + ret = -EINVAL; + } + + return ret; +} + +static int hpack_integer_encode(uint8_t *buf, size_t buflen, int value, + uint8_t prefix, uint8_t n) +{ + uint8_t limit = (1 << n) - 1; + int len = 0; + + if (buflen == 0) { + return -ENOBUFS; + } + + /* Based on RFC7541, ch 5.1. */ + if (value < limit) { + *buf = prefix | (uint8_t)value; + + return 1; + } + + *buf++ = prefix | limit; + len++; + value -= limit; + + while (value >= 128) { + if (len >= buflen) { + return -ENOBUFS; + } + + *buf = (uint8_t)((value % 128) + 128); + len++; + value /= 128; + } + + if (len >= buflen) { + return -ENOBUFS; + } + + *buf = (uint8_t)value; + len++; + + return len; +} + +static int hpack_string_encode(uint8_t *buf, size_t buflen, + enum hpack_string_type type, + struct http_hpack_header_buf *header) +{ + int ret, len = 0; + const char *str; + size_t str_len; + uint8_t prefix = 0; + + if (type == HPACK_HEADER_NAME) { + str = header->name; + str_len = header->name_len; + } else { + str = header->value; + str_len = header->value_len; + } + + /* Try to encode string into intermediate buffer. */ + ret = http_hpack_huffman_encode(str, str_len, header->buf, + sizeof(header->buf)); + if (ret > 0 && ret < str_len) { + /* Use Huffman encoded string only if smaller than the original. */ + str = header->buf; + str_len = ret; + prefix = HPACK_STRING_HUFFMAN_FLAG; + } + + /* Encode string length. */ + ret = hpack_integer_encode(buf, buflen, str_len, prefix, + HPACK_STRING_PREFIX_LEN); + if (ret < 0) { + return ret; + } + + buf += ret; + buflen -= ret; + len += ret; + + /* Copy string. */ + if (str_len > buflen) { + return -ENOBUFS; + } + + memcpy(buf, str, str_len); + len += str_len; + + return len; +} + +static int hpack_encode_literal(uint8_t *buf, size_t buflen, + struct http_hpack_header_buf *header) +{ + int ret, len = 0; + + ret = hpack_integer_encode(buf, buflen, 0, + HPACK_PREFIX_LITERAL_NEVER_INDEXED, + HPACK_PREFIX_LEN_LITERAL_NEVER_INDEXED); + if (ret < 0) { + return ret; + } + + buf += ret; + buflen -= ret; + len += ret; + + ret = hpack_string_encode(buf, buflen, HPACK_HEADER_NAME, header); + if (ret < 0) { + return ret; + } + + buf += ret; + buflen -= ret; + len += ret; + + ret = hpack_string_encode(buf, buflen, HPACK_HEADER_VALUE, header); + if (ret < 0) { + return ret; + } + + len += ret; + + return len; +} + +static int hpack_encode_literal_value(uint8_t *buf, size_t buflen, int index, + struct http_hpack_header_buf *header) +{ + int ret, len = 0; + + ret = hpack_integer_encode(buf, buflen, index, + HPACK_PREFIX_LITERAL_NEVER_INDEXED, + HPACK_PREFIX_LEN_LITERAL_NEVER_INDEXED); + if (ret < 0) { + return ret; + } + + buf += ret; + buflen -= ret; + len += ret; + + ret = hpack_string_encode(buf, buflen, HPACK_HEADER_VALUE, header); + if (ret < 0) { + return ret; + } + + len += ret; + + return len; +} + +static int hpack_encode_indexed(uint8_t *buf, size_t buflen, int index) +{ + return hpack_integer_encode(buf, buflen, index, HPACK_PREFIX_INDEXED, + HPACK_PREFIX_LEN_INDEXED); +} + +int http_hpack_encode_header(uint8_t *buf, size_t buflen, + struct http_hpack_header_buf *header) +{ + int ret, len = 0; + bool name_only; + + if (buf == NULL || header == NULL || + header->name == NULL || header->name_len == 0 || + header->value == NULL || header->value_len == 0) { + return -EINVAL; + } + + if (buflen == 0) { + return -ENOBUFS; + } + + ret = http_hpack_find_index(header, &name_only); + if (ret < 0) { + /* All literal */ + len = hpack_encode_literal(buf, buflen, header); + } else if (name_only) { + /* Literal value */ + len = hpack_encode_literal_value(buf, buflen, ret, header); + } else { + /* Indexed */ + len = hpack_encode_indexed(buf, buflen, ret); + } + + return len; +} diff --git a/subsys/net/lib/http/http_huffman.c b/subsys/net/lib/http/http_huffman.c new file mode 100644 index 0000000000000..6465eee2bc0da --- /dev/null +++ b/subsys/net/lib/http/http_huffman.c @@ -0,0 +1,480 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include + +#include +#include + +LOG_MODULE_DECLARE(net_http_server, CONFIG_NET_HTTP_SERVER_LOG_LEVEL); + +struct decode_elem { + uint8_t bitlen; + uint8_t symbol; + uint8_t code[4]; +}; + +static const struct decode_elem decode_table[] = { + { 5, 48, { 0b00000000, 0b00000000, 0b00000000, 0b00000000 } }, + { 5, 49, { 0b00001000, 0b00000000, 0b00000000, 0b00000000 } }, + { 5, 50, { 0b00010000, 0b00000000, 0b00000000, 0b00000000 } }, + { 5, 97, { 0b00011000, 0b00000000, 0b00000000, 0b00000000 } }, + { 5, 99, { 0b00100000, 0b00000000, 0b00000000, 0b00000000 } }, + { 5, 101, { 0b00101000, 0b00000000, 0b00000000, 0b00000000 } }, + { 5, 105, { 0b00110000, 0b00000000, 0b00000000, 0b00000000 } }, + { 5, 111, { 0b00111000, 0b00000000, 0b00000000, 0b00000000 } }, + { 5, 115, { 0b01000000, 0b00000000, 0b00000000, 0b00000000 } }, + { 5, 116, { 0b01001000, 0b00000000, 0b00000000, 0b00000000 } }, + { 6, 32, { 0b01010000, 0b00000000, 0b00000000, 0b00000000 } }, + { 6, 37, { 0b01010100, 0b00000000, 0b00000000, 0b00000000 } }, + { 6, 45, { 0b01011000, 0b00000000, 0b00000000, 0b00000000 } }, + { 6, 46, { 0b01011100, 0b00000000, 0b00000000, 0b00000000 } }, + { 6, 47, { 0b01100000, 0b00000000, 0b00000000, 0b00000000 } }, + { 6, 51, { 0b01100100, 0b00000000, 0b00000000, 0b00000000 } }, + { 6, 52, { 0b01101000, 0b00000000, 0b00000000, 0b00000000 } }, + { 6, 53, { 0b01101100, 0b00000000, 0b00000000, 0b00000000 } }, + { 6, 54, { 0b01110000, 0b00000000, 0b00000000, 0b00000000 } }, + { 6, 55, { 0b01110100, 0b00000000, 0b00000000, 0b00000000 } }, + { 6, 56, { 0b01111000, 0b00000000, 0b00000000, 0b00000000 } }, + { 6, 57, { 0b01111100, 0b00000000, 0b00000000, 0b00000000 } }, + { 6, 61, { 0b10000000, 0b00000000, 0b00000000, 0b00000000 } }, + { 6, 65, { 0b10000100, 0b00000000, 0b00000000, 0b00000000 } }, + { 6, 95, { 0b10001000, 0b00000000, 0b00000000, 0b00000000 } }, + { 6, 98, { 0b10001100, 0b00000000, 0b00000000, 0b00000000 } }, + { 6, 100, { 0b10010000, 0b00000000, 0b00000000, 0b00000000 } }, + { 6, 102, { 0b10010100, 0b00000000, 0b00000000, 0b00000000 } }, + { 6, 103, { 0b10011000, 0b00000000, 0b00000000, 0b00000000 } }, + { 6, 104, { 0b10011100, 0b00000000, 0b00000000, 0b00000000 } }, + { 6, 108, { 0b10100000, 0b00000000, 0b00000000, 0b00000000 } }, + { 6, 109, { 0b10100100, 0b00000000, 0b00000000, 0b00000000 } }, + { 6, 110, { 0b10101000, 0b00000000, 0b00000000, 0b00000000 } }, + { 6, 112, { 0b10101100, 0b00000000, 0b00000000, 0b00000000 } }, + { 6, 114, { 0b10110000, 0b00000000, 0b00000000, 0b00000000 } }, + { 6, 117, { 0b10110100, 0b00000000, 0b00000000, 0b00000000 } }, + { 7, 58, { 0b10111000, 0b00000000, 0b00000000, 0b00000000 } }, + { 7, 66, { 0b10111010, 0b00000000, 0b00000000, 0b00000000 } }, + { 7, 67, { 0b10111100, 0b00000000, 0b00000000, 0b00000000 } }, + { 7, 68, { 0b10111110, 0b00000000, 0b00000000, 0b00000000 } }, + { 7, 69, { 0b11000000, 0b00000000, 0b00000000, 0b00000000 } }, + { 7, 70, { 0b11000010, 0b00000000, 0b00000000, 0b00000000 } }, + { 7, 71, { 0b11000100, 0b00000000, 0b00000000, 0b00000000 } }, + { 7, 72, { 0b11000110, 0b00000000, 0b00000000, 0b00000000 } }, + { 7, 73, { 0b11001000, 0b00000000, 0b00000000, 0b00000000 } }, + { 7, 74, { 0b11001010, 0b00000000, 0b00000000, 0b00000000 } }, + { 7, 75, { 0b11001100, 0b00000000, 0b00000000, 0b00000000 } }, + { 7, 76, { 0b11001110, 0b00000000, 0b00000000, 0b00000000 } }, + { 7, 77, { 0b11010000, 0b00000000, 0b00000000, 0b00000000 } }, + { 7, 78, { 0b11010010, 0b00000000, 0b00000000, 0b00000000 } }, + { 7, 79, { 0b11010100, 0b00000000, 0b00000000, 0b00000000 } }, + { 7, 80, { 0b11010110, 0b00000000, 0b00000000, 0b00000000 } }, + { 7, 81, { 0b11011000, 0b00000000, 0b00000000, 0b00000000 } }, + { 7, 82, { 0b11011010, 0b00000000, 0b00000000, 0b00000000 } }, + { 7, 83, { 0b11011100, 0b00000000, 0b00000000, 0b00000000 } }, + { 7, 84, { 0b11011110, 0b00000000, 0b00000000, 0b00000000 } }, + { 7, 85, { 0b11100000, 0b00000000, 0b00000000, 0b00000000 } }, + { 7, 86, { 0b11100010, 0b00000000, 0b00000000, 0b00000000 } }, + { 7, 87, { 0b11100100, 0b00000000, 0b00000000, 0b00000000 } }, + { 7, 89, { 0b11100110, 0b00000000, 0b00000000, 0b00000000 } }, + { 7, 106, { 0b11101000, 0b00000000, 0b00000000, 0b00000000 } }, + { 7, 107, { 0b11101010, 0b00000000, 0b00000000, 0b00000000 } }, + { 7, 113, { 0b11101100, 0b00000000, 0b00000000, 0b00000000 } }, + { 7, 118, { 0b11101110, 0b00000000, 0b00000000, 0b00000000 } }, + { 7, 119, { 0b11110000, 0b00000000, 0b00000000, 0b00000000 } }, + { 7, 120, { 0b11110010, 0b00000000, 0b00000000, 0b00000000 } }, + { 7, 121, { 0b11110100, 0b00000000, 0b00000000, 0b00000000 } }, + { 7, 122, { 0b11110110, 0b00000000, 0b00000000, 0b00000000 } }, + { 8, 38, { 0b11111000, 0b00000000, 0b00000000, 0b00000000 } }, + { 8, 42, { 0b11111001, 0b00000000, 0b00000000, 0b00000000 } }, + { 8, 44, { 0b11111010, 0b00000000, 0b00000000, 0b00000000 } }, + { 8, 59, { 0b11111011, 0b00000000, 0b00000000, 0b00000000 } }, + { 8, 88, { 0b11111100, 0b00000000, 0b00000000, 0b00000000 } }, + { 8, 90, { 0b11111101, 0b00000000, 0b00000000, 0b00000000 } }, + { 10, 33, { 0b11111110, 0b00000000, 0b00000000, 0b00000000 } }, + { 10, 34, { 0b11111110, 0b01000000, 0b00000000, 0b00000000 } }, + { 10, 40, { 0b11111110, 0b10000000, 0b00000000, 0b00000000 } }, + { 10, 41, { 0b11111110, 0b11000000, 0b00000000, 0b00000000 } }, + { 10, 63, { 0b11111111, 0b00000000, 0b00000000, 0b00000000 } }, + { 11, 39, { 0b11111111, 0b01000000, 0b00000000, 0b00000000 } }, + { 11, 43, { 0b11111111, 0b01100000, 0b00000000, 0b00000000 } }, + { 11, 124, { 0b11111111, 0b10000000, 0b00000000, 0b00000000 } }, + { 12, 35, { 0b11111111, 0b10100000, 0b00000000, 0b00000000 } }, + { 12, 62, { 0b11111111, 0b10110000, 0b00000000, 0b00000000 } }, + { 13, 0, { 0b11111111, 0b11000000, 0b00000000, 0b00000000 } }, + { 13, 36, { 0b11111111, 0b11001000, 0b00000000, 0b00000000 } }, + { 13, 64, { 0b11111111, 0b11010000, 0b00000000, 0b00000000 } }, + { 13, 91, { 0b11111111, 0b11011000, 0b00000000, 0b00000000 } }, + { 13, 93, { 0b11111111, 0b11100000, 0b00000000, 0b00000000 } }, + { 13, 126, { 0b11111111, 0b11101000, 0b00000000, 0b00000000 } }, + { 14, 94, { 0b11111111, 0b11110000, 0b00000000, 0b00000000 } }, + { 14, 125, { 0b11111111, 0b11110100, 0b00000000, 0b00000000 } }, + { 15, 60, { 0b11111111, 0b11111000, 0b00000000, 0b00000000 } }, + { 15, 96, { 0b11111111, 0b11111010, 0b00000000, 0b00000000 } }, + { 15, 123, { 0b11111111, 0b11111100, 0b00000000, 0b00000000 } }, + { 19, 92, { 0b11111111, 0b11111110, 0b00000000, 0b00000000 } }, + { 19, 195, { 0b11111111, 0b11111110, 0b00100000, 0b00000000 } }, + { 19, 208, { 0b11111111, 0b11111110, 0b01000000, 0b00000000 } }, + { 20, 128, { 0b11111111, 0b11111110, 0b01100000, 0b00000000 } }, + { 20, 130, { 0b11111111, 0b11111110, 0b01110000, 0b00000000 } }, + { 20, 131, { 0b11111111, 0b11111110, 0b10000000, 0b00000000 } }, + { 20, 162, { 0b11111111, 0b11111110, 0b10010000, 0b00000000 } }, + { 20, 184, { 0b11111111, 0b11111110, 0b10100000, 0b00000000 } }, + { 20, 194, { 0b11111111, 0b11111110, 0b10110000, 0b00000000 } }, + { 20, 224, { 0b11111111, 0b11111110, 0b11000000, 0b00000000 } }, + { 20, 226, { 0b11111111, 0b11111110, 0b11010000, 0b00000000 } }, + { 21, 153, { 0b11111111, 0b11111110, 0b11100000, 0b00000000 } }, + { 21, 161, { 0b11111111, 0b11111110, 0b11101000, 0b00000000 } }, + { 21, 167, { 0b11111111, 0b11111110, 0b11110000, 0b00000000 } }, + { 21, 172, { 0b11111111, 0b11111110, 0b11111000, 0b00000000 } }, + { 21, 176, { 0b11111111, 0b11111111, 0b00000000, 0b00000000 } }, + { 21, 177, { 0b11111111, 0b11111111, 0b00001000, 0b00000000 } }, + { 21, 179, { 0b11111111, 0b11111111, 0b00010000, 0b00000000 } }, + { 21, 209, { 0b11111111, 0b11111111, 0b00011000, 0b00000000 } }, + { 21, 216, { 0b11111111, 0b11111111, 0b00100000, 0b00000000 } }, + { 21, 217, { 0b11111111, 0b11111111, 0b00101000, 0b00000000 } }, + { 21, 227, { 0b11111111, 0b11111111, 0b00110000, 0b00000000 } }, + { 21, 229, { 0b11111111, 0b11111111, 0b00111000, 0b00000000 } }, + { 21, 230, { 0b11111111, 0b11111111, 0b01000000, 0b00000000 } }, + { 22, 129, { 0b11111111, 0b11111111, 0b01001000, 0b00000000 } }, + { 22, 132, { 0b11111111, 0b11111111, 0b01001100, 0b00000000 } }, + { 22, 133, { 0b11111111, 0b11111111, 0b01010000, 0b00000000 } }, + { 22, 134, { 0b11111111, 0b11111111, 0b01010100, 0b00000000 } }, + { 22, 136, { 0b11111111, 0b11111111, 0b01011000, 0b00000000 } }, + { 22, 146, { 0b11111111, 0b11111111, 0b01011100, 0b00000000 } }, + { 22, 154, { 0b11111111, 0b11111111, 0b01100000, 0b00000000 } }, + { 22, 156, { 0b11111111, 0b11111111, 0b01100100, 0b00000000 } }, + { 22, 160, { 0b11111111, 0b11111111, 0b01101000, 0b00000000 } }, + { 22, 163, { 0b11111111, 0b11111111, 0b01101100, 0b00000000 } }, + { 22, 164, { 0b11111111, 0b11111111, 0b01110000, 0b00000000 } }, + { 22, 169, { 0b11111111, 0b11111111, 0b01110100, 0b00000000 } }, + { 22, 170, { 0b11111111, 0b11111111, 0b01111000, 0b00000000 } }, + { 22, 173, { 0b11111111, 0b11111111, 0b01111100, 0b00000000 } }, + { 22, 178, { 0b11111111, 0b11111111, 0b10000000, 0b00000000 } }, + { 22, 181, { 0b11111111, 0b11111111, 0b10000100, 0b00000000 } }, + { 22, 185, { 0b11111111, 0b11111111, 0b10001000, 0b00000000 } }, + { 22, 186, { 0b11111111, 0b11111111, 0b10001100, 0b00000000 } }, + { 22, 187, { 0b11111111, 0b11111111, 0b10010000, 0b00000000 } }, + { 22, 189, { 0b11111111, 0b11111111, 0b10010100, 0b00000000 } }, + { 22, 190, { 0b11111111, 0b11111111, 0b10011000, 0b00000000 } }, + { 22, 196, { 0b11111111, 0b11111111, 0b10011100, 0b00000000 } }, + { 22, 198, { 0b11111111, 0b11111111, 0b10100000, 0b00000000 } }, + { 22, 228, { 0b11111111, 0b11111111, 0b10100100, 0b00000000 } }, + { 22, 232, { 0b11111111, 0b11111111, 0b10101000, 0b00000000 } }, + { 22, 233, { 0b11111111, 0b11111111, 0b10101100, 0b00000000 } }, + { 23, 1, { 0b11111111, 0b11111111, 0b10110000, 0b00000000 } }, + { 23, 135, { 0b11111111, 0b11111111, 0b10110010, 0b00000000 } }, + { 23, 137, { 0b11111111, 0b11111111, 0b10110100, 0b00000000 } }, + { 23, 138, { 0b11111111, 0b11111111, 0b10110110, 0b00000000 } }, + { 23, 139, { 0b11111111, 0b11111111, 0b10111000, 0b00000000 } }, + { 23, 140, { 0b11111111, 0b11111111, 0b10111010, 0b00000000 } }, + { 23, 141, { 0b11111111, 0b11111111, 0b10111100, 0b00000000 } }, + { 23, 143, { 0b11111111, 0b11111111, 0b10111110, 0b00000000 } }, + { 23, 147, { 0b11111111, 0b11111111, 0b11000000, 0b00000000 } }, + { 23, 149, { 0b11111111, 0b11111111, 0b11000010, 0b00000000 } }, + { 23, 150, { 0b11111111, 0b11111111, 0b11000100, 0b00000000 } }, + { 23, 151, { 0b11111111, 0b11111111, 0b11000110, 0b00000000 } }, + { 23, 152, { 0b11111111, 0b11111111, 0b11001000, 0b00000000 } }, + { 23, 155, { 0b11111111, 0b11111111, 0b11001010, 0b00000000 } }, + { 23, 157, { 0b11111111, 0b11111111, 0b11001100, 0b00000000 } }, + { 23, 158, { 0b11111111, 0b11111111, 0b11001110, 0b00000000 } }, + { 23, 165, { 0b11111111, 0b11111111, 0b11010000, 0b00000000 } }, + { 23, 166, { 0b11111111, 0b11111111, 0b11010010, 0b00000000 } }, + { 23, 168, { 0b11111111, 0b11111111, 0b11010100, 0b00000000 } }, + { 23, 174, { 0b11111111, 0b11111111, 0b11010110, 0b00000000 } }, + { 23, 175, { 0b11111111, 0b11111111, 0b11011000, 0b00000000 } }, + { 23, 180, { 0b11111111, 0b11111111, 0b11011010, 0b00000000 } }, + { 23, 182, { 0b11111111, 0b11111111, 0b11011100, 0b00000000 } }, + { 23, 183, { 0b11111111, 0b11111111, 0b11011110, 0b00000000 } }, + { 23, 188, { 0b11111111, 0b11111111, 0b11100000, 0b00000000 } }, + { 23, 191, { 0b11111111, 0b11111111, 0b11100010, 0b00000000 } }, + { 23, 197, { 0b11111111, 0b11111111, 0b11100100, 0b00000000 } }, + { 23, 231, { 0b11111111, 0b11111111, 0b11100110, 0b00000000 } }, + { 23, 239, { 0b11111111, 0b11111111, 0b11101000, 0b00000000 } }, + { 24, 9, { 0b11111111, 0b11111111, 0b11101010, 0b00000000 } }, + { 24, 142, { 0b11111111, 0b11111111, 0b11101011, 0b00000000 } }, + { 24, 144, { 0b11111111, 0b11111111, 0b11101100, 0b00000000 } }, + { 24, 145, { 0b11111111, 0b11111111, 0b11101101, 0b00000000 } }, + { 24, 148, { 0b11111111, 0b11111111, 0b11101110, 0b00000000 } }, + { 24, 159, { 0b11111111, 0b11111111, 0b11101111, 0b00000000 } }, + { 24, 171, { 0b11111111, 0b11111111, 0b11110000, 0b00000000 } }, + { 24, 206, { 0b11111111, 0b11111111, 0b11110001, 0b00000000 } }, + { 24, 215, { 0b11111111, 0b11111111, 0b11110010, 0b00000000 } }, + { 24, 225, { 0b11111111, 0b11111111, 0b11110011, 0b00000000 } }, + { 24, 236, { 0b11111111, 0b11111111, 0b11110100, 0b00000000 } }, + { 24, 237, { 0b11111111, 0b11111111, 0b11110101, 0b00000000 } }, + { 25, 199, { 0b11111111, 0b11111111, 0b11110110, 0b00000000 } }, + { 25, 207, { 0b11111111, 0b11111111, 0b11110110, 0b10000000 } }, + { 25, 234, { 0b11111111, 0b11111111, 0b11110111, 0b00000000 } }, + { 25, 235, { 0b11111111, 0b11111111, 0b11110111, 0b10000000 } }, + { 26, 192, { 0b11111111, 0b11111111, 0b11111000, 0b00000000 } }, + { 26, 193, { 0b11111111, 0b11111111, 0b11111000, 0b01000000 } }, + { 26, 200, { 0b11111111, 0b11111111, 0b11111000, 0b10000000 } }, + { 26, 201, { 0b11111111, 0b11111111, 0b11111000, 0b11000000 } }, + { 26, 202, { 0b11111111, 0b11111111, 0b11111001, 0b00000000 } }, + { 26, 205, { 0b11111111, 0b11111111, 0b11111001, 0b01000000 } }, + { 26, 210, { 0b11111111, 0b11111111, 0b11111001, 0b10000000 } }, + { 26, 213, { 0b11111111, 0b11111111, 0b11111001, 0b11000000 } }, + { 26, 218, { 0b11111111, 0b11111111, 0b11111010, 0b00000000 } }, + { 26, 219, { 0b11111111, 0b11111111, 0b11111010, 0b01000000 } }, + { 26, 238, { 0b11111111, 0b11111111, 0b11111010, 0b10000000 } }, + { 26, 240, { 0b11111111, 0b11111111, 0b11111010, 0b11000000 } }, + { 26, 242, { 0b11111111, 0b11111111, 0b11111011, 0b00000000 } }, + { 26, 243, { 0b11111111, 0b11111111, 0b11111011, 0b01000000 } }, + { 26, 255, { 0b11111111, 0b11111111, 0b11111011, 0b10000000 } }, + { 27, 203, { 0b11111111, 0b11111111, 0b11111011, 0b11000000 } }, + { 27, 204, { 0b11111111, 0b11111111, 0b11111011, 0b11100000 } }, + { 27, 211, { 0b11111111, 0b11111111, 0b11111100, 0b00000000 } }, + { 27, 212, { 0b11111111, 0b11111111, 0b11111100, 0b00100000 } }, + { 27, 214, { 0b11111111, 0b11111111, 0b11111100, 0b01000000 } }, + { 27, 221, { 0b11111111, 0b11111111, 0b11111100, 0b01100000 } }, + { 27, 222, { 0b11111111, 0b11111111, 0b11111100, 0b10000000 } }, + { 27, 223, { 0b11111111, 0b11111111, 0b11111100, 0b10100000 } }, + { 27, 241, { 0b11111111, 0b11111111, 0b11111100, 0b11000000 } }, + { 27, 244, { 0b11111111, 0b11111111, 0b11111100, 0b11100000 } }, + { 27, 245, { 0b11111111, 0b11111111, 0b11111101, 0b00000000 } }, + { 27, 246, { 0b11111111, 0b11111111, 0b11111101, 0b00100000 } }, + { 27, 247, { 0b11111111, 0b11111111, 0b11111101, 0b01000000 } }, + { 27, 248, { 0b11111111, 0b11111111, 0b11111101, 0b01100000 } }, + { 27, 250, { 0b11111111, 0b11111111, 0b11111101, 0b10000000 } }, + { 27, 251, { 0b11111111, 0b11111111, 0b11111101, 0b10100000 } }, + { 27, 252, { 0b11111111, 0b11111111, 0b11111101, 0b11000000 } }, + { 27, 253, { 0b11111111, 0b11111111, 0b11111101, 0b11100000 } }, + { 27, 254, { 0b11111111, 0b11111111, 0b11111110, 0b00000000 } }, + { 28, 2, { 0b11111111, 0b11111111, 0b11111110, 0b00100000 } }, + { 28, 3, { 0b11111111, 0b11111111, 0b11111110, 0b00110000 } }, + { 28, 4, { 0b11111111, 0b11111111, 0b11111110, 0b01000000 } }, + { 28, 5, { 0b11111111, 0b11111111, 0b11111110, 0b01010000 } }, + { 28, 6, { 0b11111111, 0b11111111, 0b11111110, 0b01100000 } }, + { 28, 7, { 0b11111111, 0b11111111, 0b11111110, 0b01110000 } }, + { 28, 8, { 0b11111111, 0b11111111, 0b11111110, 0b10000000 } }, + { 28, 11, { 0b11111111, 0b11111111, 0b11111110, 0b10010000 } }, + { 28, 12, { 0b11111111, 0b11111111, 0b11111110, 0b10100000 } }, + { 28, 14, { 0b11111111, 0b11111111, 0b11111110, 0b10110000 } }, + { 28, 15, { 0b11111111, 0b11111111, 0b11111110, 0b11000000 } }, + { 28, 16, { 0b11111111, 0b11111111, 0b11111110, 0b11010000 } }, + { 28, 17, { 0b11111111, 0b11111111, 0b11111110, 0b11100000 } }, + { 28, 18, { 0b11111111, 0b11111111, 0b11111110, 0b11110000 } }, + { 28, 19, { 0b11111111, 0b11111111, 0b11111111, 0b00000000 } }, + { 28, 20, { 0b11111111, 0b11111111, 0b11111111, 0b00010000 } }, + { 28, 21, { 0b11111111, 0b11111111, 0b11111111, 0b00100000 } }, + { 28, 23, { 0b11111111, 0b11111111, 0b11111111, 0b00110000 } }, + { 28, 24, { 0b11111111, 0b11111111, 0b11111111, 0b01000000 } }, + { 28, 25, { 0b11111111, 0b11111111, 0b11111111, 0b01010000 } }, + { 28, 26, { 0b11111111, 0b11111111, 0b11111111, 0b01100000 } }, + { 28, 27, { 0b11111111, 0b11111111, 0b11111111, 0b01110000 } }, + { 28, 28, { 0b11111111, 0b11111111, 0b11111111, 0b10000000 } }, + { 28, 29, { 0b11111111, 0b11111111, 0b11111111, 0b10010000 } }, + { 28, 30, { 0b11111111, 0b11111111, 0b11111111, 0b10100000 } }, + { 28, 31, { 0b11111111, 0b11111111, 0b11111111, 0b10110000 } }, + { 28, 127, { 0b11111111, 0b11111111, 0b11111111, 0b11000000 } }, + { 28, 220, { 0b11111111, 0b11111111, 0b11111111, 0b11010000 } }, + { 28, 249, { 0b11111111, 0b11111111, 0b11111111, 0b11100000 } }, + { 30, 10, { 0b11111111, 0b11111111, 0b11111111, 0b11110000 } }, + { 30, 13, { 0b11111111, 0b11111111, 0b11111111, 0b11110100 } }, + { 30, 22, { 0b11111111, 0b11111111, 0b11111111, 0b11111000 } }, +}; + +static const struct decode_elem eos = { + 30, 0, { 0b11111111, 0b11111111, 0b11111111, 0b11111100 } +}; + +#define UINT32_BITLEN 32 + +#define MSB_MASK(len) (UINT32_MAX << (UINT32_BITLEN - len)) +#define LSB_MASK(len) ((1UL << len) - 1UL) + +static bool huffman_bits_compare(uint32_t bits, const struct decode_elem *entry) +{ + uint32_t mask = MSB_MASK(entry->bitlen); + uint32_t code = sys_get_be32(entry->code); + + if (code == (bits & mask)) { + return true; + } + + return false; +} + +static const struct decode_elem *huffman_decode_bits(uint32_t bits) +{ + for (int i = 0; i < ARRAY_SIZE(decode_table); i++) { + if (huffman_bits_compare(bits, &decode_table[i])) { + return &decode_table[i]; + } + } + + if (huffman_bits_compare(bits, &eos)) { + return &eos; + } + + return NULL; +} + +static const struct decode_elem *huffman_find_entry(uint8_t symbol) +{ + for (int i = 0; i < ARRAY_SIZE(decode_table); i++) { + if (decode_table[i].symbol == symbol) { + return &decode_table[i]; + } + } + + return NULL; +} + +#define MAX_PADDING_LEN 7 + +int http_hpack_huffman_decode(const uint8_t *encoded_buf, size_t encoded_len, + uint8_t *buf, size_t buflen) +{ + size_t encoded_bits_len = encoded_len * 8; + uint8_t bits_needed = UINT32_BITLEN; + const struct decode_elem *decoded; + uint8_t bits_in_byte_left = 8; + size_t decoded_len = 0; + uint32_t bits = 0; + + if (encoded_buf == NULL || buf == NULL || encoded_len == 0) { + return -EINVAL; + } + + while (encoded_bits_len > 0) { + /* Refill the bits variable */ + while (bits_needed > 0) { + if (encoded_len > 0) { + if (bits_in_byte_left <= bits_needed) { + /* Consume rest of the byte */ + bits <<= bits_in_byte_left; + bits |= *encoded_buf & + LSB_MASK(bits_in_byte_left); + bits_needed -= bits_in_byte_left; + bits_in_byte_left = 0; + } else { + /* Consume part of the byte */ + bits <<= bits_needed; + bits |= (*encoded_buf >> + (bits_in_byte_left - bits_needed)) & + LSB_MASK(bits_needed); + bits_in_byte_left -= bits_needed; + bits_needed = 0; + } + } else { + /* Pad with ones */ + bits <<= bits_needed; + bits |= LSB_MASK(bits_needed); + bits_needed = 0; + } + + /* Move to next encoded byte */ + if (bits_in_byte_left == 0) { + encoded_buf++; + encoded_len--; + bits_in_byte_left = 8; + } + } + + /* Pass to decoder */ + decoded = huffman_decode_bits(bits); + if (decoded == NULL) { + LOG_ERR("No symbol found"); + return -EBADMSG; + } + + if (decoded == &eos) { + if (encoded_bits_len > MAX_PADDING_LEN) { + LOG_ERR("eos reached prematurely"); + return -EBADMSG; + } + + break; + } + + if (encoded_bits_len < decoded->bitlen) { + LOG_ERR("Invalid symbol used for padding"); + return -EBADMSG; + } + + /* Remove consumed bits from bits variable. */ + bits_needed += decoded->bitlen; + encoded_bits_len -= decoded->bitlen; + + /* Store decoded symbol */ + if (buflen == 0) { + LOG_ERR("Not enough buffer to decode string"); + return -ENOBUFS; + } + + *buf = decoded->symbol; + buf++; + buflen--; + decoded_len++; + } + + return decoded_len; +} + +int http_hpack_huffman_encode(const uint8_t *str, size_t str_len, + uint8_t *buf, size_t buflen) +{ + const struct decode_elem *entry; + size_t buflen_bits = buflen * 8; + uint8_t bit_offset = 0; + int len = 0; + + if (str == NULL || buf == NULL || str_len == 0) { + return -EINVAL; + } + + while (str_len > 0) { + uint32_t code; + uint8_t bitlen; + + entry = huffman_find_entry(*str); + if (entry == NULL) { + return -EINVAL; + } + + if (entry->bitlen > buflen_bits) { + return -ENOBUFS; + } + + bitlen = entry->bitlen; + code = sys_get_be32(entry->code); + + while (bitlen > 0) { + uint8_t to_copy = MIN(8 - bit_offset, bitlen); + uint8_t byte = (uint8_t)((code & MSB_MASK(to_copy)) >> + (24 + bit_offset)); + + /* This is way suboptimal */ + if (bit_offset == 0) { + *buf = byte; + } else { + *buf |= byte; + } + + code <<= to_copy; + bitlen -= to_copy; + bit_offset = (bit_offset + to_copy) % 8; + + if (bit_offset == 0) { + buf++; + len++; + } + } + + buflen_bits -= entry->bitlen; + str_len--; + str++; + } + + /* Pad with ones. */ + if (bit_offset > 0) { + *buf |= LSB_MASK((8 - bit_offset)); + len++; + } + + return len; +} From d4dca18e49d0c4bc2d7e6ef7df12a883a59320b4 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Thu, 9 Nov 2023 20:09:03 +0200 Subject: [PATCH 3/5] net: lib: http_server: Initial HTTP server support Original code developed as a GSoC 2023 project by Emna Rekik. Code refactored in order to provide better bisectability as the origical commits were not bisectable. The server supports static and dynamic resources, managed by HTTP_SERVICE/HTTP_RESOURCE macros. Fixes #59685 Fixes #59686 Fixes #59688 Fixes #59690 Fixes #59670 Fixes #59700 Fixes #59684 Fixes #59693 Fixes #59693 Fixes #59694 Fixes #59699 Fixes #59696 Fixes #59688 Fixes #59690 Fixes #59670 Fixes #59700 Fixes #59685 Fixes #59686 Fixes #59688 Fixes #59691 Signed-off-by: Emna Rekik Signed-off-by: Jukka Rissanen Signed-off-by: Robert Lubos --- .../linker/common-rom/common-rom-net.ld | 1 + include/zephyr/net/http/frame.h | 52 + include/zephyr/net/http/method.h | 2 + include/zephyr/net/http/server.h | 174 +++ include/zephyr/net/http/service.h | 18 +- subsys/net/lib/CMakeLists.txt | 2 +- subsys/net/lib/http/CMakeLists.txt | 5 + subsys/net/lib/http/Kconfig | 89 +- subsys/net/lib/http/headers/mlog.h | 18 + subsys/net/lib/http/headers/server_internal.h | 45 + subsys/net/lib/http/http_client.c | 2 +- subsys/net/lib/http/http_server_core.c | 723 ++++++++++ subsys/net/lib/http/http_server_http1.c | 491 +++++++ subsys/net/lib/http/http_server_http2.c | 1243 +++++++++++++++++ 14 files changed, 2853 insertions(+), 12 deletions(-) create mode 100644 include/zephyr/net/http/frame.h create mode 100644 include/zephyr/net/http/server.h create mode 100644 subsys/net/lib/http/headers/mlog.h create mode 100644 subsys/net/lib/http/headers/server_internal.h create mode 100644 subsys/net/lib/http/http_server_core.c create mode 100644 subsys/net/lib/http/http_server_http1.c create mode 100644 subsys/net/lib/http/http_server_http2.c diff --git a/include/zephyr/linker/common-rom/common-rom-net.ld b/include/zephyr/linker/common-rom/common-rom-net.ld index 5305f9cf873c2..95aeeb99e8933 100644 --- a/include/zephyr/linker/common-rom/common-rom-net.ld +++ b/include/zephyr/linker/common-rom/common-rom-net.ld @@ -16,6 +16,7 @@ #if defined(CONFIG_HTTP_SERVER) ITERABLE_SECTION_ROM(http_service_desc, Z_LINK_ITERABLE_SUBALIGN) + ITERABLE_SECTION_ROM(http_resource_desc, Z_LINK_ITERABLE_SUBALIGN) #endif #if defined(CONFIG_COAP_SERVER) diff --git a/include/zephyr/net/http/frame.h b/include/zephyr/net/http/frame.h new file mode 100644 index 0000000000000..efdd78edf6ee5 --- /dev/null +++ b/include/zephyr/net/http/frame.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023, Emna Rekik + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_NET_HTTP_SERVER_FRAME_H_ +#define ZEPHYR_INCLUDE_NET_HTTP_SERVER_FRAME_H_ + +#include + +enum http_frame_type { + HTTP_SERVER_DATA_FRAME = 0x00, + HTTP_SERVER_HEADERS_FRAME = 0x01, + HTTP_SERVER_PRIORITY_FRAME = 0x02, + HTTP_SERVER_RST_STREAM_FRAME = 0x03, + HTTP_SERVER_SETTINGS_FRAME = 0x04, + HTTP_SERVER_PUSH_PROMISE_FRAME = 0x05, + HTTP_SERVER_PING_FRAME = 0x06, + HTTP_SERVER_GOAWAY_FRAME = 0x07, + HTTP_SERVER_WINDOW_UPDATE_FRAME = 0x08, + HTTP_SERVER_CONTINUATION_FRAME = 0x09 +}; + +#define HTTP_SERVER_HPACK_METHOD 0 +#define HTTP_SERVER_HPACK_PATH 1 + +#define HTTP_SERVER_FLAG_SETTINGS_ACK 0x1 +#define HTTP_SERVER_FLAG_END_HEADERS 0x4 +#define HTTP_SERVER_FLAG_END_STREAM 0x1 + +#define HTTP_SERVER_FRAME_HEADER_SIZE 9 +#define HTTP_SERVER_FRAME_LENGTH_OFFSET 0 +#define HTTP_SERVER_FRAME_TYPE_OFFSET 3 +#define HTTP_SERVER_FRAME_FLAGS_OFFSET 4 +#define HTTP_SERVER_FRAME_STREAM_ID_OFFSET 5 + +struct http_settings_field { + uint16_t id; + uint32_t value; +} __packed; + +enum http_settings { + HTTP_SETTINGS_HEADER_TABLE_SIZE = 1, + HTTP_SETTINGS_ENABLE_PUSH = 2, + HTTP_SETTINGS_MAX_CONCURRENT_STREAMS = 3, + HTTP_SETTINGS_INITIAL_WINDOW_SIZE = 4, + HTTP_SETTINGS_MAX_FRAME_SIZE = 5, + HTTP_SETTINGS_MAX_HEADER_LIST_SIZE = 6, +}; + +#endif diff --git a/include/zephyr/net/http/method.h b/include/zephyr/net/http/method.h index a45c555e97b97..b27e3144b236b 100644 --- a/include/zephyr/net/http/method.h +++ b/include/zephyr/net/http/method.h @@ -57,6 +57,8 @@ enum http_method { HTTP_MKCALENDAR = 30, /**< MKCALENDAR */ HTTP_LINK = 31, /**< LINK */ HTTP_UNLINK = 32, /**< UNLINK */ + + HTTP_METHOD_END_VALUE /* keep this the last value */ }; #ifdef __cplusplus diff --git a/include/zephyr/net/http/server.h b/include/zephyr/net/http/server.h new file mode 100644 index 0000000000000..35f1dcdc872f1 --- /dev/null +++ b/include/zephyr/net/http/server.h @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2023, Emna Rekik + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_NET_HTTP_SERVER_H_ +#define ZEPHYR_INCLUDE_NET_HTTP_SERVER_H_ + +#include + +#include +#include +#include +#include + +#define HTTP_SERVER_CLIENT_BUFFER_SIZE CONFIG_HTTP_SERVER_CLIENT_BUFFER_SIZE +#define HTTP_SERVER_MAX_STREAMS CONFIG_HTTP_SERVER_MAX_STREAMS +#define HTTP_SERVER_MAX_CONTENT_TYPE_LEN CONFIG_HTTP_SERVER_MAX_CONTENT_TYPE_LENGTH + +#define HTTP2_PREFACE "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" + +enum http_resource_type { + HTTP_RESOURCE_TYPE_STATIC, + HTTP_RESOURCE_TYPE_DYNAMIC, +}; + +struct http_resource_detail { + uint32_t bitmask_of_supported_http_methods; + enum http_resource_type type; + int path_len; /* length of the URL path */ + const char *content_encoding; +}; +BUILD_ASSERT(NUM_BITS( + sizeof(((struct http_resource_detail *)0)->bitmask_of_supported_http_methods)) + >= (HTTP_METHOD_END_VALUE - 1)); + +struct http_resource_detail_static { + struct http_resource_detail common; + const void *static_data; + size_t static_data_len; +}; + +struct http_client_ctx; + +/** Indicates the status of the currently processed piece of data. */ +enum http_data_status { + /** Transaction aborted, data incomplete. */ + HTTP_SERVER_DATA_ABORTED = -1, + /** Transaction incomplete, more data expected. */ + HTTP_SERVER_DATA_MORE = 0, + /** Final data fragment in current transaction. */ + HTTP_SERVER_DATA_FINAL = 1, +}; + +/** + * @typedef http_resource_dynamic_cb_t + * @brief Callback used when data is received. Data to be sent to client + * can be specified. + * + * @param client HTTP context information for this client connection. + * @param status HTTP data status, indicate whether more data is expected or not. + * @param data_buffer Data received. + * @param data_len Amount of data received. + * @param user_data User specified data. + * + * @return >0 amount of data to be sent to client, let server to call this + * function again when new data is received. + * 0 nothing to sent to client, close the connection + * <0 error, close the connection. + */ +typedef int (*http_resource_dynamic_cb_t)(struct http_client_ctx *client, + enum http_data_status status, + uint8_t *data_buffer, + size_t data_len, + void *user_data); + +struct http_resource_detail_dynamic { + struct http_resource_detail common; + http_resource_dynamic_cb_t cb; + uint8_t *data_buffer; + size_t data_buffer_len; + struct http_client_ctx *holder; + void *user_data; +}; + +struct http_resource_detail_rest { + struct http_resource_detail common; +}; + +enum http_stream_state { + HTTP_SERVER_STREAM_IDLE, + HTTP_SERVER_STREAM_RESERVED_LOCAL, + HTTP_SERVER_STREAM_RESERVED_REMOTE, + HTTP_SERVER_STREAM_OPEN, + HTTP_SERVER_STREAM_HALF_CLOSED_LOCAL, + HTTP_SERVER_STREAM_HALF_CLOSED_REMOTE, + HTTP_SERVER_STREAM_CLOSED +}; + +enum http_server_state { + HTTP_SERVER_FRAME_HEADER_STATE, + HTTP_SERVER_PREFACE_STATE, + HTTP_SERVER_REQUEST_STATE, + HTTP_SERVER_FRAME_DATA_STATE, + HTTP_SERVER_FRAME_HEADERS_STATE, + HTTP_SERVER_FRAME_SETTINGS_STATE, + HTTP_SERVER_FRAME_PRIORITY_STATE, + HTTP_SERVER_FRAME_WINDOW_UPDATE_STATE, + HTTP_SERVER_FRAME_CONTINUATION_STATE, + HTTP_SERVER_FRAME_PING_STATE, + HTTP_SERVER_FRAME_RST_STREAM_STATE, + HTTP_SERVER_FRAME_GOAWAY_STATE, + HTTP_SERVER_DONE_STATE, +}; + +enum http1_parser_state { + HTTP1_INIT_HEADER_STATE, + HTTP1_WAITING_HEADER_STATE, + HTTP1_RECEIVING_HEADER_STATE, + HTTP1_RECEIVED_HEADER_STATE, + HTTP1_RECEIVING_DATA_STATE, + HTTP1_MESSAGE_COMPLETE_STATE, +}; + +#define HTTP_SERVER_INITIAL_WINDOW_SIZE 65536 + +struct http_stream_ctx { + int stream_id; + enum http_stream_state stream_state; + int window_size; /**< Stream-level window size. */ +}; + +struct http_frame { + uint32_t length; + uint32_t stream_identifier; + uint8_t type; + uint8_t flags; + uint8_t *payload; +}; + +struct http_client_ctx { + int fd; + bool preface_sent; + bool has_upgrade_header; + unsigned char buffer[HTTP_SERVER_CLIENT_BUFFER_SIZE]; + unsigned char *cursor; /**< Cursor indicating currently processed byte. */ + size_t data_len; /**< Data left to process in the buffer. */ + int window_size; /**< Connection-level window size. */ + enum http_server_state server_state; + struct http_frame current_frame; + struct http_resource_detail *current_detail; + struct http_hpack_header_buf header_field; + struct http_stream_ctx streams[HTTP_SERVER_MAX_STREAMS]; + struct http_parser_settings parser_settings; + struct http_parser parser; + unsigned char url_buffer[CONFIG_HTTP_SERVER_MAX_URL_LENGTH]; + unsigned char content_type[CONFIG_HTTP_SERVER_MAX_CONTENT_TYPE_LENGTH]; + size_t content_len; + enum http_method method; + enum http1_parser_state parser_state; + int http1_frag_data_len; + bool headers_sent; + struct k_work_delayable inactivity_timer; +}; + +/* Starts the HTTP2 server */ +int http_server_start(void); + +/* Stops the HTTP2 server */ +int http_server_stop(void); + +#endif diff --git a/include/zephyr/net/http/service.h b/include/zephyr/net/http/service.h index 3e48147cd01f7..2f17b5022f7cd 100644 --- a/include/zephyr/net/http/service.h +++ b/include/zephyr/net/http/service.h @@ -44,7 +44,7 @@ struct http_resource_desc { const STRUCT_SECTION_ITERABLE_ALTERNATE(http_resource_desc_##_service, http_resource_desc, \ _name) = { \ .resource = _resource, \ - .detail = (_detail), \ + .detail = (void *)(_detail), \ } struct http_service_desc { @@ -55,12 +55,14 @@ struct http_service_desc { size_t backlog; struct http_resource_desc *res_begin; struct http_resource_desc *res_end; +#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) const sec_tag_t *sec_tag_list; size_t sec_tag_list_size; +#endif }; -#define __z_http_service_define(_name, _host, _port, _concurrent, _backlog, _detail, _res_begin, \ - _res_end, ...) \ +#define __z_http_service_define(_name, _host, _port, _concurrent, _backlog, _detail, _res_begin, \ + _res_end, ...) \ static const STRUCT_SECTION_ITERABLE(http_service_desc, _name) = { \ .host = _host, \ .port = (uint16_t *)(_port), \ @@ -69,10 +71,12 @@ struct http_service_desc { .backlog = (_backlog), \ .res_begin = (_res_begin), \ .res_end = (_res_end), \ - .sec_tag_list = COND_CODE_0(NUM_VA_ARGS_LESS_1(__VA_ARGS__), (NULL), \ - (GET_ARG_N(1, __VA_ARGS__))), \ - .sec_tag_list_size = COND_CODE_0(NUM_VA_ARGS_LESS_1(__VA_ARGS__), (0), \ - (GET_ARG_N(1, GET_ARGS_LESS_N(1, __VA_ARGS__)))), \ + COND_CODE_1(CONFIG_NET_SOCKETS_SOCKOPT_TLS, \ + (.sec_tag_list = COND_CODE_0(NUM_VA_ARGS_LESS_1(__VA_ARGS__), (NULL), \ + (GET_ARG_N(1, __VA_ARGS__))),), ()) \ + COND_CODE_1(CONFIG_NET_SOCKETS_SOCKOPT_TLS, \ + (.sec_tag_list_size = COND_CODE_0(NUM_VA_ARGS_LESS_1(__VA_ARGS__), (0),\ + (GET_ARG_N(1, GET_ARGS_LESS_N(1, __VA_ARGS__))))), ())\ } /** diff --git a/subsys/net/lib/CMakeLists.txt b/subsys/net/lib/CMakeLists.txt index 40795ac5a1696..02d43e2190af5 100644 --- a/subsys/net/lib/CMakeLists.txt +++ b/subsys/net/lib/CMakeLists.txt @@ -27,7 +27,7 @@ if (CONFIG_DNS_RESOLVER add_subdirectory(dns) endif() -if(CONFIG_HTTP_PARSER_URL OR CONFIG_HTTP_PARSER OR CONFIG_HTTP_CLIENT) +if(CONFIG_HTTP) add_subdirectory(http) endif() diff --git a/subsys/net/lib/http/CMakeLists.txt b/subsys/net/lib/http/CMakeLists.txt index 5d9d4a06a3254..4d4409b2ad731 100644 --- a/subsys/net/lib/http/CMakeLists.txt +++ b/subsys/net/lib/http/CMakeLists.txt @@ -11,3 +11,8 @@ zephyr_include_directories(${ZEPHYR_BASE}/subsys/net/ip) zephyr_library_sources_ifdef(CONFIG_HTTP_PARSER http_parser.c) zephyr_library_sources_ifdef(CONFIG_HTTP_PARSER_URL http_parser_url.c) zephyr_library_sources_ifdef(CONFIG_HTTP_CLIENT http_client.c) +zephyr_library_sources_ifdef(CONFIG_HTTP_SERVER http_server_core.c + http_server_http1.c + http_server_http2.c + http_hpack.c + http_huffman.c) diff --git a/subsys/net/lib/http/Kconfig b/subsys/net/lib/http/Kconfig index 789e45827c33a..b78d8e93e3555 100644 --- a/subsys/net/lib/http/Kconfig +++ b/subsys/net/lib/http/Kconfig @@ -32,13 +32,96 @@ config HTTP_CLIENT config HTTP_SERVER bool "HTTP Server [EXPERIMENTAL]" - select WARN_EXPERIMENTAL + select HTTP_PARSER + select HTTP_PARSER_URL + select EXPERIMENTAL + help + HTTP1 and HTTP2 server support. + +if HTTP_SERVER + +config HTTP_SERVER_STACK_SIZE + int "HTTP server thread stack size" + default 3072 + help + HTTP server thread stack size for processing RX/TX events. + +config HTTP_SERVER_NUM_SERVICES + int "Number of HTTP Server Instances" + default 1 + range 1 100 + help + This setting determines the number of http services that the server supports. + +config HTTP_SERVER_MAX_CLIENTS + int "Max number of HTTP/2 clients" + default 3 + range 1 100 + help + This setting determines the maximum number of HTTP/2 clients that the server can handle at once. + +config HTTP_SERVER_MAX_STREAMS + int "Max number of HTTP/2 streams" + default 10 + range 1 100 + help + This setting determines the maximum number of HTTP/2 streams for each client. + +config HTTP_SERVER_CLIENT_BUFFER_SIZE + int "Client Buffer Size" + default 256 + range 64 1024 help - HTTP server support. - Note: this is a work-in-progress + This setting determines the buffer size for each client. + +config HTTP_SERVER_HUFFMAN_DECODE_BUFFER_SIZE + int "Size of the buffer used for decoding Huffman-encoded strings" + default 256 + range 64 1024 + help + Size of the buffer used for decoding Huffman-encoded strings when + processing HPACK compressed headers. This effectively limits the + maximum length of an individual HTTP header supported. + +config HTTP_SERVER_MAX_URL_LENGTH + int "Maximum HTTP URL Length" + default 256 + range 32 2048 + help + This setting determines the maximum length of the HTTP URL that the server can process. + +config HTTP_SERVER_MAX_CONTENT_TYPE_LENGTH + int "Maximum HTTP Content-Type Length" + default 64 + range 1 128 + help + This setting determines the maximum length of the HTTP Content-Length field. + +config HTTP_SERVER_CLIENT_INACTIVITY_TIMEOUT + int "Client inactivity timeout (seconds)" + default 10 + range 1 86400 + help + This timeout specifies maximum time the client may remain inactive + (i. e. not sending or receiving any data) before the server drops the + connection. + +endif + +# Hidden option to avoid having multiple individual options that are ORed together +config HTTP + bool + depends on (HTTP_PARSER_URL || HTTP_PARSER || HTTP_CLIENT || HTTP_SERVER) + default y module = NET_HTTP module-dep = NET_LOG module-str = Log level for HTTP client library module-help = Enables HTTP client code to output debug messages. source "subsys/net/Kconfig.template.log_config.net" + +module = NET_HTTP_SERVER +module-dep = NET_LOG +module-str = Log level for HTTP server library +module-help = Enables HTTP server code to output debug messages. +source "subsys/net/Kconfig.template.log_config.net" diff --git a/subsys/net/lib/http/headers/mlog.h b/subsys/net/lib/http/headers/mlog.h new file mode 100644 index 0000000000000..e70c67d2cc4da --- /dev/null +++ b/subsys/net/lib/http/headers/mlog.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2023, Emna Rekik + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef MLOG_H_ +#define MLOG_H_ + +#include + +#define LOG_MODULE_REGISTER(x, y) + +#define LOG_INF(fmt, args...) printf("I: " fmt "\n", ##args) +#define LOG_ERR(fmt, args...) printf("E: " fmt "\n", ##args) +#define LOG_DBG(fmt, args...) printf("D: " fmt "\n", ##args) + +#endif diff --git a/subsys/net/lib/http/headers/server_internal.h b/subsys/net/lib/http/headers/server_internal.h new file mode 100644 index 0000000000000..61e8a36fa538a --- /dev/null +++ b/subsys/net/lib/http/headers/server_internal.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2023, Emna Rekik + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef HTTP_SERVER_INTERNAL_H_ +#define HTTP_SERVER_INTERNAL_H_ + +#include + +#include +#include +#include +#include +#include + +/* HTTP1/HTTP2 state handling */ +int handle_http_frame_rst_frame(struct http_client_ctx *client); +int handle_http_frame_goaway(struct http_client_ctx *client); +int handle_http_frame_settings(struct http_client_ctx *client); +int handle_http_frame_priority(struct http_client_ctx *client); +int handle_http_frame_continuation(struct http_client_ctx *client); +int handle_http_frame_window_update(struct http_client_ctx *client); +int handle_http_frame_header(struct http_client_ctx *client); +int handle_http_frame_headers(struct http_client_ctx *client); +int handle_http_frame_data(struct http_client_ctx *client); +int handle_http1_request(struct http_client_ctx *client); +int handle_http1_to_http2_upgrade(struct http_client_ctx *client); + +int enter_http1_request(struct http_client_ctx *client); +int enter_http2_request(struct http_client_ctx *client); +int enter_http_done_state(struct http_client_ctx *client); + +/* Others */ +struct http_resource_detail *get_resource_detail(const char *path, int *len); +int http_server_sendall(struct http_client_ctx *client, const void *buf, size_t len); +void http_client_timer_restart(struct http_client_ctx *client); + +/* TODO Could be static, but currently used in tests. */ +int parse_http_frame_header(struct http_client_ctx *client); +const char *get_frame_type_name(enum http_frame_type type); + +#endif /* HTTP_SERVER_INTERNAL_H_ */ diff --git a/subsys/net/lib/http/http_client.c b/subsys/net/lib/http/http_client.c index 0f73ce6e8f880..58ff1389b47b4 100644 --- a/subsys/net/lib/http/http_client.c +++ b/subsys/net/lib/http/http_client.c @@ -11,7 +11,7 @@ */ #include -LOG_MODULE_REGISTER(net_http, CONFIG_NET_HTTP_LOG_LEVEL); +LOG_MODULE_REGISTER(net_http_client, CONFIG_NET_HTTP_LOG_LEVEL); #include #include diff --git a/subsys/net/lib/http/http_server_core.c b/subsys/net/lib/http/http_server_core.c new file mode 100644 index 0000000000000..a7f11b45b8ea0 --- /dev/null +++ b/subsys/net/lib/http/http_server_core.c @@ -0,0 +1,723 @@ +/* + * Copyright (c) 2023, Emna Rekik + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(net_http_server, CONFIG_NET_HTTP_SERVER_LOG_LEVEL); + +#include "../../ip/net_private.h" +#include "headers/server_internal.h" + +#if defined(CONFIG_NET_TC_THREAD_COOPERATIVE) +/* Lowest priority cooperative thread */ +#define THREAD_PRIORITY K_PRIO_COOP(CONFIG_NUM_COOP_PRIORITIES - 1) +#else +#define THREAD_PRIORITY K_PRIO_PREEMPT(CONFIG_NUM_PREEMPT_PRIORITIES - 1) +#endif + +#define INVALID_SOCK -1 +#define INACTIVITY_TIMEOUT K_SECONDS(CONFIG_HTTP_SERVER_CLIENT_INACTIVITY_TIMEOUT) + +#define HTTP_SERVER_MAX_SERVICES CONFIG_HTTP_SERVER_NUM_SERVICES +#define HTTP_SERVER_MAX_CLIENTS CONFIG_HTTP_SERVER_MAX_CLIENTS +#define HTTP_SERVER_SOCK_COUNT (1 + HTTP_SERVER_MAX_SERVICES + HTTP_SERVER_MAX_CLIENTS) + +struct http_server_ctx { + int num_clients; + int listen_fds; /* max value of 1 + MAX_SERVICES */ + + /* First pollfd is eventfd that can be used to stop the server, + * then we have the server listen sockets, + * and then the accepted sockets. + */ + struct zsock_pollfd fds[HTTP_SERVER_SOCK_COUNT]; + struct http_client_ctx clients[HTTP_SERVER_MAX_CLIENTS]; +}; + +static struct http_server_ctx server_ctx; +static K_SEM_DEFINE(server_start, 0, 1); +static bool server_running; + +int http_server_init(struct http_server_ctx *ctx) +{ + int proto; + int failed = 0, count = 0; + int svc_count; + socklen_t len; + int fd, af, i; + struct sockaddr_storage addr_storage; + const union { + struct sockaddr *addr; + struct sockaddr_in *addr4; + struct sockaddr_in6 *addr6; + } addr = { + .addr = (struct sockaddr *)&addr_storage + }; + + HTTP_SERVICE_COUNT(&svc_count); + + /* Initialize fds */ + memset(ctx->fds, 0, sizeof(ctx->fds)); + memset(ctx->clients, 0, sizeof(ctx->clients)); + + for (i = 0; i < ARRAY_SIZE(ctx->fds); i++) { + ctx->fds[i].fd = INVALID_SOCK; + } + + /* Create an eventfd that can be used to trigger events during polling */ + fd = eventfd(0, 0); + if (fd < 0) { + fd = -errno; + LOG_ERR("eventfd failed (%d)", fd); + return fd; + } + + ctx->fds[count].fd = fd; + ctx->fds[count].events = ZSOCK_POLLIN; + count++; + + HTTP_SERVICE_FOREACH(svc) { + /* set the default address (in6addr_any / INADDR_ANY are all 0) */ + memset(&addr_storage, 0, sizeof(struct sockaddr_storage)); + + /* Set up the server address struct according to address family */ + if (IS_ENABLED(CONFIG_NET_IPV6) && + zsock_inet_pton(AF_INET6, svc->host, &addr.addr6->sin6_addr) == 1) { + af = AF_INET6; + len = sizeof(*addr.addr6); + + addr.addr6->sin6_family = AF_INET6; + addr.addr6->sin6_port = htons(*svc->port); + } else if (IS_ENABLED(CONFIG_NET_IPV4) && + zsock_inet_pton(AF_INET, svc->host, &addr.addr4->sin_addr) == 1) { + af = AF_INET; + len = sizeof(*addr.addr4); + + addr.addr4->sin_family = AF_INET; + addr.addr4->sin_port = htons(*svc->port); + } else if (IS_ENABLED(CONFIG_NET_IPV6)) { + /* prefer IPv6 if both IPv6 and IPv4 are supported */ + af = AF_INET6; + len = sizeof(*addr.addr6); + + addr.addr6->sin6_family = AF_INET6; + addr.addr6->sin6_port = htons(*svc->port); + } else if (IS_ENABLED(CONFIG_NET_IPV4)) { + af = AF_INET; + len = sizeof(*addr.addr4); + + addr.addr4->sin_family = AF_INET; + addr.addr4->sin_port = htons(*svc->port); + } else { + LOG_ERR("Neither IPv4 nor IPv6 is enabled"); + failed++; + break; + } + + /* Create a socket */ + if (COND_CODE_1(CONFIG_NET_SOCKETS_SOCKOPT_TLS, + (svc->sec_tag_list != NULL), + (0))) { + proto = IPPROTO_TLS_1_2; + } else { + proto = IPPROTO_TCP; + } + + fd = zsock_socket(af, SOCK_STREAM, proto); + if (fd < 0) { + LOG_ERR("socket: %d", errno); + failed++; + continue; + } + +#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) + if (svc->sec_tag_list != NULL) { + if (zsock_setsockopt(fd, SOL_TLS, TLS_SEC_TAG_LIST, + svc->sec_tag_list, + svc->sec_tag_list_size) < 0) { + LOG_ERR("setsockopt: %d", errno); + zsock_close(fd); + continue; + } + + if (zsock_setsockopt(fd, SOL_TLS, TLS_HOSTNAME, "localhost", + sizeof("localhost")) < 0) { + LOG_ERR("setsockopt: %d", errno); + zsock_close(fd); + continue; + } + } +#endif + + if (zsock_setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, + sizeof(int)) < 0) { + LOG_ERR("setsockopt: %d", errno); + zsock_close(fd); + continue; + } + + if (zsock_bind(fd, addr.addr, len) < 0) { + LOG_ERR("bind: %d", errno); + failed++; + zsock_close(fd); + continue; + } + + if (*svc->port == 0) { + /* ephemeral port - read back the port number */ + len = sizeof(addr_storage); + if (zsock_getsockname(fd, addr.addr, &len) < 0) { + LOG_ERR("getsockname: %d", errno); + zsock_close(fd); + continue; + } + + *svc->port = ntohs(addr.addr4->sin_port); + } + + if (zsock_listen(fd, HTTP_SERVER_MAX_CLIENTS) < 0) { + LOG_ERR("listen: %d", errno); + failed++; + zsock_close(fd); + continue; + } + + LOG_DBG("Initialized HTTP Service %s:%u", svc->host, *svc->port); + + ctx->fds[count].fd = fd; + ctx->fds[count].events = ZSOCK_POLLIN; + count++; + } + + if (failed >= svc_count) { + LOG_ERR("All services failed (%d)", failed); + return -ESRCH; + } + + ctx->listen_fds = count; + ctx->num_clients = 0; + + return 0; +} + +static int accept_new_client(int server_fd) +{ + int new_socket; + socklen_t addrlen; + struct sockaddr_storage sa; + + memset(&sa, 0, sizeof(sa)); + addrlen = sizeof(sa); + + new_socket = zsock_accept(server_fd, (struct sockaddr *)&sa, &addrlen); + if (new_socket < 0) { + new_socket = -errno; + LOG_DBG("[%d] accept failed (%d)", server_fd, new_socket); + return new_socket; + } + + LOG_DBG("New client from %s:%d", + net_sprint_addr(sa.ss_family, &net_sin((struct sockaddr *)&sa)->sin_addr), + ntohs(net_sin((struct sockaddr *)&sa)->sin_port)); + + return new_socket; +} + +static int close_all_sockets(struct http_server_ctx *ctx) +{ + zsock_close(ctx->fds[0].fd); /* close eventfd */ + ctx->fds[0].fd = -1; + + for (int i = 1; i < ARRAY_SIZE(ctx->fds); i++) { + if (ctx->fds[i].fd < 0) { + continue; + } + + zsock_close(ctx->fds[i].fd); + ctx->fds[i].fd = -1; + } + + return 0; +} + +static void client_release_resources(struct http_client_ctx *client) +{ + struct http_resource_detail *detail; + struct http_resource_detail_dynamic *dynamic_detail; + + HTTP_SERVICE_FOREACH(service) { + HTTP_SERVICE_FOREACH_RESOURCE(service, resource) { + detail = resource->detail; + + if (detail->type != HTTP_RESOURCE_TYPE_DYNAMIC) { + continue; + } + + dynamic_detail = (struct http_resource_detail_dynamic *)detail; + + if (dynamic_detail->holder != client) { + continue; + } + + /* If the client still holds the resource at this point, + * it means the transaction was not complete. Release + * the resource and notify application. + */ + dynamic_detail->holder = NULL; + + if (dynamic_detail->cb == NULL) { + continue; + } + + dynamic_detail->cb(client, HTTP_SERVER_DATA_ABORTED, + NULL, 0, dynamic_detail->user_data); + } + } +} + +static void close_client_connection(struct http_client_ctx *client) +{ + int i; + struct k_work_sync sync; + + __ASSERT_NO_MSG(IS_ARRAY_ELEMENT(server_ctx.clients, client)); + + k_work_cancel_delayable_sync(&client->inactivity_timer, &sync); + zsock_close(client->fd); + client_release_resources(client); + + server_ctx.num_clients--; + + for (i = server_ctx.listen_fds; i < ARRAY_SIZE(server_ctx.fds); i++) { + if (server_ctx.fds[i].fd == client->fd) { + server_ctx.fds[i].fd = INVALID_SOCK; + break; + } + } + + memset(client, 0, sizeof(struct http_client_ctx)); + client->fd = INVALID_SOCK; + +} + +static void client_timeout(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct http_client_ctx *client = + CONTAINER_OF(dwork, struct http_client_ctx, inactivity_timer); + + LOG_DBG("Client %p timeout", client); + + /* Shutdown the socket. This will be detected by poll() and a proper + * cleanup will proceed. + */ + (void)zsock_shutdown(client->fd, ZSOCK_SHUT_RD); +} + +void http_client_timer_restart(struct http_client_ctx *client) +{ + __ASSERT_NO_MSG(IS_ARRAY_ELEMENT(server_ctx.clients, client)); + + k_work_reschedule(&client->inactivity_timer, INACTIVITY_TIMEOUT); +} + +static void init_client_ctx(struct http_client_ctx *client, int new_socket) +{ + client->fd = new_socket; + client->data_len = 0; + client->server_state = HTTP_SERVER_PREFACE_STATE; + client->has_upgrade_header = false; + client->preface_sent = false; + client->window_size = HTTP_SERVER_INITIAL_WINDOW_SIZE; + + memset(client->buffer, 0, sizeof(client->buffer)); + memset(client->url_buffer, 0, sizeof(client->url_buffer)); + k_work_init_delayable(&client->inactivity_timer, client_timeout); + http_client_timer_restart(client); + + ARRAY_FOR_EACH(client->streams, i) { + client->streams[i].stream_state = HTTP_SERVER_STREAM_IDLE; + client->streams[i].stream_id = 0; + } +} + +static int handle_http_preface(struct http_client_ctx *client) +{ + LOG_DBG("HTTP_SERVER_PREFACE_STATE."); + + if (client->data_len < sizeof(HTTP2_PREFACE) - 1) { + /* We don't have full preface yet, get more data. */ + return -EAGAIN; + } + + if (strncmp(client->cursor, HTTP2_PREFACE, sizeof(HTTP2_PREFACE) - 1) != 0) { + return enter_http1_request(client); + } + + return enter_http2_request(client); +} + +static int handle_http_done(struct http_client_ctx *client) +{ + LOG_DBG("HTTP_SERVER_DONE_STATE"); + + close_client_connection(client); + + return -EAGAIN; +} + +int enter_http_done_state(struct http_client_ctx *client) +{ + close_client_connection(client); + + client->server_state = HTTP_SERVER_DONE_STATE; + + return -EAGAIN; +} + +static int handle_http_request(struct http_client_ctx *client) +{ + int ret = -EINVAL; + + client->cursor = client->buffer; + + do { + switch (client->server_state) { + case HTTP_SERVER_FRAME_DATA_STATE: + ret = handle_http_frame_data(client); + break; + case HTTP_SERVER_PREFACE_STATE: + ret = handle_http_preface(client); + break; + case HTTP_SERVER_REQUEST_STATE: + ret = handle_http1_request(client); + break; + case HTTP_SERVER_FRAME_HEADER_STATE: + ret = handle_http_frame_header(client); + break; + case HTTP_SERVER_FRAME_HEADERS_STATE: + ret = handle_http_frame_headers(client); + break; + case HTTP_SERVER_FRAME_CONTINUATION_STATE: + ret = handle_http_frame_continuation(client); + break; + case HTTP_SERVER_FRAME_SETTINGS_STATE: + ret = handle_http_frame_settings(client); + break; + case HTTP_SERVER_FRAME_WINDOW_UPDATE_STATE: + ret = handle_http_frame_window_update(client); + break; + case HTTP_SERVER_FRAME_RST_STREAM_STATE: + ret = handle_http_frame_rst_frame(client); + break; + case HTTP_SERVER_FRAME_GOAWAY_STATE: + ret = handle_http_frame_goaway(client); + break; + case HTTP_SERVER_FRAME_PRIORITY_STATE: + ret = handle_http_frame_priority(client); + break; + case HTTP_SERVER_DONE_STATE: + ret = handle_http_done(client); + break; + default: + ret = handle_http_done(client); + break; + } + } while (ret >= 0 && client->data_len > 0); + + if (ret < 0 && ret != -EAGAIN) { + return ret; + } + + if (client->data_len > 0) { + /* Move any remaining data in the buffer. */ + memmove(client->buffer, client->cursor, client->data_len); + } + + return 0; +} + +static int http_server_run(struct http_server_ctx *ctx) +{ + struct http_client_ctx *client; + eventfd_t value; + bool found_slot; + int new_socket; + int ret, i, j; + int sock_error; + socklen_t optlen = sizeof(int); + + value = 0; + + while (1) { + ret = zsock_poll(ctx->fds, HTTP_SERVER_SOCK_COUNT, -1); + if (ret < 0) { + ret = -errno; + LOG_DBG("poll failed (%d)", ret); + return ret; + } + + if (ret == 0) { + /* should not happen because timeout is -1 */ + break; + } + + if (ret == 1 && ctx->fds[0].revents) { + eventfd_read(ctx->fds[0].fd, &value); + LOG_DBG("Received stop event. exiting .."); + goto closing; + } + + for (i = 1; i < ARRAY_SIZE(ctx->fds); i++) { + if (ctx->fds[i].fd < 0) { + continue; + } + + if (ctx->fds[i].revents & ZSOCK_POLLHUP) { + if (i >= ctx->listen_fds) { + LOG_DBG("Client #%d has disconnected", + i - ctx->listen_fds); + + client = &ctx->clients[i - ctx->listen_fds]; + close_client_connection(client); + } + + continue; + } + + if (ctx->fds[i].revents & ZSOCK_POLLERR) { + (void)zsock_getsockopt(ctx->fds[i].fd, SOL_SOCKET, + SO_ERROR, &sock_error, &optlen); + LOG_DBG("Error on fd %d %d", ctx->fds[i].fd, sock_error); + + if (i >= ctx->listen_fds) { + client = &ctx->clients[i - ctx->listen_fds]; + close_client_connection(client); + continue; + } + + /* Listening socket error, abort. */ + LOG_ERR("Listening socket error, aborting."); + return -sock_error; + + } + + if (!(ctx->fds[i].revents & ZSOCK_POLLIN)) { + continue; + } + + /* First check if we have something to accept */ + if (i < ctx->listen_fds) { + new_socket = accept_new_client(ctx->fds[i].fd); + if (new_socket < 0) { + ret = -errno; + LOG_DBG("accept: %d", ret); + continue; + } + + found_slot = false; + + for (j = ctx->listen_fds; j < ARRAY_SIZE(ctx->fds); j++) { + if (ctx->fds[j].fd != INVALID_SOCK) { + continue; + } + + ctx->fds[j].fd = new_socket; + ctx->fds[j].events = ZSOCK_POLLIN; + ctx->fds[j].revents = 0; + + ctx->num_clients++; + + LOG_DBG("Init client #%d", j - ctx->listen_fds); + + init_client_ctx(&ctx->clients[j - ctx->listen_fds], + new_socket); + found_slot = true; + break; + } + + if (!found_slot) { + LOG_DBG("No free slot found."); + zsock_close(new_socket); + } + + continue; + } + + /* Client sock */ + client = &ctx->clients[i - ctx->listen_fds]; + + ret = zsock_recv(client->fd, client->buffer + client->data_len, + sizeof(client->buffer) - client->data_len, 0); + if (ret <= 0) { + if (ret == 0) { + LOG_DBG("Connection closed by peer for client #%d", + i - ctx->listen_fds); + } else { + ret = -errno; + LOG_DBG("ERROR reading from socket (%d)", ret); + } + + close_client_connection(client); + continue; + } + + client->data_len += ret; + + http_client_timer_restart(client); + + ret = handle_http_request(client); + if (ret < 0 && ret != -EAGAIN) { + if (ret == -ENOTCONN) { + LOG_DBG("Client closed connection while handling request"); + } else { + LOG_ERR("HTTP request handling error (%d)", ret); + } + close_client_connection(client); + } else if (client->data_len == sizeof(client->buffer)) { + /* If the RX buffer is still full after parsing, + * it means we won't be able to handle this request + * with the current buffer size. + */ + LOG_ERR("RX buffer too small to handle request"); + close_client_connection(client); + } + } + } + + return 0; + +closing: + /* Close all client connections and the server socket */ + return close_all_sockets(ctx); +} + +/* Compare two strings where the terminator is either "\0" or "?" */ +static int compare_strings(const char *s1, const char *s2) +{ + while ((*s1 && *s2) && (*s1 == *s2) && (*s1 != '?')) { + s1++; + s2++; + } + + /* Check if both strings have reached their terminators or '?' */ + if ((*s1 == '\0' || *s1 == '?') && (*s2 == '\0' || *s2 == '?')) { + return 0; /* Strings are equal */ + } + + return 1; /* Strings are not equal */ +} + +struct http_resource_detail *get_resource_detail(const char *path, + int *path_len) +{ + HTTP_SERVICE_FOREACH(service) { + HTTP_SERVICE_FOREACH_RESOURCE(service, resource) { + if (compare_strings(path, resource->resource) == 0) { + NET_DBG("Got match for %s", resource->resource); + + *path_len = strlen(resource->resource); + return resource->detail; + } + } + } + + NET_DBG("No match for %s", path); + + return NULL; +} + +int http_server_sendall(struct http_client_ctx *client, const void *buf, size_t len) +{ + while (len) { + ssize_t out_len = zsock_send(client->fd, buf, len, 0); + + if (out_len < 0) { + return -errno; + } + + buf = (const char *)buf + out_len; + len -= out_len; + + http_client_timer_restart(client); + } + + return 0; +} + +int http_server_start(void) +{ + if (server_running) { + LOG_DBG("HTTP server already started"); + return -EALREADY; + } + + server_running = true; + k_sem_give(&server_start); + + LOG_DBG("Starting HTTP server"); + + return 0; +} + +int http_server_stop(void) +{ + if (!server_running) { + LOG_DBG("HTTP server already stopped"); + return -EALREADY; + } + + server_running = false; + k_sem_reset(&server_start); + eventfd_write(server_ctx.fds[0].fd, 1); + + LOG_DBG("Stopping HTTP server"); + + return 0; +} + +static void http_server_thread(void *p1, void *p2, void *p3) +{ + int ret; + + ARG_UNUSED(p1); + ARG_UNUSED(p2); + ARG_UNUSED(p3); + + while (true) { + k_sem_take(&server_start, K_FOREVER); + + while (server_running) { + ret = http_server_init(&server_ctx); + if (ret < 0) { + LOG_ERR("Failed to initialize HTTP2 server"); + return; + } + + ret = http_server_run(&server_ctx); + if (server_running) { + LOG_INF("Re-starting server (%d)", ret); + } + } + } +} + +K_THREAD_DEFINE(http_server_tid, CONFIG_HTTP_SERVER_STACK_SIZE, + http_server_thread, NULL, NULL, NULL, THREAD_PRIORITY, 0, 0); diff --git a/subsys/net/lib/http/http_server_http1.c b/subsys/net/lib/http/http_server_http1.c new file mode 100644 index 0000000000000..c7dff045d7483 --- /dev/null +++ b/subsys/net/lib/http/http_server_http1.c @@ -0,0 +1,491 @@ +/* + * Copyright (c) 2023, Emna Rekik + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +LOG_MODULE_DECLARE(net_http_server, CONFIG_NET_HTTP_SERVER_LOG_LEVEL); + +#include "headers/server_internal.h" + +#define TEMP_BUF_LEN 64 + +static const char final_chunk[] = "0\r\n\r\n"; +static const char *crlf = &final_chunk[3]; + +static int handle_http1_static_resource( + struct http_resource_detail_static *static_detail, + struct http_client_ctx *client) +{ +#define RESPONSE_TEMPLATE \ + "HTTP/1.1 200 OK\r\n" \ + "Content-Type: text/html\r\n" \ + "Content-Length: %d\r\n" + + /* Add couple of bytes to total response */ + char http_response[sizeof(RESPONSE_TEMPLATE) + + sizeof("Content-Encoding: 01234567890123456789\r\n") + + sizeof("xxxx") + + sizeof("\r\n")]; + const char *data; + int len; + int ret; + + if (static_detail->common.bitmask_of_supported_http_methods & BIT(HTTP_GET)) { + data = static_detail->static_data; + len = static_detail->static_data_len; + + if (static_detail->common.content_encoding != NULL && + static_detail->common.content_encoding[0] != '\0') { + snprintk(http_response, sizeof(http_response), + RESPONSE_TEMPLATE "Content-Encoding: %s\r\n\r\n", + len, static_detail->common.content_encoding); + } else { + snprintk(http_response, sizeof(http_response), + RESPONSE_TEMPLATE "\r\n", len); + } + + ret = http_server_sendall(client, http_response, + strlen(http_response)); + if (ret < 0) { + return ret; + } + + ret = http_server_sendall(client, data, len); + if (ret < 0) { + return ret; + } + } + + return 0; +} + +#define RESPONSE_TEMPLATE_CHUNKED \ + "HTTP/1.1 200 OK\r\n" \ + "Content-Type: text/html\r\n" \ + "Transfer-Encoding: chunked\r\n\r\n" + +#define RESPONSE_TEMPLATE_DYNAMIC \ + "HTTP/1.1 200 OK\r\n" \ + "Content-Type: text/html\r\n\r\n" \ + +static int dynamic_get_req(struct http_resource_detail_dynamic *dynamic_detail, + struct http_client_ctx *client) +{ + /* offset tells from where the GET params start */ + int ret, remaining, offset = dynamic_detail->common.path_len; + char *ptr; + char tmp[TEMP_BUF_LEN]; + + ret = http_server_sendall(client, RESPONSE_TEMPLATE_CHUNKED, + sizeof(RESPONSE_TEMPLATE_CHUNKED) - 1); + if (ret < 0) { + return ret; + } + + remaining = strlen(&client->url_buffer[dynamic_detail->common.path_len]); + + /* Pass URL to the client */ + while (1) { + int copy_len, send_len; + enum http_data_status status; + + ptr = &client->url_buffer[offset]; + copy_len = MIN(remaining, dynamic_detail->data_buffer_len); + + memcpy(dynamic_detail->data_buffer, ptr, copy_len); + + if (copy_len == remaining) { + status = HTTP_SERVER_DATA_FINAL; + } else { + status = HTTP_SERVER_DATA_MORE; + } + + send_len = dynamic_detail->cb(client, status, + dynamic_detail->data_buffer, + copy_len, dynamic_detail->user_data); + if (send_len > 0) { + ret = snprintk(tmp, sizeof(tmp), "%x\r\n", send_len); + ret = http_server_sendall(client, tmp, ret); + if (ret < 0) { + return ret; + } + + ret = http_server_sendall(client, + dynamic_detail->data_buffer, + send_len); + if (ret < 0) { + return ret; + } + + (void)http_server_sendall(client, crlf, 2); + + offset += copy_len; + remaining -= copy_len; + + continue; + } + + break; + } + + dynamic_detail->holder = NULL; + + ret = http_server_sendall(client, final_chunk, + sizeof(final_chunk) - 1); + if (ret < 0) { + return ret; + } + + return 0; +} + +static int dynamic_post_req(struct http_resource_detail_dynamic *dynamic_detail, + struct http_client_ctx *client) +{ + /* offset tells from where the POST params start */ + char *start = client->cursor; + int ret, remaining = client->data_len, offset = 0; + int copy_len; + char *ptr; + char tmp[TEMP_BUF_LEN]; + + if (start == NULL) { + return -ENOENT; + } + + if (!client->headers_sent) { + ret = http_server_sendall(client, RESPONSE_TEMPLATE_CHUNKED, + sizeof(RESPONSE_TEMPLATE_CHUNKED) - 1); + if (ret < 0) { + return ret; + } + client->headers_sent = true; + } + + copy_len = MIN(remaining, dynamic_detail->data_buffer_len); + while (copy_len > 0) { + enum http_data_status status; + int send_len; + + ptr = &start[offset]; + + memcpy(dynamic_detail->data_buffer, ptr, copy_len); + + if (copy_len == remaining && + client->parser_state == HTTP1_MESSAGE_COMPLETE_STATE) { + status = HTTP_SERVER_DATA_FINAL; + } else { + status = HTTP_SERVER_DATA_MORE; + } + + send_len = dynamic_detail->cb(client, status, + dynamic_detail->data_buffer, + copy_len, dynamic_detail->user_data); + if (send_len > 0) { + ret = snprintk(tmp, sizeof(tmp), "%x\r\n", send_len); + ret = http_server_sendall(client, tmp, ret); + if (ret < 0) { + return ret; + } + + ret = http_server_sendall(client, + dynamic_detail->data_buffer, + send_len); + if (ret < 0) { + return ret; + } + + (void)http_server_sendall(client, crlf, 2); + + offset += copy_len; + remaining -= copy_len; + } + + copy_len = MIN(remaining, dynamic_detail->data_buffer_len); + } + + if (client->parser_state == HTTP1_MESSAGE_COMPLETE_STATE) { + ret = http_server_sendall(client, final_chunk, + sizeof(final_chunk) - 1); + if (ret < 0) { + return ret; + } + + dynamic_detail->holder = NULL; + } + + return 0; +} + +static int handle_http1_dynamic_resource( + struct http_resource_detail_dynamic *dynamic_detail, + struct http_client_ctx *client) +{ + uint32_t user_method; + int ret; + + if (dynamic_detail->cb == NULL) { + return -ESRCH; + } + + user_method = dynamic_detail->common.bitmask_of_supported_http_methods; + + if (!(BIT(client->method) & user_method)) { + return -ENOPROTOOPT; + } + + if (dynamic_detail->holder != NULL && dynamic_detail->holder != client) { + static const char conflict_response[] = + "HTTP/1.1 409 Conflict\r\n\r\n"; + + ret = http_server_sendall(client, conflict_response, + sizeof(conflict_response) - 1); + if (ret < 0) { + LOG_DBG("Cannot write to socket (%d)", ret); + return ret; + } + + return enter_http_done_state(client); + } + + dynamic_detail->holder = client; + + switch (client->method) { + case HTTP_HEAD: + if (user_method & BIT(HTTP_HEAD)) { + ret = http_server_sendall( + client, RESPONSE_TEMPLATE_DYNAMIC, + sizeof(RESPONSE_TEMPLATE_DYNAMIC) - 1); + if (ret < 0) { + return ret; + } + + dynamic_detail->holder = NULL; + + return 0; + } + + case HTTP_GET: + /* For GET request, we do not pass any data to the app but let the app + * send data to the peer. + */ + if (user_method & BIT(HTTP_GET)) { + return dynamic_get_req(dynamic_detail, client); + } + + goto not_supported; + + case HTTP_POST: + if (user_method & BIT(HTTP_POST)) { + return dynamic_post_req(dynamic_detail, client); + } + + goto not_supported; + +not_supported: + default: + LOG_DBG("HTTP method %s (%d) not supported.", + http_method_str(client->method), + client->method); + + return -ENOTSUP; + } + + return 0; +} + +static int on_header_field(struct http_parser *parser, const char *at, + size_t length) +{ + struct http_client_ctx *ctx = CONTAINER_OF(parser, + struct http_client_ctx, + parser); + + ctx->parser_state = HTTP1_RECEIVING_HEADER_STATE; + + return 0; +} + +static int on_headers_complete(struct http_parser *parser) +{ + struct http_client_ctx *ctx = CONTAINER_OF(parser, + struct http_client_ctx, + parser); + + ctx->parser_state = HTTP1_RECEIVED_HEADER_STATE; + + return 0; +} + +static int on_url(struct http_parser *parser, const char *at, size_t length) +{ + struct http_client_ctx *ctx = CONTAINER_OF(parser, + struct http_client_ctx, + parser); + size_t offset = strlen(ctx->url_buffer); + + ctx->parser_state = HTTP1_WAITING_HEADER_STATE; + + if (offset + length > sizeof(ctx->url_buffer) - 1) { + LOG_DBG("URL too long to handle"); + return -EMSGSIZE; + } + + memcpy(ctx->url_buffer + offset, at, length); + offset += length; + ctx->url_buffer[offset] = '\0'; + + return 0; +} + +static int on_body(struct http_parser *parser, const char *at, size_t length) +{ + struct http_client_ctx *ctx = CONTAINER_OF(parser, + struct http_client_ctx, + parser); + + ctx->parser_state = HTTP1_RECEIVING_DATA_STATE; + + ctx->http1_frag_data_len += length; + + return 0; +} + +static int on_message_complete(struct http_parser *parser) +{ + struct http_client_ctx *ctx = CONTAINER_OF(parser, + struct http_client_ctx, + parser); + + ctx->parser_state = HTTP1_MESSAGE_COMPLETE_STATE; + + return 0; +} + +int enter_http1_request(struct http_client_ctx *client) +{ + client->server_state = HTTP_SERVER_REQUEST_STATE; + + http_parser_init(&client->parser, HTTP_REQUEST); + http_parser_settings_init(&client->parser_settings); + + client->parser_settings.on_header_field = on_header_field; + client->parser_settings.on_headers_complete = on_headers_complete; + client->parser_settings.on_url = on_url; + client->parser_settings.on_body = on_body; + client->parser_settings.on_message_complete = on_message_complete; + client->parser_state = HTTP1_INIT_HEADER_STATE; + + return 0; +} + +int handle_http1_request(struct http_client_ctx *client) +{ + int ret, path_len = 0; + struct http_resource_detail *detail; + bool skip_headers = (client->parser_state < HTTP1_RECEIVING_DATA_STATE); + size_t parsed; + + LOG_DBG("HTTP_SERVER_REQUEST"); + + client->http1_frag_data_len = 0; + + parsed = http_parser_execute(&client->parser, &client->parser_settings, + client->cursor, client->data_len); + + if (parsed > client->data_len) { + LOG_ERR("HTTP/1 parser error, too much data consumed"); + return -EBADMSG; + } + + if (client->parser.http_errno != HPE_OK) { + LOG_ERR("HTTP/1 parsing error, %d", client->parser.http_errno); + return -EBADMSG; + } + + if (client->parser_state < HTTP1_RECEIVED_HEADER_STATE) { + client->cursor += parsed; + client->data_len -= parsed; + + return 0; + } + + client->method = client->parser.method; + client->has_upgrade_header = client->parser.upgrade; + + if (skip_headers) { + LOG_DBG("Requested URL: %s", client->url_buffer); + + size_t frag_headers_len; + + if (parsed < client->http1_frag_data_len) { + return -EBADMSG; + } + + frag_headers_len = parsed - client->http1_frag_data_len; + parsed -= frag_headers_len; + + client->cursor += frag_headers_len; + client->data_len -= frag_headers_len; + } + + if (client->has_upgrade_header) { + return handle_http1_to_http2_upgrade(client); + } + + detail = get_resource_detail(client->url_buffer, &path_len); + if (detail != NULL) { + detail->path_len = path_len; + + if (detail->type == HTTP_RESOURCE_TYPE_STATIC) { + ret = handle_http1_static_resource( + (struct http_resource_detail_static *)detail, + client); + if (ret < 0) { + return ret; + } + } else if (detail->type == HTTP_RESOURCE_TYPE_DYNAMIC) { + ret = handle_http1_dynamic_resource( + (struct http_resource_detail_dynamic *)detail, + client); + if (ret < 0) { + return ret; + } + } + } else { + static const char not_found_response[] = + "HTTP/1.1 404 Not Found\r\n" + "Content-Length: 9\r\n\r\n" + "Not Found"; + + ret = http_server_sendall(client, not_found_response, + sizeof(not_found_response) - 1); + if (ret < 0) { + LOG_DBG("Cannot write to socket (%d)", ret); + return ret; + } + } + + client->cursor += parsed; + client->data_len -= parsed; + + if (client->parser_state == HTTP1_MESSAGE_COMPLETE_STATE) { + LOG_DBG("Connection closed client %p", client); + enter_http_done_state(client); + } + + return 0; +} diff --git a/subsys/net/lib/http/http_server_http2.c b/subsys/net/lib/http/http_server_http2.c new file mode 100644 index 0000000000000..b26139a79bb26 --- /dev/null +++ b/subsys/net/lib/http/http_server_http2.c @@ -0,0 +1,1243 @@ +/* + * Copyright (c) 2023, Emna Rekik + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +LOG_MODULE_DECLARE(net_http_server, CONFIG_NET_HTTP_SERVER_LOG_LEVEL); + +#include "headers/server_internal.h" + +static const char content_404[] = { +#ifdef INCLUDE_HTML_CONTENT +#include "not_found_page.html.gz.inc" +#endif +}; + +static bool settings_ack_flag(unsigned char flags) +{ + return (flags & HTTP_SERVER_FLAG_SETTINGS_ACK) != 0; +} + +/* Disabled for now to avoid warning, as temporarily not used. */ +static bool end_headers_flag(unsigned char flags) +{ + return (flags & HTTP_SERVER_FLAG_END_HEADERS) != 0; +} + +static bool end_stream_flag(unsigned char flags) +{ + return (flags & HTTP_SERVER_FLAG_END_STREAM) != 0; +} + +static void print_http_frames(struct http_client_ctx *client) +{ +#if defined(PRINT_COLOR) + const char *bold = "\033[1m"; + const char *reset = "\033[0m"; + const char *green = "\033[32m"; + const char *blue = "\033[34m"; +#else + const char *bold = ""; + const char *reset = ""; + const char *green = ""; + const char *blue = ""; +#endif + + struct http_frame *frame = &client->current_frame; + int payload_received_length; + + LOG_DBG("%s=====================================%s", green, reset); + LOG_DBG("%sReceived %s Frame :%s", bold, get_frame_type_name(frame->type), reset); + LOG_DBG(" %sLength:%s %u", blue, reset, frame->length); + LOG_DBG(" %sType:%s %u (%s)", blue, reset, frame->type, get_frame_type_name(frame->type)); + LOG_DBG(" %sFlags:%s %u", blue, reset, frame->flags); + LOG_DBG(" %sStream Identifier:%s %u", blue, reset, frame->stream_identifier); + + if (client->data_len > frame->length) { + payload_received_length = frame->length; + } else { + payload_received_length = client->data_len; + } + + LOG_HEXDUMP_DBG(frame->payload, payload_received_length, "Payload"); + LOG_DBG("%s=====================================%s", green, reset); +} + +static struct http_stream_ctx *find_http_stream_context( + struct http_client_ctx *client, uint32_t stream_id) +{ + ARRAY_FOR_EACH(client->streams, i) { + if (client->streams[i].stream_id == stream_id) { + return &client->streams[i]; + } + } + + return NULL; +} + +static struct http_stream_ctx *allocate_http_stream_context( + struct http_client_ctx *client, uint32_t stream_id) +{ + ARRAY_FOR_EACH(client->streams, i) { + if (client->streams[i].stream_state == HTTP_SERVER_STREAM_IDLE) { + client->streams[i].stream_id = stream_id; + client->streams[i].stream_state = HTTP_SERVER_STREAM_OPEN; + client->streams[i].window_size = + HTTP_SERVER_INITIAL_WINDOW_SIZE; + return &client->streams[i]; + } + } + + return NULL; +} + +static void release_http_stream_context(struct http_client_ctx *client, + uint32_t stream_id) +{ + ARRAY_FOR_EACH(client->streams, i) { + if (client->streams[i].stream_id == stream_id) { + client->streams[i].stream_id = 0; + client->streams[i].stream_state = HTTP_SERVER_STREAM_IDLE; + break; + } + } +} + +static int add_header_field(struct http_client_ctx *client, uint8_t **buf, + size_t *buflen, const char *name, const char *value) +{ + int ret; + + client->header_field.name = name; + client->header_field.name_len = strlen(name); + client->header_field.value = value; + client->header_field.value_len = strlen(value); + + ret = http_hpack_encode_header(*buf, *buflen, &client->header_field); + if (ret < 0) { + return ret; + } + + *buf += ret; + *buflen -= ret; + + return 0; +} + +static void encode_frame_header(uint8_t *buf, uint32_t payload_len, + enum http_frame_type frame_type, + uint8_t flags, uint32_t stream_id) +{ + sys_put_be24(payload_len, &buf[HTTP_SERVER_FRAME_LENGTH_OFFSET]); + buf[HTTP_SERVER_FRAME_TYPE_OFFSET] = frame_type; + buf[HTTP_SERVER_FRAME_FLAGS_OFFSET] = flags; + sys_put_be32(stream_id, &buf[HTTP_SERVER_FRAME_STREAM_ID_OFFSET]); +} + +static int send_headers_frame(struct http_client_ctx *client, + enum http_status status, uint32_t stream_id, + const char *content_encoding, uint8_t flags) +{ + uint8_t headers_frame[64]; + uint8_t status_str[4]; + uint8_t *buf = headers_frame + HTTP_SERVER_FRAME_HEADER_SIZE; + size_t buflen = sizeof(headers_frame) - HTTP_SERVER_FRAME_HEADER_SIZE; + size_t payload_len; + int ret; + + ret = snprintf(status_str, sizeof(status_str), "%d", status); + if (ret > sizeof(status_str) - 1) { + return -EINVAL; + } + + ret = add_header_field(client, &buf, &buflen, ":status", status_str); + if (ret < 0) { + return ret; + } + + if (content_encoding != NULL) { + ret = add_header_field(client, &buf, &buflen, "content-encoding", + "gzip"); + if (ret < 0) { + return ret; + } + } + + payload_len = sizeof(headers_frame) - buflen - HTTP_SERVER_FRAME_HEADER_SIZE; + flags |= HTTP_SERVER_FLAG_END_HEADERS; + + encode_frame_header(headers_frame, payload_len, HTTP_SERVER_HEADERS_FRAME, + flags, stream_id); + + ret = http_server_sendall(client, headers_frame, + payload_len + HTTP_SERVER_FRAME_HEADER_SIZE); + if (ret < 0) { + LOG_DBG("Cannot write to socket (%d)", ret); + return ret; + } + + return 0; +} + +static int send_data_frame(struct http_client_ctx *client, const char *payload, + size_t length, uint32_t stream_id, uint8_t flags) +{ + uint8_t frame_header[HTTP_SERVER_FRAME_HEADER_SIZE]; + int ret; + + encode_frame_header(frame_header, length, HTTP_SERVER_DATA_FRAME, + end_stream_flag(flags) ? + HTTP_SERVER_FLAG_END_STREAM : 0, + stream_id); + + ret = http_server_sendall(client, frame_header, sizeof(frame_header)); + if (ret < 0) { + LOG_DBG("Cannot write to socket (%d)", ret); + } else { + if (payload != NULL && length > 0) { + ret = http_server_sendall(client, payload, length); + if (ret < 0) { + LOG_DBG("Cannot write to socket (%d)", ret); + } + } + } + + return ret; +} + +int send_settings_frame(struct http_client_ctx *client, bool ack) +{ + uint8_t settings_frame[HTTP_SERVER_FRAME_HEADER_SIZE + + 2 * sizeof(struct http_settings_field)]; + struct http_settings_field *setting; + size_t len; + int ret; + + if (ack) { + encode_frame_header(settings_frame, 0, + HTTP_SERVER_SETTINGS_FRAME, + HTTP_SERVER_FLAG_SETTINGS_ACK, 0); + len = HTTP_SERVER_FRAME_HEADER_SIZE; + } else { + encode_frame_header(settings_frame, + 2 * sizeof(struct http_settings_field), + HTTP_SERVER_SETTINGS_FRAME, 0, 0); + + setting = (struct http_settings_field *) + (settings_frame + HTTP_SERVER_FRAME_HEADER_SIZE); + UNALIGNED_PUT(htons(HTTP_SETTINGS_HEADER_TABLE_SIZE), + &setting->id); + UNALIGNED_PUT(0, &setting->value); + + setting++; + UNALIGNED_PUT(htons(HTTP_SETTINGS_MAX_CONCURRENT_STREAMS), + &setting->id); + UNALIGNED_PUT(htonl(CONFIG_HTTP_SERVER_MAX_STREAMS), + &setting->value); + + len = HTTP_SERVER_FRAME_HEADER_SIZE + + 2 * sizeof(struct http_settings_field); + } + + ret = http_server_sendall(client, settings_frame, len); + if (ret < 0) { + LOG_DBG("Cannot write to socket (%d)", ret); + return ret; + } + + return 0; +} + +int send_window_update_frame(struct http_client_ctx *client, + struct http_stream_ctx *stream) +{ + uint8_t window_update_frame[HTTP_SERVER_FRAME_HEADER_SIZE + + sizeof(uint32_t)]; + uint32_t window_update; + uint32_t stream_id; + int ret; + + if (stream != NULL) { + window_update = HTTP_SERVER_INITIAL_WINDOW_SIZE - stream->window_size; + stream->window_size = HTTP_SERVER_INITIAL_WINDOW_SIZE; + stream_id = stream->stream_id; + } else { + window_update = HTTP_SERVER_INITIAL_WINDOW_SIZE - client->window_size; + client->window_size = HTTP_SERVER_INITIAL_WINDOW_SIZE; + stream_id = 0; + } + + encode_frame_header(window_update_frame, sizeof(uint32_t), + HTTP_SERVER_WINDOW_UPDATE_FRAME, + 0, stream_id); + sys_put_be32(window_update, + window_update_frame + HTTP_SERVER_FRAME_HEADER_SIZE); + + ret = http_server_sendall(client, window_update_frame, + sizeof(window_update_frame)); + if (ret < 0) { + LOG_DBG("Cannot write to socket (%d)", ret); + return ret; + } + + return 0; +} + +static int send_http2_404(struct http_client_ctx *client, + struct http_frame *frame) +{ + int ret; + + ret = send_headers_frame(client, HTTP_404_NOT_FOUND, + frame->stream_identifier, NULL, 0); + if (ret < 0) { + LOG_DBG("Cannot write to socket (%d)", ret); + return ret; + } + + ret = send_data_frame(client, content_404, sizeof(content_404), + frame->stream_identifier, + HTTP_SERVER_FLAG_END_STREAM); + if (ret < 0) { + LOG_DBG("Cannot write to socket (%d)", ret); + } + + return ret; +} + +static int send_http2_409(struct http_client_ctx *client, + struct http_frame *frame) +{ + int ret; + + ret = send_headers_frame(client, HTTP_409_CONFLICT, + frame->stream_identifier, NULL, + HTTP_SERVER_FLAG_END_STREAM); + if (ret < 0) { + LOG_DBG("Cannot write to socket (%d)", ret); + } + + return ret; +} + +static int handle_http2_static_resource( + struct http_resource_detail_static *static_detail, + struct http_frame *frame, struct http_client_ctx *client) +{ + const char *content_200; + size_t content_len; + int ret; + + if (!(static_detail->common.bitmask_of_supported_http_methods & BIT(HTTP_GET))) { + return -ENOTSUP; + } + + content_200 = static_detail->static_data; + content_len = static_detail->static_data_len; + + ret = send_headers_frame(client, HTTP_200_OK, frame->stream_identifier, + static_detail->common.content_encoding, 0); + if (ret < 0) { + LOG_DBG("Cannot write to socket (%d)", ret); + goto out; + } + + ret = send_data_frame(client, content_200, content_len, + frame->stream_identifier, + HTTP_SERVER_FLAG_END_STREAM); + if (ret < 0) { + LOG_DBG("Cannot write to socket (%d)", ret); + goto out; + } + +out: + return ret; +} + +static int dynamic_get_req_v2(struct http_resource_detail_dynamic *dynamic_detail, + struct http_client_ctx *client) +{ + struct http_frame *frame = &client->current_frame; + int ret, remaining, offset = dynamic_detail->common.path_len; + char *ptr; + + ret = send_headers_frame(client, HTTP_200_OK, frame->stream_identifier, + dynamic_detail->common.content_encoding, 0); + if (ret < 0) { + LOG_DBG("Cannot write to socket (%d)", ret); + return ret; + } + + remaining = strlen(&client->url_buffer[dynamic_detail->common.path_len]); + + /* Pass URL to the client */ + while (1) { + int copy_len, send_len; + enum http_data_status status; + + ptr = &client->url_buffer[offset]; + copy_len = MIN(remaining, dynamic_detail->data_buffer_len); + + if (copy_len > 0) { + memcpy(dynamic_detail->data_buffer, ptr, copy_len); + } + + if (copy_len == remaining) { + status = HTTP_SERVER_DATA_FINAL; + } else { + status = HTTP_SERVER_DATA_MORE; + } + + send_len = dynamic_detail->cb(client, status, + dynamic_detail->data_buffer, + copy_len, + dynamic_detail->user_data); + if (send_len > 0) { + ret = send_data_frame(client, + dynamic_detail->data_buffer, + send_len, + frame->stream_identifier, + 0); + if (ret < 0) { + break; + } + + offset += copy_len; + remaining -= copy_len; + + continue; + } + + ret = send_data_frame(client, NULL, 0, + frame->stream_identifier, + HTTP_SERVER_FLAG_END_STREAM); + if (ret < 0) { + LOG_DBG("Cannot send last frame (%d)", ret); + } + + dynamic_detail->holder = NULL; + + break; + } + + return ret; +} + +static int dynamic_post_req_v2(struct http_resource_detail_dynamic *dynamic_detail, + struct http_client_ctx *client) +{ + struct http_frame *frame = &client->current_frame; + size_t data_len; + int copy_len; + int ret = 0; + + if (dynamic_detail == NULL) { + return -ENOENT; + } + + data_len = MIN(frame->length, client->data_len); + copy_len = MIN(data_len, dynamic_detail->data_buffer_len); + + while (copy_len > 0) { + enum http_data_status status; + int send_len; + + /* Read all the user data and pass it to application. After + * passing all the data, if application returns 0, it means + * that there is no more data to send to client. + */ + memcpy(dynamic_detail->data_buffer, client->cursor, copy_len); + data_len -= copy_len; + client->cursor += copy_len; + client->data_len -= copy_len; + frame->length -= copy_len; + + if (frame->length == 0 && end_stream_flag(frame->flags)) { + status = HTTP_SERVER_DATA_FINAL; + } else { + status = HTTP_SERVER_DATA_MORE; + } + + send_len = dynamic_detail->cb(client, status, + dynamic_detail->data_buffer, + copy_len, + dynamic_detail->user_data); + if (send_len > 0) { + uint8_t flags = 0; + + if (!client->headers_sent) { + ret = send_headers_frame( + client, HTTP_200_OK, frame->stream_identifier, + dynamic_detail->common.content_encoding, 0); + if (ret < 0) { + LOG_DBG("Cannot write to socket (%d)", ret); + return ret; + } + + client->headers_sent = true; + } + + /* In case no more data is available, that was the last + * callback call, so we can include END_STREAM flag. + */ + if (frame->length == 0 && end_stream_flag(frame->flags)) { + flags = HTTP_SERVER_FLAG_END_STREAM; + } + + ret = send_data_frame(client, + dynamic_detail->data_buffer, + send_len, + frame->stream_identifier, + flags); + if (ret < 0) { + LOG_DBG("Cannot send data frame (%d)", ret); + return ret; + } + } + + copy_len = MIN(data_len, dynamic_detail->data_buffer_len); + }; + + if (frame->length == 0 && end_stream_flag(frame->flags)) { + if (!client->headers_sent) { + /* The callback did not report any data to send, therefore send + * headers frame now, including END_STREAM flag. + */ + ret = send_headers_frame( + client, HTTP_200_OK, frame->stream_identifier, + dynamic_detail->common.content_encoding, + HTTP_SERVER_FLAG_END_STREAM); + if (ret < 0) { + LOG_DBG("Cannot write to socket (%d)", ret); + return ret; + } + + client->headers_sent = true; + } + + dynamic_detail->holder = NULL; + } + + + return ret; +} + +static int handle_http2_dynamic_resource( + struct http_resource_detail_dynamic *dynamic_detail, + struct http_frame *frame, struct http_client_ctx *client) +{ + uint32_t user_method; + int ret; + + if (dynamic_detail->cb == NULL) { + return -ESRCH; + } + + user_method = dynamic_detail->common.bitmask_of_supported_http_methods; + + if (!(BIT(client->method) & user_method)) { + return -ENOPROTOOPT; + } + + if (dynamic_detail->holder != NULL && dynamic_detail->holder != client) { + ret = send_http2_409(client, frame); + if (ret < 0) { + return ret; + } + + return enter_http_done_state(client); + } + + dynamic_detail->holder = client; + + switch (client->method) { + case HTTP_GET: + if (user_method & BIT(HTTP_GET)) { + return dynamic_get_req_v2(dynamic_detail, client); + } + + goto not_supported; + + case HTTP_POST: + /* The data will come in DATA frames. Remember the detail ptr + * which needs to be known when passing data to application. + */ + if (user_method & BIT(HTTP_POST)) { + client->current_detail = + (struct http_resource_detail *)dynamic_detail; + break; + } + + goto not_supported; + +not_supported: + default: + LOG_DBG("HTTP method %s (%d) not supported.", + http_method_str(client->method), + client->method); + + return -ENOTSUP; + } + + return 0; +} + +int enter_http2_request(struct http_client_ctx *client) +{ + int ret; + + client->server_state = HTTP_SERVER_FRAME_HEADER_STATE; + client->data_len -= sizeof(HTTP2_PREFACE) - 1; + client->cursor += sizeof(HTTP2_PREFACE) - 1; + + /* HTTP/2 client preface received, send server preface + * (settings frame). + */ + if (!client->preface_sent) { + ret = send_settings_frame(client, false); + if (ret < 0) { + return ret; + } + + client->preface_sent = true; + } + + return 0; +} + +static int enter_http_frame_settings_state(struct http_client_ctx *client) +{ + client->server_state = HTTP_SERVER_FRAME_SETTINGS_STATE; + + return 0; +} + +static int enter_http_frame_data_state(struct http_client_ctx *client) +{ + struct http_frame *frame = &client->current_frame; + struct http_stream_ctx *stream; + + if (frame->stream_identifier == 0) { + LOG_DBG("Stream ID 0 is forbidden for data frames."); + return -EBADMSG; + } + + stream = find_http_stream_context(client, frame->stream_identifier); + if (stream == NULL) { + LOG_DBG("No stream context found for ID %d", + frame->stream_identifier); + return -EBADMSG; + } + + if (stream->stream_state != HTTP_SERVER_STREAM_OPEN && + stream->stream_state != HTTP_SERVER_STREAM_HALF_CLOSED_REMOTE) { + LOG_DBG("Stream ID %d in a wrong state %d", stream->stream_id, + stream->stream_state); + return -EBADMSG; + } + + stream->window_size -= frame->length; + client->window_size -= frame->length; + client->server_state = HTTP_SERVER_FRAME_DATA_STATE; + + return 0; +} + +static int enter_http_frame_headers_state(struct http_client_ctx *client) +{ + struct http_frame *frame = &client->current_frame; + struct http_stream_ctx *stream; + + stream = find_http_stream_context(client, frame->stream_identifier); + if (!stream) { + LOG_DBG("|| stream ID || %d", frame->stream_identifier); + + stream = allocate_http_stream_context(client, frame->stream_identifier); + if (!stream) { + LOG_DBG("No available stream slots. Connection closed."); + + return -ENOMEM; + } + } + + client->server_state = HTTP_SERVER_FRAME_HEADERS_STATE; + + return 0; +} + +static int enter_http_frame_continuation_state(struct http_client_ctx *client) +{ + client->server_state = HTTP_SERVER_FRAME_CONTINUATION_STATE; + + return 0; +} + +static int enter_http_frame_window_update_state(struct http_client_ctx *client) +{ + client->server_state = HTTP_SERVER_FRAME_WINDOW_UPDATE_STATE; + + return 0; +} + +static int enter_http_frame_priority_state(struct http_client_ctx *client) +{ + client->server_state = HTTP_SERVER_FRAME_PRIORITY_STATE; + + return 0; +} + +static int enter_http_frame_rst_stream_state(struct http_client_ctx *client) +{ + client->server_state = HTTP_SERVER_FRAME_RST_STREAM_STATE; + + return 0; +} + +static int enter_http_frame_goaway_state(struct http_client_ctx *client) +{ + client->server_state = HTTP_SERVER_FRAME_GOAWAY_STATE; + + return 0; +} + +int handle_http_frame_header(struct http_client_ctx *client) +{ + int bytes_consumed; + int parse_result; + + LOG_DBG("HTTP_SERVER_FRAME_HEADER"); + + parse_result = parse_http_frame_header(client); + if (parse_result == 0) { + return -EAGAIN; + } else if (parse_result < 0) { + return parse_result; + } + + bytes_consumed = HTTP_SERVER_FRAME_HEADER_SIZE; + + client->cursor += bytes_consumed; + client->data_len -= bytes_consumed; + + switch (client->current_frame.type) { + case HTTP_SERVER_DATA_FRAME: + return enter_http_frame_data_state(client); + case HTTP_SERVER_HEADERS_FRAME: + return enter_http_frame_headers_state(client); + case HTTP_SERVER_CONTINUATION_FRAME: + return enter_http_frame_continuation_state(client); + case HTTP_SERVER_SETTINGS_FRAME: + return enter_http_frame_settings_state(client); + case HTTP_SERVER_WINDOW_UPDATE_FRAME: + return enter_http_frame_window_update_state(client); + case HTTP_SERVER_RST_STREAM_FRAME: + return enter_http_frame_rst_stream_state(client); + case HTTP_SERVER_GOAWAY_FRAME: + return enter_http_frame_goaway_state(client); + case HTTP_SERVER_PRIORITY_FRAME: + return enter_http_frame_priority_state(client); + default: + return enter_http_done_state(client); + } + + return 0; +} + +/* This feature is theoretically obsoleted in RFC9113, but curl for instance + * still uses it, so implement as described in RFC7540. + */ +int handle_http1_to_http2_upgrade(struct http_client_ctx *client) +{ + static const char switching_protocols[] = + "HTTP/1.1 101 Switching Protocols\r\n" + "Connection: Upgrade\r\n" + "Upgrade: h2c\r\n" + "\r\n"; + struct http_frame *frame = &client->current_frame; + struct http_resource_detail *detail; + int path_len; + int ret; + + /* Create an artificial Data frame, so that we can proceed with HTTP2 + * processing. The HTTP/1.1 request that is sent prior to upgrade is + * assigned a stream identifier of 1. + */ + frame->stream_identifier = 1; + frame->type = HTTP_SERVER_DATA_FRAME; + frame->length = client->http1_frag_data_len; + if (client->parser_state == HTTP1_MESSAGE_COMPLETE_STATE) { + frame->flags = HTTP_SERVER_FLAG_END_STREAM; + } else { + frame->flags = 0; + } + + if (!client->preface_sent) { + ret = http_server_sendall(client, switching_protocols, + sizeof(switching_protocols) - 1); + if (ret < 0) { + goto error; + } + + /* The first HTTP/2 frame sent by the server MUST be a server connection + * preface. + */ + ret = send_settings_frame(client, false); + if (ret < 0) { + goto error; + } + + client->preface_sent = true; + } + + detail = get_resource_detail(client->url_buffer, &path_len); + if (detail != NULL) { + detail->path_len = path_len; + + if (detail->type == HTTP_RESOURCE_TYPE_STATIC) { + ret = handle_http2_static_resource( + (struct http_resource_detail_static *)detail, + frame, client); + if (ret < 0) { + goto error; + } + } else if (detail->type == HTTP_RESOURCE_TYPE_DYNAMIC) { + ret = handle_http2_dynamic_resource( + (struct http_resource_detail_dynamic *)detail, + frame, client); + if (ret < 0) { + goto error; + } + + if (client->method == HTTP_POST) { + ret = dynamic_post_req_v2( + (struct http_resource_detail_dynamic *)detail, + client); + if (ret < 0) { + goto error; + } + } + + } + } else { + ret = send_http2_404(client, frame); + if (ret < 0) { + goto error; + } + } + + /* Only after the complete HTTP1 payload has been processed, switch + * to HTTP2. + */ + if (client->parser_state == HTTP1_MESSAGE_COMPLETE_STATE) { + client->current_detail = NULL; + client->server_state = HTTP_SERVER_PREFACE_STATE; + client->cursor += client->data_len; + client->data_len = 0; + } + + return 0; + +error: + return ret; +} + +int handle_http_frame_data(struct http_client_ctx *client) +{ + struct http_frame *frame = &client->current_frame; + int ret; + + LOG_DBG("HTTP_SERVER_FRAME_DATA_STATE"); + + print_http_frames(client); + + if (client->current_detail == NULL) { + /* There is no handler */ + LOG_DBG("No dynamic handler found."); + (void)send_http2_404(client, frame); + return -ENOENT; + } + + ret = dynamic_post_req_v2( + (struct http_resource_detail_dynamic *)client->current_detail, + client); + if (ret < 0 && ret == -ENOENT) { + ret = send_http2_404(client, frame); + } + + if (ret < 0) { + return ret; + } + + if (frame->length == 0) { + struct http_stream_ctx *stream = + find_http_stream_context(client, frame->stream_identifier); + + if (stream == NULL) { + LOG_DBG("No stream context found for ID %d", + frame->stream_identifier); + return -EBADMSG; + } + + ret = send_window_update_frame(client, stream); + if (ret < 0) { + return ret; + } + + ret = send_window_update_frame(client, NULL); + if (ret < 0) { + return ret; + } + + /* Whole frame consumed, expect next one. */ + client->server_state = HTTP_SERVER_FRAME_HEADER_STATE; + + if (end_stream_flag(frame->flags)) { + client->current_detail = NULL; + release_http_stream_context(client, frame->stream_identifier); + } + } + + return 0; +} + +static int process_header(struct http_client_ctx *client, + struct http_hpack_header_buf *header) +{ + if (header->name_len == (sizeof(":method") - 1) && + memcmp(header->name, ":method", header->name_len) == 0) { + /* TODO Improve string to method conversion */ + if (header->value_len == (sizeof("GET") - 1) && + memcmp(header->value, "GET", header->value_len) == 0) { + client->method = HTTP_GET; + } else if (header->value_len == (sizeof("POST") - 1) && + memcmp(header->value, "POST", header->value_len) == 0) { + client->method = HTTP_POST; + } else { + /* Unknown method */ + return -EBADMSG; + } + } else if (header->name_len == (sizeof(":path") - 1) && + memcmp(header->name, ":path", header->name_len) == 0) { + if (header->value_len > sizeof(client->url_buffer) - 1) { + /* URL too long to handle */ + return -ENOBUFS; + } + + memcpy(client->url_buffer, header->value, header->value_len); + client->url_buffer[header->value_len] = '\0'; + } else if (header->name_len == (sizeof("content-type") - 1) && + memcmp(header->name, "content-type", header->name_len) == 0) { + if (header->value_len > sizeof(client->content_type) - 1) { + /* Content-type too long to handle */ + return -ENOBUFS; + } + + memcpy(client->content_type, header->value, header->value_len); + client->content_type[header->value_len] = '\0'; + } else if (header->name_len == (sizeof("content-length") - 1) && + memcmp(header->name, "content-length", header->name_len) == 0) { + char len_str[16] = { 0 }; + char *endptr; + unsigned long len; + + memcpy(len_str, header->value, MIN(sizeof(len_str), header->value_len)); + len_str[sizeof(len_str) - 1] = '\0'; + + len = strtoul(len_str, &endptr, 10); + if (*endptr != '\0') { + return -EINVAL; + } + + client->content_len = (size_t)len; + } else { + /* Just ignore for now. */ + LOG_DBG("Ignoring field %.*s", (int)header->name_len, header->name); + } + + return 0; +} + +int handle_http_frame_headers(struct http_client_ctx *client) +{ + struct http_frame *frame = &client->current_frame; + struct http_resource_detail *detail; + int ret, path_len; + + LOG_DBG("HTTP_SERVER_FRAME_HEADERS"); + + print_http_frames(client); + + while (frame->length > 0) { + struct http_hpack_header_buf *header = &client->header_field; + + ret = http_hpack_decode_header(client->cursor, client->data_len, + header); + if (ret <= 0) { + ret = (ret == 0) ? -EBADMSG : ret; + return ret; + } + + if (ret > frame->length) { + LOG_ERR("Protocol error, frame length exceeded"); + return -EBADMSG; + } + + frame->length -= ret; + client->cursor += ret; + client->data_len -= ret; + + LOG_DBG("Parsed header: %.*s %.*s", (int)header->name_len, + header->name, (int)header->value_len, header->value); + + ret = process_header(client, header); + if (ret < 0) { + return ret; + } + } + + if (!end_headers_flag(frame->flags)) { + /* More headers to come in the continuation frame. */ + client->server_state = HTTP_SERVER_FRAME_HEADER_STATE; + + /* TODO Implement continuation frame processing. */ + + return 0; + } + + detail = get_resource_detail(client->url_buffer, &path_len); + if (detail != NULL) { + detail->path_len = path_len; + + if (detail->type == HTTP_RESOURCE_TYPE_STATIC) { + ret = handle_http2_static_resource( + (struct http_resource_detail_static *)detail, + frame, client); + if (ret < 0) { + return ret; + } + } else if (detail->type == HTTP_RESOURCE_TYPE_DYNAMIC) { + ret = handle_http2_dynamic_resource( + (struct http_resource_detail_dynamic *)detail, + frame, client); + if (ret < 0) { + return ret; + } + } + + } else { + ret = send_http2_404(client, frame); + if (ret < 0) { + return ret; + } + } + + if (end_stream_flag(frame->flags)) { + release_http_stream_context(client, frame->stream_identifier); + } + + client->server_state = HTTP_SERVER_FRAME_HEADER_STATE; + + return 0; +} + +int handle_http_frame_priority(struct http_client_ctx *client) +{ + struct http_frame *frame = &client->current_frame; + int bytes_consumed; + + LOG_DBG("HTTP_SERVER_FRAME_PRIORITY_STATE"); + + print_http_frames(client); + + if (client->data_len < frame->length) { + return -EAGAIN; + } + + bytes_consumed = client->current_frame.length; + client->data_len -= bytes_consumed; + client->cursor += bytes_consumed; + + client->server_state = HTTP_SERVER_FRAME_HEADER_STATE; + + return 0; +} + +int handle_http_frame_rst_frame(struct http_client_ctx *client) +{ + struct http_frame *frame = &client->current_frame; + int bytes_consumed; + + LOG_DBG("FRAME_RST_STREAM"); + + print_http_frames(client); + + if (client->data_len < frame->length) { + return -EAGAIN; + } + + bytes_consumed = client->current_frame.length; + client->data_len -= bytes_consumed; + client->cursor += bytes_consumed; + + client->server_state = HTTP_SERVER_FRAME_HEADER_STATE; + + return 0; +} + +int handle_http_frame_settings(struct http_client_ctx *client) +{ + struct http_frame *frame = &client->current_frame; + int bytes_consumed; + + LOG_DBG("HTTP_SERVER_FRAME_SETTINGS"); + + print_http_frames(client); + + if (client->data_len < frame->length) { + return -EAGAIN; + } + + bytes_consumed = client->current_frame.length; + client->data_len -= bytes_consumed; + client->cursor += bytes_consumed; + + if (!settings_ack_flag(frame->flags)) { + int ret; + + ret = send_settings_frame(client, true); + if (ret < 0) { + LOG_DBG("Cannot write to socket (%d)", ret); + return ret; + } + } + + client->server_state = HTTP_SERVER_FRAME_HEADER_STATE; + + return 0; +} + +int handle_http_frame_goaway(struct http_client_ctx *client) +{ + struct http_frame *frame = &client->current_frame; + int bytes_consumed; + + LOG_DBG("HTTP_SERVER_FRAME_GOAWAY"); + + print_http_frames(client); + + if (client->data_len < frame->length) { + return -EAGAIN; + } + + bytes_consumed = client->current_frame.length; + client->data_len -= bytes_consumed; + client->cursor += bytes_consumed; + + enter_http_done_state(client); + + return 0; +} + +int handle_http_frame_window_update(struct http_client_ctx *client) +{ + struct http_frame *frame = &client->current_frame; + int bytes_consumed; + + LOG_DBG("HTTP_SERVER_FRAME_WINDOW_UPDATE"); + + print_http_frames(client); + + /* TODO Implement flow control, for now just ignore. */ + + if (client->data_len < frame->length) { + return -EAGAIN; + } + + bytes_consumed = client->current_frame.length; + client->data_len -= bytes_consumed; + client->cursor += bytes_consumed; + + client->server_state = HTTP_SERVER_FRAME_HEADER_STATE; + + return 0; +} + +int handle_http_frame_continuation(struct http_client_ctx *client) +{ + LOG_DBG("HTTP_SERVER_FRAME_CONTINUATION_STATE"); + client->server_state = HTTP_SERVER_FRAME_HEADERS_STATE; + + return 0; +} + +const char *get_frame_type_name(enum http_frame_type type) +{ + switch (type) { + case HTTP_SERVER_DATA_FRAME: + return "DATA"; + case HTTP_SERVER_HEADERS_FRAME: + return "HEADERS"; + case HTTP_SERVER_PRIORITY_FRAME: + return "PRIORITY"; + case HTTP_SERVER_RST_STREAM_FRAME: + return "RST_STREAM"; + case HTTP_SERVER_SETTINGS_FRAME: + return "SETTINGS"; + case HTTP_SERVER_PUSH_PROMISE_FRAME: + return "PUSH_PROMISE"; + case HTTP_SERVER_PING_FRAME: + return "PING"; + case HTTP_SERVER_GOAWAY_FRAME: + return "GOAWAY"; + case HTTP_SERVER_WINDOW_UPDATE_FRAME: + return "WINDOW_UPDATE"; + case HTTP_SERVER_CONTINUATION_FRAME: + return "CONTINUATION"; + default: + return "UNKNOWN"; + } +} + +int parse_http_frame_header(struct http_client_ctx *client) +{ + unsigned char *buffer = client->cursor; + unsigned long buffer_len = client->data_len; + struct http_frame *frame = &client->current_frame; + + frame->length = 0; + frame->stream_identifier = 0; + + if (buffer_len < HTTP_SERVER_FRAME_HEADER_SIZE) { + return 0; + } + + frame->length = (buffer[HTTP_SERVER_FRAME_LENGTH_OFFSET] << 16) | + (buffer[HTTP_SERVER_FRAME_LENGTH_OFFSET + 1] << 8) | + buffer[HTTP_SERVER_FRAME_LENGTH_OFFSET + 2]; + frame->type = buffer[HTTP_SERVER_FRAME_TYPE_OFFSET]; + frame->flags = buffer[HTTP_SERVER_FRAME_FLAGS_OFFSET]; + frame->stream_identifier = (buffer[HTTP_SERVER_FRAME_STREAM_ID_OFFSET] << 24) | + (buffer[HTTP_SERVER_FRAME_STREAM_ID_OFFSET + 1] << 16) | + (buffer[HTTP_SERVER_FRAME_STREAM_ID_OFFSET + 2] << 8) | + buffer[HTTP_SERVER_FRAME_STREAM_ID_OFFSET + 3]; + frame->stream_identifier &= 0x7FFFFFFF; + frame->payload = buffer + HTTP_SERVER_FRAME_HEADER_SIZE; + + LOG_DBG("Frame len %d type 0x%02x flags 0x%02x id %d", + frame->length, frame->type, frame->flags, frame->stream_identifier); + + return 1; +} From 620f5a2e20dbb87f0caf2c6ddc700e359a0b9ca1 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Thu, 9 Nov 2023 20:13:41 +0200 Subject: [PATCH 4/5] tests: net: http_server: Add tests for the HTTP server Tests for HTTP server support. Signed-off-by: Emna Rekik Signed-off-by: Jukka Rissanen Signed-off-by: Robert Lubos --- tests/net/lib/http_server/common/prj.conf | 5 + .../net/lib/http_server/common/testcase.yaml | 4 +- .../net/lib/http_server/crime/CMakeLists.txt | 24 ++ tests/net/lib/http_server/crime/prj.conf | 54 +++ .../net/lib/http_server/crime/sections-rom.ld | 3 + .../net/lib/http_server/crime/src/index.html | 10 + tests/net/lib/http_server/crime/src/main.c | 139 ++++++ .../http_server/crime/src/not_found_page.html | 10 + tests/net/lib/http_server/crime/testcase.yaml | 16 + .../lib/http_server/prototype/CMakeLists.txt | 18 + tests/net/lib/http_server/prototype/prj.conf | 53 +++ .../lib/http_server/prototype/sections-rom.ld | 3 + .../net/lib/http_server/prototype/src/main.c | 402 ++++++++++++++++++ .../lib/http_server/prototype/testcase.yaml | 16 + tests/net/lib/http_server/tls/CMakeLists.txt | 54 +++ tests/net/lib/http_server/tls/prj.conf | 61 +++ tests/net/lib/http_server/tls/sections-rom.ld | 3 + tests/net/lib/http_server/tls/src/index.html | 10 + tests/net/lib/http_server/tls/src/main.c | 265 ++++++++++++ tests/net/lib/http_server/tls/testcase.yaml | 19 + 20 files changed, 1168 insertions(+), 1 deletion(-) create mode 100644 tests/net/lib/http_server/crime/CMakeLists.txt create mode 100644 tests/net/lib/http_server/crime/prj.conf create mode 100644 tests/net/lib/http_server/crime/sections-rom.ld create mode 100644 tests/net/lib/http_server/crime/src/index.html create mode 100644 tests/net/lib/http_server/crime/src/main.c create mode 100644 tests/net/lib/http_server/crime/src/not_found_page.html create mode 100644 tests/net/lib/http_server/crime/testcase.yaml create mode 100644 tests/net/lib/http_server/prototype/CMakeLists.txt create mode 100644 tests/net/lib/http_server/prototype/prj.conf create mode 100644 tests/net/lib/http_server/prototype/sections-rom.ld create mode 100644 tests/net/lib/http_server/prototype/src/main.c create mode 100644 tests/net/lib/http_server/prototype/testcase.yaml create mode 100644 tests/net/lib/http_server/tls/CMakeLists.txt create mode 100644 tests/net/lib/http_server/tls/prj.conf create mode 100644 tests/net/lib/http_server/tls/sections-rom.ld create mode 100644 tests/net/lib/http_server/tls/src/index.html create mode 100644 tests/net/lib/http_server/tls/src/main.c create mode 100644 tests/net/lib/http_server/tls/testcase.yaml diff --git a/tests/net/lib/http_server/common/prj.conf b/tests/net/lib/http_server/common/prj.conf index 7fa5d3d6dd051..626a805048245 100644 --- a/tests/net/lib/http_server/common/prj.conf +++ b/tests/net/lib/http_server/common/prj.conf @@ -7,3 +7,8 @@ CONFIG_TEST_RANDOM_GENERATOR=y CONFIG_ZTEST_STACK_SIZE=1024 CONFIG_HTTP_SERVER=y +CONFIG_EVENTFD=y +CONFIG_POSIX_API=y + +# Networking config +CONFIG_NET_SOCKETS=y diff --git a/tests/net/lib/http_server/common/testcase.yaml b/tests/net/lib/http_server/common/testcase.yaml index b86f2dc0c9574..11d720068cc56 100644 --- a/tests/net/lib/http_server/common/testcase.yaml +++ b/tests/net/lib/http_server/common/testcase.yaml @@ -6,6 +6,8 @@ common: - server integration_platforms: - native_sim - + platform_exclude: + - native_posix + - native_posix/native/64 tests: net.http.server.common: {} diff --git a/tests/net/lib/http_server/crime/CMakeLists.txt b/tests/net/lib/http_server/crime/CMakeLists.txt new file mode 100644 index 0000000000000..9ccfbe570b384 --- /dev/null +++ b/tests/net/lib/http_server/crime/CMakeLists.txt @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(crime) + +set(BASE_PATH "../../../../../subsys/net/lib/http/") +include_directories(${BASE_PATH}/headers) + +FILE(GLOB app_sources src/main.c) +target_sources(app PRIVATE ${app_sources}) + +set(gen_dir ${ZEPHYR_BINARY_DIR}/include/generated/) + +set(source_file_index src/index.html) +generate_inc_file_for_target(app ${source_file_index} ${gen_dir}/index.html.inc) +generate_inc_file_for_target(app ${source_file_index} ${gen_dir}/index.html.gz.inc --gzip) + +set(source_file_not_found src/not_found_page.html) +generate_inc_file_for_target(app ${source_file_not_found} ${gen_dir}/not_found_page.html.inc) +generate_inc_file_for_target(app ${source_file_not_found} ${gen_dir}/not_found_page.html.gz.inc --gzip) + +zephyr_linker_sources(SECTIONS sections-rom.ld) +zephyr_iterable_section(NAME http_resource_desc_test_http_service KVMA RAM_REGION GROUP RODATA_REGION SUBALIGN 4) diff --git a/tests/net/lib/http_server/crime/prj.conf b/tests/net/lib/http_server/crime/prj.conf new file mode 100644 index 0000000000000..0596a54e47c77 --- /dev/null +++ b/tests/net/lib/http_server/crime/prj.conf @@ -0,0 +1,54 @@ +CONFIG_ZTEST=y +CONFIG_NET_TEST=y + +# Eventfd +CONFIG_EVENTFD=y +CONFIG_POSIX_API=y + +CONFIG_ENTROPY_GENERATOR=y +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_ZTEST_STACK_SIZE=1024 + +CONFIG_POSIX_MAX_FDS=10 +CONFIG_REQUIRES_FULL_LIBC=y +CONFIG_EVENTFD_MAX=10 +CONFIG_NET_MAX_CONTEXTS=10 +CONFIG_NET_MAX_CONN=10 + +# Networking config +CONFIG_NETWORKING=y +CONFIG_NET_IPV4=y +CONFIG_NET_IPV6=y +CONFIG_NET_TCP=y +CONFIG_NET_SOCKETS=y +CONFIG_NET_LOOPBACK=y +CONFIG_NET_LOOPBACK_MTU=1280 +CONFIG_NET_DRIVERS=y +CONFIG_NET_SOCKETS_POLL_MAX=8 +CONFIG_NET_BUF_RX_COUNT=32 +CONFIG_NET_BUF_TX_COUNT=32 +CONFIG_NET_PKT_RX_COUNT=16 +CONFIG_NET_PKT_TX_COUNT=16 + +# Reduce the retry count, so the close always finishes within a second +CONFIG_NET_TCP_RETRY_COUNT=3 +CONFIG_NET_TCP_INIT_RETRANSMISSION_TIMEOUT=120 + +# JSON +CONFIG_JSON_LIBRARY=y + +# HTTP parser +CONFIG_HTTP_PARSER_URL=y +CONFIG_HTTP_PARSER=y +CONFIG_HTTP_SERVER=y + +CONFIG_HTTP_SERVER_MAX_CLIENTS=5 +CONFIG_HTTP_SERVER_MAX_STREAMS=5 + +# Network address config +CONFIG_NET_CONFIG_SETTINGS=n + +CONFIG_MAIN_STACK_SIZE=2048 + +# Network debug config +CONFIG_NET_LOG=y diff --git a/tests/net/lib/http_server/crime/sections-rom.ld b/tests/net/lib/http_server/crime/sections-rom.ld new file mode 100644 index 0000000000000..512251641b2ad --- /dev/null +++ b/tests/net/lib/http_server/crime/sections-rom.ld @@ -0,0 +1,3 @@ +#include + +ITERABLE_SECTION_ROM(http_resource_desc_test_http_service, 4) diff --git a/tests/net/lib/http_server/crime/src/index.html b/tests/net/lib/http_server/crime/src/index.html new file mode 100644 index 0000000000000..7f82a1e3975b9 --- /dev/null +++ b/tests/net/lib/http_server/crime/src/index.html @@ -0,0 +1,10 @@ + + + + My Simple Server + + +

Welcome to my simple server!

+

This is a simple HTML file.

+ + diff --git a/tests/net/lib/http_server/crime/src/main.c b/tests/net/lib/http_server/crime/src/main.c new file mode 100644 index 0000000000000..992d64eff56a4 --- /dev/null +++ b/tests/net/lib/http_server/crime/src/main.c @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2023, Emna Rekik + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "server_internal.h" + +#include + +#include +#include +#include + +#define BUFFER_SIZE 256 +#define MY_IPV4_ADDR "127.0.0.1" +#define SERVER_PORT 8080 +#define TIMEOUT 1000 + +static const unsigned char index_html_gz[] = { +#include "index.html.gz.inc" +}; + +static const unsigned char compressed_inc_file[] = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0xff, 0x35, 0x8e, 0xc1, 0x0a, 0xc2, 0x30, + 0x0c, 0x86, 0xef, 0x7d, 0x8a, 0xec, 0x05, 0x2c, + 0xbb, 0x87, 0x5c, 0x54, 0xf0, 0xe0, 0x50, 0x58, + 0x41, 0x3c, 0x4e, 0x17, 0x69, 0x21, 0xa5, 0x65, + 0x2d, 0x42, 0xdf, 0xde, 0xba, 0x6e, 0x21, 0x10, + 0xf8, 0xf9, 0xbe, 0x9f, 0x60, 0x77, 0xba, 0x1d, + 0xcd, 0xf3, 0x7e, 0x06, 0x9b, 0xbd, 0x90, 0xc2, + 0xfd, 0xf0, 0x34, 0x93, 0x82, 0x3a, 0x98, 0x5d, + 0x16, 0xa6, 0xa1, 0xc0, 0xe8, 0x7c, 0x14, 0x86, + 0x91, 0x97, 0x2f, 0x2f, 0xa8, 0x5b, 0xae, 0x50, + 0x37, 0x16, 0x5f, 0x61, 0x2e, 0x9b, 0x62, 0x7b, + 0x7a, 0xb0, 0xbc, 0x83, 0x67, 0xc8, 0x01, 0x7c, + 0x81, 0xd4, 0xd4, 0xb4, 0xaa, 0x5d, 0x55, 0xfa, + 0x8d, 0x8c, 0x64, 0xac, 0x4b, 0x50, 0x77, 0xda, + 0xa1, 0x8b, 0x19, 0xae, 0xf0, 0x71, 0xc2, 0x07, + 0xd4, 0xf1, 0xdf, 0xdf, 0x8a, 0xab, 0xb4, 0xbe, + 0xf6, 0x03, 0xea, 0x2d, 0x11, 0x5c, 0xb2, 0x00, + 0x00, 0x00, +}; + +static uint16_t test_http_service_port = SERVER_PORT; +HTTP_SERVICE_DEFINE(test_http_service, MY_IPV4_ADDR, + &test_http_service_port, 1, + 10, NULL); + +struct http_resource_detail_static index_html_gz_resource_detail = { + .common = { + .type = HTTP_RESOURCE_TYPE_STATIC, + .bitmask_of_supported_http_methods = BIT(HTTP_GET), + }, + .static_data = index_html_gz, + .static_data_len = sizeof(index_html_gz), +}; + +HTTP_RESOURCE_DEFINE(index_html_gz_resource, test_http_service, "/", + &index_html_gz_resource_detail); + +static void test_crime(void) +{ + int ret, recv_len; + int client_fd; + int proto = IPPROTO_TCP; + char *ptr; + const char *data; + size_t len; + struct sockaddr_in sa; + static unsigned char buf[512]; + + zassert_ok(http_server_start(), "Failed to start the server"); + + ret = zsock_socket(AF_INET, SOCK_STREAM, proto); + zassert_not_equal(ret, -1, "failed to create client socket (%d)", errno); + client_fd = ret; + + sa.sin_family = AF_INET; + sa.sin_port = htons(SERVER_PORT); + + ret = zsock_inet_pton(AF_INET, MY_IPV4_ADDR, &sa.sin_addr.s_addr); + zassert_not_equal(-1, ret, "inet_pton() failed (%d)", errno); + zassert_not_equal(0, ret, "%s is not a valid IPv4 address", MY_IPV4_ADDR); + zassert_equal(1, ret, "inet_pton() failed to convert %s", MY_IPV4_ADDR); + + memset(buf, '\0', sizeof(buf)); + ptr = (char *)zsock_inet_ntop(AF_INET, &sa.sin_addr, buf, sizeof(buf)); + zassert_not_equal(ptr, NULL, "inet_ntop() failed (%d)", errno); + + ret = zsock_connect(client_fd, (struct sockaddr *)&sa, sizeof(sa)); + zassert_not_equal(ret, -1, "failed to connect (%s/%d)", strerror(errno), errno); + + char *http1_request = "GET / HTTP/1.1\r\n" + "Host: 127.0.0.1:8080\r\n" + "Accept: */*\r\n" + "Accept-Encoding: deflate, gzip, br\r\n" + "\r\n"; + + ret = zsock_send(client_fd, http1_request, strlen(http1_request), 0); + zassert_not_equal(ret, -1, "send() failed (%d)", errno); + + memset(buf, 0, sizeof(buf)); + recv_len = zsock_recv(client_fd, buf, sizeof(buf), 0); + zassert_not_equal(recv_len, -1, "recv() failed (%d)", errno); + + len = sizeof(index_html_gz); + + while (recv_len < len) { + ret = zsock_recv(client_fd, buf + recv_len, sizeof(buf) - recv_len, 0); + zassert_not_equal(ret, -1, "recv() failed (%d)", errno); + + recv_len += ret; + } + + data = strstr(buf, "\r\n\r\n"); + zassert_not_null(data, "Header not found"); + + data += 4; + + zassert_equal(len, sizeof(compressed_inc_file), "Invalid compressed file size"); + + ret = memcmp(data, compressed_inc_file, len); + zassert_equal(ret, 0, + "inc_file and compressed_inc_file contents are not identical (%d)", ret); + + ret = zsock_close(client_fd); + zassert_not_equal(-1, ret, "close() failed on the client fd (%d)", errno); + + zassert_ok(http_server_stop(), "Failed to stop the server"); +} + +ZTEST(framework_tests_crime, test_gen_gz_inc_file) +{ + test_crime(); +} + +ZTEST_SUITE(framework_tests_crime, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/net/lib/http_server/crime/src/not_found_page.html b/tests/net/lib/http_server/crime/src/not_found_page.html new file mode 100644 index 0000000000000..c4bf66f08ee1e --- /dev/null +++ b/tests/net/lib/http_server/crime/src/not_found_page.html @@ -0,0 +1,10 @@ + + + + 404 Not Found + + +

404 Not Found

+

The requested resource was not found.

+ + diff --git a/tests/net/lib/http_server/crime/testcase.yaml b/tests/net/lib/http_server/crime/testcase.yaml new file mode 100644 index 0000000000000..2c75b4f1ca18b --- /dev/null +++ b/tests/net/lib/http_server/crime/testcase.yaml @@ -0,0 +1,16 @@ +common: + harness: net + min_ram: 16 + tags: + - http + - net + - server + - socket + integration_platforms: + - native_sim + - qemu_x86 + platform_exclude: + - native_posix + - native_posix/native/64 +tests: + net.http.server.crime: {} diff --git a/tests/net/lib/http_server/prototype/CMakeLists.txt b/tests/net/lib/http_server/prototype/CMakeLists.txt new file mode 100644 index 0000000000000..4842b30949079 --- /dev/null +++ b/tests/net/lib/http_server/prototype/CMakeLists.txt @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(prototype) + +set(BASE_PATH "../../../../../subsys/net/lib/http/") +include_directories(${BASE_PATH}/headers) + +FILE(GLOB app_sources src/main.c) +target_sources(app PRIVATE ${app_sources}) + +set(gen_dir ${ZEPHYR_BINARY_DIR}/include/generated/) + +target_link_libraries(app PRIVATE zephyr_interface zephyr) + +zephyr_linker_sources(SECTIONS sections-rom.ld) +zephyr_iterable_section(NAME http_resource_desc_test_http_service KVMA RAM_REGION GROUP RODATA_REGION SUBALIGN 4) diff --git a/tests/net/lib/http_server/prototype/prj.conf b/tests/net/lib/http_server/prototype/prj.conf new file mode 100644 index 0000000000000..20570610ef79d --- /dev/null +++ b/tests/net/lib/http_server/prototype/prj.conf @@ -0,0 +1,53 @@ +CONFIG_ZTEST=y +CONFIG_NET_TEST=y + +# Eventfd +CONFIG_EVENTFD=y +CONFIG_POSIX_API=y + +CONFIG_ENTROPY_GENERATOR=y +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_POSIX_MAX_FDS=10 +CONFIG_REQUIRES_FULL_LIBC=y +CONFIG_EVENTFD_MAX=10 +CONFIG_NET_MAX_CONTEXTS=10 +CONFIG_NET_MAX_CONN=10 + +# Networking config +CONFIG_NETWORKING=y +CONFIG_NET_IPV4=y +CONFIG_NET_IPV6=y +CONFIG_NET_TCP=y +CONFIG_NET_SOCKETS=y +CONFIG_NET_LOOPBACK=y +CONFIG_NET_LOOPBACK_MTU=1280 +CONFIG_NET_DRIVERS=y +CONFIG_NET_SOCKETS_POLL_MAX=8 +CONFIG_NET_BUF_RX_COUNT=32 +CONFIG_NET_BUF_TX_COUNT=32 +CONFIG_NET_PKT_RX_COUNT=16 +CONFIG_NET_PKT_TX_COUNT=16 + +# Reduce the retry count, so the close always finishes within a second +CONFIG_NET_TCP_RETRY_COUNT=3 +CONFIG_NET_TCP_INIT_RETRANSMISSION_TIMEOUT=120 + +# JSON +CONFIG_JSON_LIBRARY=y + +# HTTP parser +CONFIG_HTTP_PARSER_URL=y +CONFIG_HTTP_PARSER=y +CONFIG_HTTP_SERVER=y + +CONFIG_HTTP_SERVER_MAX_CLIENTS=5 +CONFIG_HTTP_SERVER_MAX_STREAMS=5 + +# Network address config +CONFIG_NET_CONFIG_SETTINGS=n + +CONFIG_MAIN_STACK_SIZE=2048 +CONFIG_ZTEST_STACK_SIZE=18192 + +# Network debug config +CONFIG_NET_LOG=y diff --git a/tests/net/lib/http_server/prototype/sections-rom.ld b/tests/net/lib/http_server/prototype/sections-rom.ld new file mode 100644 index 0000000000000..512251641b2ad --- /dev/null +++ b/tests/net/lib/http_server/prototype/sections-rom.ld @@ -0,0 +1,3 @@ +#include + +ITERABLE_SECTION_ROM(http_resource_desc_test_http_service, 4) diff --git a/tests/net/lib/http_server/prototype/src/main.c b/tests/net/lib/http_server/prototype/src/main.c new file mode 100644 index 0000000000000..99db7f81f7c52 --- /dev/null +++ b/tests/net/lib/http_server/prototype/src/main.c @@ -0,0 +1,402 @@ +/* + * Copyright (c) 2023, Emna Rekik + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "server_internal.h" + +#include + +#include +#include +#include +#include + +#define SUPPORT_BACKWARD_COMPATIBILITY 1 +#define SUPPORT_HTTP_SERVER_UPGRADE 2 +#define BUFFER_SIZE 256 +#define MY_IPV4_ADDR "127.0.0.1" +#define SERVER_PORT 8080 +#define TIMEOUT 1000 + + +/* Magic, SETTINGS[0], HEADERS[1]: GET /, HEADERS[3]: GET /index.html, SETTINGS[0], GOAWAY[0]*/ +static const unsigned char frame[] = { + /* Magic */ + 0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x32, + 0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a, + /* SETTINGS[0] */ + 0x00, 0x00, 0x0c, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x64, 0x00, 0x04, 0x00, 0x00, 0xff, 0xff, + /* HEADERS[1]: GET / */ + 0x00, 0x00, 0x21, 0x01, 0x05, 0x00, 0x00, 0x00, 0x01, + 0x82, 0x84, 0x86, 0x41, 0x8a, 0x0b, 0xe2, 0x5c, 0x0b, 0x89, 0x70, 0xdc, + 0x78, 0x0f, 0x03, 0x53, 0x03, 0x2a, 0x2f, 0x2a, 0x90, 0x7a, 0x8a, 0xaa, + 0x69, 0xd2, 0x9a, 0xc4, 0xc0, 0x57, 0x68, 0x0b, 0x83, + /* HEADERS[3]: GET /index.html */ + 0x00, 0x00, 0x21, 0x01, 0x05, 0x00, 0x00, 0x00, 0x03, + 0x82, 0x85, 0x86, 0x41, 0x8a, 0x0b, 0xe2, 0x5c, 0x0b, 0x89, 0x70, 0xdc, + 0x78, 0x0f, 0x03, 0x53, 0x03, 0x2a, 0x2f, 0x2a, 0x90, 0x7a, 0x8a, 0xaa, + 0x69, 0xd2, 0x9a, 0xc4, 0xc0, 0x57, 0x68, 0x0b, 0x83, + /* SETTINGS[0] */ + 0x00, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, + /* GOAWAY[0] */ + 0x00, 0x00, 0x08, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static uint16_t test_http_service_port = SERVER_PORT; +HTTP_SERVICE_DEFINE(test_http_service, MY_IPV4_ADDR, + &test_http_service_port, 1, 10, NULL); + +static const char index_html_gz[] = "Hello, World!"; +struct http_resource_detail_static index_html_gz_resource_detail = { + .common = { + .type = HTTP_RESOURCE_TYPE_STATIC, + .bitmask_of_supported_http_methods = BIT(HTTP_GET), + }, + .static_data = index_html_gz, + .static_data_len = sizeof(index_html_gz), +}; + +HTTP_RESOURCE_DEFINE(index_html_gz_resource, test_http_service, "/", + &index_html_gz_resource_detail); + +static void test_streams(void) +{ + int ret; + int client_fd; + int proto = IPPROTO_TCP; + char *ptr; + struct sockaddr_in sa; + static unsigned char buf[512]; + unsigned int length; + uint8_t type; + size_t offset; + uint32_t stream_id; + + zassert_ok(http_server_start(), "Failed to start the server"); + + ret = zsock_socket(AF_INET, SOCK_STREAM, proto); + zassert_not_equal(ret, -1, "failed to create client socket (%d)", errno); + client_fd = ret; + + sa.sin_family = AF_INET; + sa.sin_port = htons(SERVER_PORT); + + ret = zsock_inet_pton(AF_INET, MY_IPV4_ADDR, &sa.sin_addr.s_addr); + zassert_not_equal(-1, ret, "inet_pton() failed (%d)", errno); + zassert_not_equal(0, ret, "%s is not a valid IPv4 address", MY_IPV4_ADDR); + zassert_equal(1, ret, "inet_pton() failed to convert %s", MY_IPV4_ADDR); + + memset(buf, '\0', sizeof(buf)); + ptr = (char *)zsock_inet_ntop(AF_INET, &sa.sin_addr, buf, sizeof(buf)); + zassert_not_equal(ptr, NULL, "inet_ntop() failed (%d)", errno); + + ret = zsock_connect(client_fd, (struct sockaddr *)&sa, sizeof(sa)); + zassert_not_equal(ret, -1, "failed to connect (%d)", errno); + + ret = zsock_send(client_fd, frame, sizeof(frame), 0); + zassert_not_equal(ret, -1, "send() failed (%d)", errno); + + memset(buf, 0, sizeof(buf)); + offset = 0; + do { + ret = zsock_recv(client_fd, buf + offset, sizeof(buf) - offset, 0); + zassert_not_equal(ret, -1, "recv() failed (%d)", errno); + + offset += ret; + } while (ret > 0); + + /* Settings frame is expected twice (server settings + settings ACK) */ + length = (buf[0] << 16) | (buf[1] << 8) | buf[2]; + length += 9; + type = buf[3]; + stream_id = (buf[5] << 24) | (buf[6] << 16) | (buf[7] << 8) | buf[8]; + stream_id &= 0x7fffffff; + + zassert_true((type == 0x4 && stream_id == 0), + "Expected a SETTINGS frame with stream ID 0"); + zassert_true(offset > length, "Parsing error, buffer exceeded"); + + offset -= length; + memmove(buf, buf + length, offset); + + length = (buf[0] << 16) | (buf[1] << 8) | buf[2]; + length += 9; + type = buf[3]; + stream_id = (buf[5] << 24) | (buf[6] << 16) | (buf[7] << 8) | buf[8]; + stream_id &= 0x7fffffff; + + zassert_true((type == 0x4 && stream_id == 0), + "Expected a SETTINGS frame with stream ID 0"); + zassert_true(offset > length, "Parsing error, buffer exceeded"); + + offset -= length; + memmove(buf, buf + length, offset); + + length = (buf[0] << 16) | (buf[1] << 8) | buf[2]; + length += 9; + type = buf[3]; + stream_id = (buf[5] << 24) | (buf[6] << 16) | (buf[7] << 8) | buf[8]; + stream_id &= 0x7fffffff; + + zassert_true((type == 0x1 && stream_id == 1), + "Expected a HEADERS frame with stream ID 1, got %d", stream_id); + zassert_true(offset > length, "Parsing error, buffer exceeded"); + + offset -= length; + memmove(buf, buf + length, offset); + + length = (buf[0] << 16) | (buf[1] << 8) | buf[2]; + length += 9; + type = buf[3]; + stream_id = (buf[5] << 24) | (buf[6] << 16) | (buf[7] << 8) | buf[8]; + stream_id &= 0x7fffffff; + buf[9] = 0; + + zassert_true((type == 0x0 && stream_id == 1), + "Expected a DATA frame with stream ID 1, got %d", stream_id); + zassert_true(offset > length, "Parsing error, buffer exceeded"); + + offset -= length; + memmove(buf, buf + length, offset); + + length = (buf[0] << 16) | (buf[1] << 8) | buf[2]; + length += 9; + type = buf[3]; + stream_id = (buf[5] << 24) | (buf[6] << 16) | (buf[7] << 8) | buf[8]; + stream_id &= 0x7fffffff; + + zassert_true((type == 0x1 && stream_id == 3), + "Expected a HEADERS frame with stream ID 3"); + zassert_true(offset >= length, "Parsing error, buffer exceeded"); + + offset -= length; + memmove(buf, buf + length, offset); + + length = (buf[0] << 16) | (buf[1] << 8) | buf[2]; + length += 9; + type = buf[3]; + stream_id = (buf[5] << 24) | (buf[6] << 16) | (buf[7] << 8) | buf[8]; + stream_id &= 0x7fffffff; + + zassert_true((type == 0x0 && stream_id == 3), + "Expected a DATA frame with stream ID 3"); + + ret = zsock_close(client_fd); + zassert_not_equal(-1, ret, "close() failed on the client fd (%d)", errno); + + zassert_ok(http_server_stop(), "Failed to stop the server"); +} + +ZTEST(server_function_tests, test_http_concurrent_streams) +{ + test_streams(); +} + +static void test_common(int test_support) +{ + int ret; + int client_fd; + int proto = IPPROTO_TCP; + char *ptr; + struct sockaddr_in sa = { 0 }; + static unsigned char buf[512]; + + zassert_ok(http_server_start(), "Failed to start the server"); + + ret = zsock_socket(AF_INET, SOCK_STREAM, proto); + zassert_not_equal(ret, -1, "failed to create client socket (%d)", errno); + client_fd = ret; + + sa.sin_family = AF_INET; + sa.sin_port = htons(SERVER_PORT); + + ret = zsock_inet_pton(AF_INET, MY_IPV4_ADDR, &sa.sin_addr.s_addr); + zassert_not_equal(-1, ret, "inet_pton() failed (%d)", errno); + zassert_not_equal(0, ret, "%s is not a valid IPv4 address", MY_IPV4_ADDR); + zassert_equal(1, ret, "inet_pton() failed to convert %s", MY_IPV4_ADDR); + + memset(buf, '\0', sizeof(buf)); + ptr = (char *)zsock_inet_ntop(AF_INET, &sa.sin_addr, buf, sizeof(buf)); + zassert_not_equal(ptr, NULL, "inet_ntop() failed (%d)", errno); + + ret = zsock_connect(client_fd, (struct sockaddr *)&sa, sizeof(sa)); + zassert_not_equal(ret, -1, "failed to connect (%s/%d)", strerror(errno), errno); + + if (test_support == SUPPORT_BACKWARD_COMPATIBILITY) { + + char *http1_request = "GET / HTTP/1.1\r\n" + "Host: 127.0.0.1:8080\r\n" + "User-Agent: curl/7.68.0\r\n" + "Accept: */*\r\n" + "Accept-Encoding: deflate, gzip, br\r\n" + "\r\n"; + + ret = zsock_send(client_fd, http1_request, strlen(http1_request), 0); + zassert_not_equal(ret, -1, "send() failed (%d)", errno); + + char expected_response[] = "HTTP/1.1 200 OK\r\n" + "Content-Type: text/html\r\n" + "Content-Length: 14\r\n" + "\r\n"; + + memset(buf, 0, sizeof(buf)); + ret = zsock_recv(client_fd, buf, sizeof(buf), 0); + zassert_not_equal(ret, -1, "recv() failed (%d)", errno); + + zassert_equal(strncmp(buf, expected_response, + strlen(expected_response)), 0, + "Received data doesn't match expected response"); + + } else if (test_support == SUPPORT_HTTP_SERVER_UPGRADE) { + + ret = zsock_send(client_fd, frame, sizeof(frame), 0); + zassert_not_equal(ret, -1, "send() failed (%d)", errno); + + memset(buf, 0, sizeof(buf)); + ret = zsock_recv(client_fd, buf, sizeof(buf), 0); + zassert_not_equal(ret, -1, "recv() failed (%d)", errno); + + uint8_t type = buf[3]; + + zassert_true(type == 0x4, "Expected a SETTINGS frame"); + } + + ret = zsock_close(client_fd); + zassert_not_equal(-1, ret, "close() failed on the client fd (%d)", errno); + + zassert_ok(http_server_stop(), "Failed to stop the server"); +} + +ZTEST(server_function_tests, test_http_upgrade) +{ + test_common(SUPPORT_HTTP_SERVER_UPGRADE); +} + +ZTEST(server_function_tests, test_backward_compatibility) +{ + test_common(SUPPORT_BACKWARD_COMPATIBILITY); +} + +ZTEST(server_function_tests, test_http_server_start_stop) +{ + struct sockaddr_in sa = { 0 }; + int client_fd; + int ret; + + sa.sin_family = AF_INET; + sa.sin_port = htons(SERVER_PORT); + + ret = zsock_inet_pton(AF_INET, MY_IPV4_ADDR, &sa.sin_addr.s_addr); + zassert_not_equal(-1, ret, "inet_pton() failed (%d)", errno); + zassert_not_equal(0, ret, "%s is not a valid IPv4 address", MY_IPV4_ADDR); + zassert_equal(1, ret, "inet_pton() failed to convert %s", MY_IPV4_ADDR); + + zassert_ok(http_server_start(), "Failed to start the server"); + zassert_not_ok(http_server_start(), "Server start should report na error."); + + zassert_ok(http_server_stop(), "Failed to stop the server"); + zassert_not_ok(http_server_stop(), "Server stop should report na error."); + + zassert_ok(http_server_start(), "Failed to start the server"); + + /* Server should be listening now. */ + ret = zsock_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + zassert_not_equal(ret, -1, "failed to create client socket (%d)", errno); + client_fd = ret; + + ret = zsock_connect(client_fd, (struct sockaddr *)&sa, sizeof(sa)); + zassert_not_equal(ret, -1, "failed to connect to the server (%d)", errno); + + ret = zsock_close(client_fd); + zassert_not_equal(-1, ret, "close() failed on the client fd (%d)", errno); + + zassert_ok(http_server_stop(), "Failed to stop the server"); +} + +ZTEST(server_function_tests, test_get_frame_type_name) +{ + zassert_equal(strcmp(get_frame_type_name(HTTP_SERVER_DATA_FRAME), "DATA"), 0, + "Unexpected frame type"); + zassert_equal(strcmp(get_frame_type_name(HTTP_SERVER_HEADERS_FRAME), "HEADERS"), 0, + "Unexpected frame type"); + zassert_equal(strcmp(get_frame_type_name(HTTP_SERVER_PRIORITY_FRAME), "PRIORITY"), 0, + "Unexpected frame type"); + zassert_equal(strcmp(get_frame_type_name(HTTP_SERVER_RST_STREAM_FRAME), "RST_STREAM"), 0, + "Unexpected frame type"); + zassert_equal(strcmp(get_frame_type_name(HTTP_SERVER_SETTINGS_FRAME), "SETTINGS"), 0, + "Unexpected frame type"); + zassert_equal(strcmp(get_frame_type_name(HTTP_SERVER_PUSH_PROMISE_FRAME), "PUSH_PROMISE"), + 0, "Unexpected frame type"); + zassert_equal(strcmp(get_frame_type_name(HTTP_SERVER_PING_FRAME), "PING"), 0, + "Unexpected frame type"); + zassert_equal(strcmp(get_frame_type_name(HTTP_SERVER_GOAWAY_FRAME), "GOAWAY"), 0, + "Unexpected frame type"); + zassert_equal(strcmp(get_frame_type_name(HTTP_SERVER_WINDOW_UPDATE_FRAME), "WINDOW_UPDATE"), + 0, "Unexpected frame type"); + zassert_equal(strcmp(get_frame_type_name(HTTP_SERVER_CONTINUATION_FRAME), "CONTINUATION"), + 0, "Unexpected frame type"); +} + +ZTEST(server_function_tests, test_parse_http_frames) +{ + static struct http_client_ctx ctx_client1; + static struct http_client_ctx ctx_client2; + struct http_frame *frame; + + unsigned char buffer1[] = { + 0x00, 0x00, 0x0c, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x64, 0x00, + 0x04, 0x00, 0x00, 0xff, 0xff, 0x00 + }; + unsigned char buffer2[] = { + 0x00, 0x00, 0x21, 0x01, 0x05, 0x00, 0x00, 0x00, + 0x01, 0x82, 0x84, 0x86, 0x41, 0x8a, 0x0b, 0xe2, + 0x5c, 0x0b, 0x89, 0x70, 0xdc, 0x78, 0x0f, 0x03, + 0x53, 0x03, 0x2a, 0x2f, 0x2a, 0x90, 0x7a, 0x8a, + 0xaa, 0x69, 0xd2, 0x9a, 0xc4, 0xc0, 0x57, 0x68, + 0x0b, 0x83 + }; + + memcpy(ctx_client1.buffer, buffer1, sizeof(buffer1)); + memcpy(ctx_client2.buffer, buffer2, sizeof(buffer2)); + + ctx_client1.cursor = ctx_client1.buffer; + ctx_client1.data_len = ARRAY_SIZE(buffer1); + + ctx_client2.cursor = ctx_client2.buffer; + ctx_client2.data_len = ARRAY_SIZE(buffer2); + + /* Test: Buffer with the first frame */ + int parser1 = parse_http_frame_header(&ctx_client1); + + zassert_equal(parser1, 1, "Failed to parse the first frame"); + + frame = &ctx_client1.current_frame; + + /* Validate frame details for the 1st frame */ + zassert_equal(frame->length, 0x0C, "Expected length for the 1st frame doesn't match"); + zassert_equal(frame->type, 0x04, "Expected type for the 1st frame doesn't match"); + zassert_equal(frame->flags, 0x00, "Expected flags for the 1st frame doesn't match"); + zassert_equal(frame->stream_identifier, 0x00, + "Expected stream_identifier for the 1st frame doesn't match"); + + /* Test: Buffer with the second frame */ + int parser2 = parse_http_frame_header(&ctx_client2); + + zassert_equal(parser2, 1, "Failed to parse the second frame"); + + frame = &ctx_client2.current_frame; + + /* Validate frame details for the 2nd frame */ + zassert_equal(frame->length, 0x21, "Expected length for the 2nd frame doesn't match"); + zassert_equal(frame->type, 0x01, "Expected type for the 2nd frame doesn't match"); + zassert_equal(frame->flags, 0x05, "Expected flags for the 2nd frame doesn't match"); + zassert_equal(frame->stream_identifier, 0x01, + "Expected stream_identifier for the 2nd frame doesn't match"); +} + +ZTEST_SUITE(server_function_tests, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/net/lib/http_server/prototype/testcase.yaml b/tests/net/lib/http_server/prototype/testcase.yaml new file mode 100644 index 0000000000000..d5683174bcd04 --- /dev/null +++ b/tests/net/lib/http_server/prototype/testcase.yaml @@ -0,0 +1,16 @@ +common: + harness: net + min_ram: 16 + tags: + - http + - net + - server + - socket + integration_platforms: + - native_sim + - qemu_x86 + platform_exclude: + - native_posix + - native_posix/native/64 +tests: + net.http.server.prototype: {} diff --git a/tests/net/lib/http_server/tls/CMakeLists.txt b/tests/net/lib/http_server/tls/CMakeLists.txt new file mode 100644 index 0000000000000..f72aa8b44769e --- /dev/null +++ b/tests/net/lib/http_server/tls/CMakeLists.txt @@ -0,0 +1,54 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(tls) + +set(BASE_PATH "../../../../../subsys/net/lib/http/") +include_directories(${BASE_PATH}/headers) + +set(gen_dir ${ZEPHYR_BINARY_DIR}/include/generated/) + +if (${CONFIG_TLS_CREDENTIALS}) + generate_inc_file_for_target( + app + ${ZEPHYR_BASE}/samples/net/sockets/http_server/src/ca.der + ${gen_dir}/ca.inc + ) + + generate_inc_file_for_target( + app + ${ZEPHYR_BASE}/samples/net/sockets/http_server/src/server.der + ${gen_dir}/server.inc + ) + + generate_inc_file_for_target( + app + ${ZEPHYR_BASE}/samples/net/sockets/http_server/src/server_privkey.der + ${gen_dir}/server_privkey.inc + ) + + # we reuse the same certificate / private key for client + # since it seems to be the only one that is signed by a ca + generate_inc_file_for_target( + app + ${ZEPHYR_BASE}/samples/net/sockets/http_server/src/server.der + ${gen_dir}/client.inc + ) + + generate_inc_file_for_target( + app + ${ZEPHYR_BASE}/samples/net/sockets/http_server/src/server_privkey.der + ${gen_dir}/client_privkey.inc + ) +endif() + +set(source_file_index src/index.html) +generate_inc_file_for_target(app ${source_file_index} ${gen_dir}/index.html.inc) +generate_inc_file_for_target(app ${source_file_index} ${gen_dir}/index.html.gz.inc --gzip) + +FILE(GLOB app_sources src/main.c) +target_sources(app PRIVATE ${app_sources}) + +zephyr_linker_sources(SECTIONS sections-rom.ld) +zephyr_iterable_section(NAME http_resource_desc_test_http_service KVMA RAM_REGION GROUP RODATA_REGION SUBALIGN 4) diff --git a/tests/net/lib/http_server/tls/prj.conf b/tests/net/lib/http_server/tls/prj.conf new file mode 100644 index 0000000000000..997a3b6426433 --- /dev/null +++ b/tests/net/lib/http_server/tls/prj.conf @@ -0,0 +1,61 @@ +# General config +CONFIG_ZTEST=y +CONFIG_NET_TEST=y + +CONFIG_ENTROPY_GENERATOR=y +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_FDTABLE=y +CONFIG_EVENTFD=y +CONFIG_POSIX_API=y + +# Networking config +CONFIG_NETWORKING=y +CONFIG_NET_LOOPBACK=y +CONFIG_NET_LOOPBACK_MTU=1280 +CONFIG_NET_DRIVERS=y +CONFIG_NET_IPV4=y +CONFIG_NET_IPV6=y +CONFIG_NET_TCP=y +CONFIG_NET_SOCKETS=y + +# Logging / Debugging options +CONFIG_NET_LOG=y + +# TLS Options +CONFIG_TLS_CREDENTIALS=y +CONFIG_NET_SOCKETS_SOCKOPT_TLS=y +CONFIG_TLS_MAX_CREDENTIALS_NUMBER=5 +CONFIG_MBEDTLS_ENABLE_HEAP=y +CONFIG_MBEDTLS_HEAP_SIZE=60000 +CONFIG_MBEDTLS=y +CONFIG_MBEDTLS_BUILTIN=y +CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=2048 +CONFIG_NET_SOCKETS_TLS_MAX_CONTEXTS=6 + +# Network buffers / packets / sizes +CONFIG_NET_BUF_TX_COUNT=128 +CONFIG_NET_BUF_RX_COUNT=128 +CONFIG_NET_PKT_TX_COUNT=16 +CONFIG_NET_PKT_RX_COUNT=16 +CONFIG_POSIX_MAX_FDS=32 +CONFIG_NET_SOCKETS_POLL_MAX=32 +CONFIG_POSIX_MAX_FDS=32 +CONFIG_REQUIRES_FULL_LIBC=y +CONFIG_EVENTFD_MAX=10 +CONFIG_NET_MAX_CONTEXTS=10 +CONFIG_NET_MAX_CONN=10 + +# Stack sizes +CONFIG_MAIN_STACK_SIZE=2048 +CONFIG_ZTEST_STACK_SIZE=4096 + +# JSON +CONFIG_JSON_LIBRARY=y + +# HTTP parser +CONFIG_HTTP_PARSER_URL=y +CONFIG_HTTP_PARSER=y +CONFIG_HTTP_SERVER=y + +# Network address config +CONFIG_NET_CONFIG_SETTINGS=n diff --git a/tests/net/lib/http_server/tls/sections-rom.ld b/tests/net/lib/http_server/tls/sections-rom.ld new file mode 100644 index 0000000000000..512251641b2ad --- /dev/null +++ b/tests/net/lib/http_server/tls/sections-rom.ld @@ -0,0 +1,3 @@ +#include + +ITERABLE_SECTION_ROM(http_resource_desc_test_http_service, 4) diff --git a/tests/net/lib/http_server/tls/src/index.html b/tests/net/lib/http_server/tls/src/index.html new file mode 100644 index 0000000000000..7f82a1e3975b9 --- /dev/null +++ b/tests/net/lib/http_server/tls/src/index.html @@ -0,0 +1,10 @@ + + + + My Simple Server + + +

Welcome to my simple server!

+

This is a simple HTML file.

+ + diff --git a/tests/net/lib/http_server/tls/src/main.c b/tests/net/lib/http_server/tls/src/main.c new file mode 100644 index 0000000000000..34c5c8fe7f0b4 --- /dev/null +++ b/tests/net/lib/http_server/tls/src/main.c @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2023, Emna Rekik + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "server_internal.h" +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(net_test, CONFIG_NET_SOCKETS_LOG_LEVEL); + +/** @brief Stack size for the server thread */ +#define STACK_SIZE 8192 + +#define MY_IPV4_ADDR "127.0.0.1" + +/** @brief arbitrary timeout value in ms */ +#define TIMEOUT 1000 + +#define BUFFER_SIZE 256 +#define SERVER_PORT 8000 + +enum tls_tag { + /** The Certificate Authority public key */ + CA_CERTIFICATE_TAG, + /** Used for both the public and private server keys */ + SERVER_CERTIFICATE_TAG, + /** Used for both the public and private client keys */ + CLIENT_CERTIFICATE_TAG, +}; + +static const sec_tag_t server_tag_list_verify[] = { + CA_CERTIFICATE_TAG, + SERVER_CERTIFICATE_TAG, +}; + +static uint16_t test_http_service_port = SERVER_PORT; +HTTPS_SERVICE_DEFINE(test_http_service, MY_IPV4_ADDR, &test_http_service_port, + 1, 10, NULL, server_tag_list_verify, + sizeof(server_tag_list_verify)); + +static const unsigned char ca[] = { +#include "ca.inc" +}; + +/** + * @brief The Server Certificate + * + * This is the public key of the server. + */ +static const unsigned char server[] = { +#include "server.inc" +}; + +/** + * @brief The Server Private Key + * + * This is the private key of the server. + */ +static const unsigned char server_privkey[] = { +#include "server_privkey.inc" +}; + +/** + * @brief The Client Certificate + * + * This is the public key of the client. + */ +static const unsigned char client[] = { +#include "client.inc" +}; + +/** + * @brief The Client Private Key + * + * This is the private key of the client. + */ +static const unsigned char client_privkey[] = { +#include "client_privkey.inc" +}; + +static const unsigned char index_html_gz[] = { +#include "index.html.gz.inc" +}; + +static const unsigned char compressed_inc_file[] = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0xff, 0x35, 0x8e, 0xc1, 0x0a, 0xc2, 0x30, + 0x0c, 0x86, 0xef, 0x7d, 0x8a, 0xec, 0x05, 0x2c, + 0xbb, 0x87, 0x5c, 0x54, 0xf0, 0xe0, 0x50, 0x58, + 0x41, 0x3c, 0x4e, 0x17, 0x69, 0x21, 0xa5, 0x65, + 0x2d, 0x42, 0xdf, 0xde, 0xba, 0x6e, 0x21, 0x10, + 0xf8, 0xf9, 0xbe, 0x9f, 0x60, 0x77, 0xba, 0x1d, + 0xcd, 0xf3, 0x7e, 0x06, 0x9b, 0xbd, 0x90, 0xc2, + 0xfd, 0xf0, 0x34, 0x93, 0x82, 0x3a, 0x98, 0x5d, + 0x16, 0xa6, 0xa1, 0xc0, 0xe8, 0x7c, 0x14, 0x86, + 0x91, 0x97, 0x2f, 0x2f, 0xa8, 0x5b, 0xae, 0x50, + 0x37, 0x16, 0x5f, 0x61, 0x2e, 0x9b, 0x62, 0x7b, + 0x7a, 0xb0, 0xbc, 0x83, 0x67, 0xc8, 0x01, 0x7c, + 0x81, 0xd4, 0xd4, 0xb4, 0xaa, 0x5d, 0x55, 0xfa, + 0x8d, 0x8c, 0x64, 0xac, 0x4b, 0x50, 0x77, 0xda, + 0xa1, 0x8b, 0x19, 0xae, 0xf0, 0x71, 0xc2, 0x07, + 0xd4, 0xf1, 0xdf, 0xdf, 0x8a, 0xab, 0xb4, 0xbe, + 0xf6, 0x03, 0xea, 0x2d, 0x11, 0x5c, 0xb2, 0x00, + 0x00, 0x00, +}; + +struct http_resource_detail_static index_html_gz_resource_detail = { + .common = { + .type = HTTP_RESOURCE_TYPE_STATIC, + .bitmask_of_supported_http_methods = BIT(HTTP_GET), + }, + .static_data = index_html_gz, + .static_data_len = sizeof(index_html_gz), +}; + +HTTP_RESOURCE_DEFINE(index_html_gz_resource, test_http_service, "/", + &index_html_gz_resource_detail); + +static void test_tls(void) +{ + int ret, recv_len; + int client_fd; + int proto = IPPROTO_TCP; + size_t len; + char *ptr; + const char *data; + struct sockaddr_in sa; + static unsigned char buf[512]; + char http1_request[] = + "GET / HTTP/1.1\r\n" + "Host: 127.0.0.1:8080\r\n" + "Accept: */*\r\n" + "Accept-Encoding: deflate, gzip, br\r\n" + "\r\n"; + + /* set the common protocol for both client and server */ + if (IS_ENABLED(CONFIG_NET_SOCKETS_SOCKOPT_TLS)) { + proto = IPPROTO_TLS_1_2; + } + + zassert_ok(http_server_start(), "Failed to start the server"); + + ret = zsock_socket(AF_INET, SOCK_STREAM, proto); + zassert_not_equal(ret, -1, "failed to create client socket (%d)", errno); + client_fd = ret; + + if (IS_ENABLED(CONFIG_TLS_CREDENTIALS) && + IS_ENABLED(CONFIG_NET_SOCKETS_SOCKOPT_TLS)) { + static const sec_tag_t sec_tag_list_verify_none[] = { + CA_CERTIFICATE_TAG, + }; + const sec_tag_t *sec_tag_list; + size_t sec_tag_list_size; + + sec_tag_list_size = sizeof(sec_tag_list); + sec_tag_list = sec_tag_list_verify_none; + + ret = zsock_setsockopt(client_fd, SOL_TLS, TLS_SEC_TAG_LIST, + sec_tag_list, sec_tag_list_size); + zassert_not_equal(ret, -1, "failed to set TLS_SEC_TAG_LIST (%d)", errno); + + ret = zsock_setsockopt(client_fd, SOL_TLS, TLS_HOSTNAME, + "localhost", sizeof("localhost")); + zassert_not_equal(ret, -1, "failed to set TLS_HOSTNAME (%d)", errno); + } + + sa.sin_family = AF_INET; + sa.sin_port = htons(SERVER_PORT); + + ret = zsock_inet_pton(AF_INET, MY_IPV4_ADDR, &sa.sin_addr.s_addr); + zassert_not_equal(-1, ret, "inet_pton() failed (%d)", errno); + zassert_not_equal(ret, 0, "%s is not a valid IPv4 address", MY_IPV4_ADDR); + zassert_equal(ret, 1, "inet_pton() failed to convert %s", MY_IPV4_ADDR); + + memset(buf, '\0', sizeof(buf)); + ptr = (char *)zsock_inet_ntop(AF_INET, &sa.sin_addr, buf, sizeof(buf)); + zassert_not_equal(ptr, NULL, "inet_ntop() failed (%d)", errno); + + ret = zsock_connect(client_fd, (struct sockaddr *)&sa, sizeof(sa)); + zassert_not_equal(ret, -1, "failed to connect (%d)", errno); + + ret = zsock_send(client_fd, http1_request, sizeof(http1_request) - 1, 0); + zassert_not_equal(ret, -1, "send() failed (%d)", errno); + zassert_equal(ret, sizeof(http1_request) - 1, "expected: %zu actual: %d", + sizeof(http1_request) - 1, ret); + + memset(buf, 0, sizeof(buf)); + recv_len = zsock_recv(client_fd, buf, sizeof(buf), 0); + zassert_not_equal(recv_len, -1, "recv() failed (%d)", errno); + + len = sizeof(index_html_gz); + + while (recv_len < len) { + ret = zsock_recv(client_fd, buf + recv_len, sizeof(buf) - recv_len, 0); + zassert_not_equal(ret, -1, "recv() failed (%d)", errno); + + recv_len += ret; + } + + data = strstr(buf, "\r\n\r\n"); + zassert_not_null(data, "Header not found"); + + data += 4; + + zassert_equal(len, sizeof(compressed_inc_file), "Invalid compressed file size"); + + ret = memcmp(data, compressed_inc_file, len); + zassert_equal(ret, 0, + "inc_file and compressed_inc_file contents are not identical (%d)", ret); + + ret = zsock_close(client_fd); + zassert_not_equal(ret, -1, "close() failed on the client fd (%d)", errno); + + zassert_ok(http_server_stop(), "Failed to stop the server"); +} + +ZTEST(framework_tests_tls, test_tls) +{ + test_tls(); +} + +static void *setup(void) +{ + int ret; + + if (IS_ENABLED(CONFIG_TLS_CREDENTIALS)) { + NET_DBG("Loading credentials"); + ret = tls_credential_add(CA_CERTIFICATE_TAG, + TLS_CREDENTIAL_CA_CERTIFICATE, + ca, sizeof(ca)); + zassert_equal(ret, 0, "failed to add CA Certificate (%d)", ret); + + ret = tls_credential_add(SERVER_CERTIFICATE_TAG, + TLS_CREDENTIAL_SERVER_CERTIFICATE, + server, sizeof(server)); + zassert_equal(ret, 0, "failed to add Server Certificate (%d)", ret); + + ret = tls_credential_add(SERVER_CERTIFICATE_TAG, + TLS_CREDENTIAL_PRIVATE_KEY, + server_privkey, sizeof(server_privkey)); + zassert_equal(ret, 0, "failed to add Server Private Key (%d)", ret); + + ret = tls_credential_add(CLIENT_CERTIFICATE_TAG, + TLS_CREDENTIAL_SERVER_CERTIFICATE, + client, sizeof(client)); + zassert_equal(ret, 0, "failed to add Client Certificate (%d)", ret); + + ret = tls_credential_add(CLIENT_CERTIFICATE_TAG, + TLS_CREDENTIAL_PRIVATE_KEY, + client_privkey, sizeof(client_privkey)); + zassert_equal(ret, 0, "failed to add Client Private Key (%d)", ret); + } + + return NULL; +} + +ZTEST_SUITE(framework_tests_tls, NULL, setup, NULL, NULL, NULL); diff --git a/tests/net/lib/http_server/tls/testcase.yaml b/tests/net/lib/http_server/tls/testcase.yaml new file mode 100644 index 0000000000000..13f6ded54d1d8 --- /dev/null +++ b/tests/net/lib/http_server/tls/testcase.yaml @@ -0,0 +1,19 @@ +common: + harness: net + min_ram: 192 + tags: + - http + - net + - server + - socket + integration_platforms: + - native_sim + - qemu_x86 + platform_exclude: + - native_posix + - native_posix/native/64 + platform_allow: + - native_sim + - qemu_x86 +tests: + net.http.server.tls: {} From 49ccbff3125962287014ef5f93c8cee9d8fc433d Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Thu, 9 Nov 2023 20:15:12 +0200 Subject: [PATCH 5/5] samples: net: http_server: Add HTTP server sample application A simple HTTP server sample application. Signed-off-by: Emna Rekik Signed-off-by: Jukka Rissanen Signed-off-by: Robert Lubos --- .../net/sockets/http_server/CMakeLists.txt | 59 ++++++ samples/net/sockets/http_server/Kconfig | 41 ++++ samples/net/sockets/http_server/README.rst | 118 ++++++++++++ samples/net/sockets/http_server/prj.conf | 71 +++++++ samples/net/sockets/http_server/sample.yaml | 16 ++ .../net/sockets/http_server/sections-rom.ld | 4 + samples/net/sockets/http_server/src/ca.der | Bin 0 -> 783 bytes .../net/sockets/http_server/src/certificate.h | 50 +++++ .../net/sockets/http_server/src/dummy_psk.h | 14 ++ .../http_server/src/https-server-cert.der | Bin 0 -> 767 bytes .../http_server/src/https-server-key.der | Bin 0 -> 1218 bytes .../net/sockets/http_server/src/index.html | 10 + samples/net/sockets/http_server/src/main.c | 180 ++++++++++++++++++ .../http_server/src/not_found_page.html | 10 + .../net/sockets/http_server/src/server.der | Bin 0 -> 693 bytes .../http_server/src/server_privkey.der | Bin 0 -> 1219 bytes 16 files changed, 573 insertions(+) create mode 100644 samples/net/sockets/http_server/CMakeLists.txt create mode 100644 samples/net/sockets/http_server/Kconfig create mode 100644 samples/net/sockets/http_server/README.rst create mode 100644 samples/net/sockets/http_server/prj.conf create mode 100644 samples/net/sockets/http_server/sample.yaml create mode 100644 samples/net/sockets/http_server/sections-rom.ld create mode 100644 samples/net/sockets/http_server/src/ca.der create mode 100644 samples/net/sockets/http_server/src/certificate.h create mode 100644 samples/net/sockets/http_server/src/dummy_psk.h create mode 100644 samples/net/sockets/http_server/src/https-server-cert.der create mode 100644 samples/net/sockets/http_server/src/https-server-key.der create mode 100644 samples/net/sockets/http_server/src/index.html create mode 100644 samples/net/sockets/http_server/src/main.c create mode 100644 samples/net/sockets/http_server/src/not_found_page.html create mode 100644 samples/net/sockets/http_server/src/server.der create mode 100644 samples/net/sockets/http_server/src/server_privkey.der diff --git a/samples/net/sockets/http_server/CMakeLists.txt b/samples/net/sockets/http_server/CMakeLists.txt new file mode 100644 index 0000000000000..5b27e5ee7ca96 --- /dev/null +++ b/samples/net/sockets/http_server/CMakeLists.txt @@ -0,0 +1,59 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +find_package(Python REQUIRED COMPONENTS Interpreter) + +project(http_server) + +if(CONFIG_NET_SOCKETS_SOCKOPT_TLS AND + CONFIG_MBEDTLS_KEY_EXCHANGE_PSK_ENABLED AND + (CONFIG_NET_SAMPLE_PSK_HEADER_FILE STREQUAL "dummy_psk.h")) + add_custom_target(development_psk + COMMAND ${CMAKE_COMMAND} -E echo "----------------------------------------------------------" + COMMAND ${CMAKE_COMMAND} -E echo "--- WARNING: Using dummy PSK! Only suitable for ---" + COMMAND ${CMAKE_COMMAND} -E echo "--- development. Set NET_SAMPLE_PSK_HEADER_FILE to use ---" + COMMAND ${CMAKE_COMMAND} -E echo "--- own pre-shared key. ---" + COMMAND ${CMAKE_COMMAND} -E echo "----------------------------------------------------------" + ) + add_dependencies(app development_psk) +endif() + +option(INCLUDE_HTML_CONTENT "Include the HTML content" ON) + +target_sources(app PRIVATE src/main.c) + +set(gen_dir ${ZEPHYR_BINARY_DIR}/include/generated/) + +set(source_file_index src/index.html) +generate_inc_file_for_target(app ${source_file_index} ${gen_dir}/index.html.gz.inc --gzip) + +set(source_file_not_found src/not_found_page.html) +generate_inc_file_for_target(app ${source_file_not_found} ${gen_dir}/not_found_page.html.gz.inc --gzip) + +target_link_libraries(app PRIVATE zephyr_interface zephyr) + +zephyr_linker_sources(SECTIONS sections-rom.ld) +zephyr_linker_section_ifdef(CONFIG_NET_SAMPLE_HTTPS_SERVICE NAME + http_resource_desc_test_https_service + KVMA RAM_REGION GROUP RODATA_REGION + SUBALIGN Z_LINK_ITERABLE_SUBALIGN) +zephyr_linker_section_ifdef(CONFIG_NET_SAMPLE_HTTP_SERVICE NAME + http_resource_desc_test_http_service + KVMA RAM_REGION GROUP RODATA_REGION + SUBALIGN Z_LINK_ITERABLE_SUBALIGN) + +foreach(inc_file + ca.der + server.der + server_privkey.der + https-server-cert.der + https-server-key.der + ) + generate_inc_file_for_target( + app + src/${inc_file} + ${gen_dir}/${inc_file}.inc + ) +endforeach() diff --git a/samples/net/sockets/http_server/Kconfig b/samples/net/sockets/http_server/Kconfig new file mode 100644 index 0000000000000..90104be01fa19 --- /dev/null +++ b/samples/net/sockets/http_server/Kconfig @@ -0,0 +1,41 @@ +# Config options for http2 server sample application + +# Copyright (c) 2023, Emna Rekik +# SPDX-License-Identifier: Apache-2.0 + +mainmenu "HTTP2 server sample application" + +config NET_SAMPLE_HTTP_SERVICE + bool "Enable http service" + default y + +config NET_SAMPLE_HTTP_SERVER_SERVICE_PORT + int "Port number for http service" + default 80 + depends on NET_SAMPLE_HTTP_SERVICE + +config NET_SAMPLE_HTTPS_SERVICE + bool "Enable https service" + depends on NET_SOCKETS_SOCKOPT_TLS || TLS_CREDENTIALS + +config NET_SAMPLE_HTTPS_SERVER_SERVICE_PORT + int "Port number for https service" + default 443 + depends on NET_SAMPLE_HTTPS_SERVICE + +config NET_SAMPLE_PSK_HEADER_FILE + string "Header file containing PSK" + default "dummy_psk.h" + depends on MBEDTLS_KEY_EXCHANGE_PSK_ENABLED + help + Name of a header file containing a + pre-shared key. + +config NET_SAMPLE_CERTS_WITH_SC + bool "Signed certificates" + depends on NET_SOCKETS_SOCKOPT_TLS + help + Enable this flag, if you are interested to run this + application with signed certificates and keys. + +source "Kconfig.zephyr" diff --git a/samples/net/sockets/http_server/README.rst b/samples/net/sockets/http_server/README.rst new file mode 100644 index 0000000000000..cc4cd1c548219 --- /dev/null +++ b/samples/net/sockets/http_server/README.rst @@ -0,0 +1,118 @@ +Zephyr HTTP Server +================== + +Overview +-------- + +This sample application demonstrates the use of the ``http_server`` library. +This library provides high-level functions to simplify and abstract server implementation. +The server supports the HTTP/1.1 protocol which can also be upgraded to HTTP/2, +it also support native HTTP/2 protocol without upgrading. + +Requirement +----------- + +`QEMU Networking `_ + +Building and running the server +------------------------------- + +To build and run the application: + +.. code-block:: bash + + $ west build -p auto -b -t run samples/net/sockets/http_server + +When the server is up, we can make requests to the server using either HTTP/1.1 or +HTTP/2 protocol from the host machine. + +**With HTTP/1.1:** + +- Using a browser: ``http://192.0.2.1/`` +- Using curl: ``curl -v --compressed http://192.0.2.1/`` +- Using ab (Apache Bench): ``ab -n10 http://192.0.2.1/`` + +**With HTTP/2:** + +- Using nghttp client: ``nghttp -v --no-dep http://192.0.2.1/`` +- Using curl: ``curl --http2 -v --compressed http://192.0.2.1/`` +- Using h2load: ``h2load -n10 http://192.0.2.1/`` + +Server Customization +--------------------- + +The server sample contains several parameters that can be customized based on +the requirements. These are the configurable parameters: + +- ``CONFIG_NET_SAMPLE_HTTP_SERVER_SERVICE_PORT``: Configures the service port. + +- ``CONFIG_HTTP_SERVER_MAX_CLIENTS``: Defines the maximum number of HTTP/2 + clients that the server can handle simultaneously. + +- ``CONFIG_HTTP_SERVER_MAX_STREAMS``: Specifies the maximum number of HTTP/2 + streams that can be established per client. + +- ``CONFIG_HTTP_SERVER_CLIENT_BUFFER_SIZE``: Defines the buffer size allocated + for each client. This limits the maximum length of an individual HTTP header + supported. + +- ``CONFIG_HTTP_SERVER_MAX_URL_LENGTH``: Specifies the maximum length of an HTTP + URL that the server can process. + +To customize these options, we can run ``west build -t menuconfig``, which provides +us with an interactive configuration interface. Then we could navigate from the top-level +menu to: ``-> Subsystems and OS Services -> Networking -> Network Protocols``. + +Performance Analysis +-------------------- + +CPU Usage Profiling +******************* + +We can use ``perf`` to collect statistics about the CPU usage of our server +running in native_sim board with the ``stat`` command: + +.. code-block:: bash + + $ sudo perf stat -p + +``perf stat`` will then start monitoring our server. We can let it run while +sending requests to our server. Once we've collected enough data, we can +stop ``perf stat``, which will print a summary of the performance statistics. + +Hotspot Analysis +**************** + +``perf record`` and ``perf report`` can be used together to identify the +functions in our code that consume the most CPU time: + +.. code-block:: bash + + $ sudo perf record -g -p -o perf.data + +After running our server under load (For example, using ApacheBench tool), +we can stop the recording and analyze the data using: + +.. code-block:: bash + + $ sudo perf report -i perf.data + +After generating a file named ``perf.data`` which contains the profiling data, +we can visualize it using ``FlameGraph`` tool. It's particularly useful for +identifying the most expensive code-paths and inspect where our application is +spending the most time. + +To do this, we need to convert the ``perf.data`` to a format that ``FlameGraph`` +can understand: + +.. code-block:: bash + + $ sudo perf script | ~/FlameGraph/stackcollapse-perf.pl > out.perf-folded + +And, then, generate the ``FlameGraph``: + +.. code-block:: bash + + $ ~/FlameGraph/flamegraph.pl out.perf-folded > flamegraph.svg + +We can view flamegraph.svg using a web browser. diff --git a/samples/net/sockets/http_server/prj.conf b/samples/net/sockets/http_server/prj.conf new file mode 100644 index 0000000000000..9e56f3332e7f2 --- /dev/null +++ b/samples/net/sockets/http_server/prj.conf @@ -0,0 +1,71 @@ +# General config +CONFIG_MAIN_STACK_SIZE=3072 +CONFIG_SHELL=y +CONFIG_LOG=y +CONFIG_ENTROPY_GENERATOR=y +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_INIT_STACKS=y +CONFIG_POSIX_MAX_FDS=32 +CONFIG_POSIX_API=y +CONFIG_FDTABLE=y +CONFIG_NET_SOCKETS_POLL_MAX=32 + +# Eventfd +CONFIG_EVENTFD=y + +# Networking config +CONFIG_NETWORKING=y +CONFIG_NET_IPV4=y +CONFIG_NET_IPV6=y +CONFIG_NET_TCP=y +CONFIG_NET_SOCKETS=y +CONFIG_NET_CONNECTION_MANAGER=y +CONFIG_NET_SHELL=y +CONFIG_NET_LOG=y + +# JSON +CONFIG_JSON_LIBRARY=y + +# HTTP parser +CONFIG_HTTP_PARSER_URL=y +CONFIG_HTTP_PARSER=y +CONFIG_HTTP_SERVER=y + +# Network buffers +CONFIG_NET_PKT_RX_COUNT=16 +CONFIG_NET_PKT_TX_COUNT=16 +CONFIG_NET_BUF_RX_COUNT=128 +CONFIG_NET_BUF_TX_COUNT=128 +CONFIG_NET_CONTEXT_NET_PKT_POOL=y + +# IP address options +CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=3 +CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=4 +CONFIG_NET_MAX_CONTEXTS=32 +CONFIG_NET_MAX_CONN=32 + +# Network address config +CONFIG_NET_CONFIG_SETTINGS=y +CONFIG_NET_CONFIG_NEED_IPV4=y +CONFIG_NET_CONFIG_NEED_IPV6=y +CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.1" +CONFIG_NET_CONFIG_PEER_IPV4_ADDR="192.0.2.2" +CONFIG_NET_CONFIG_MY_IPV4_GW="192.0.2.2" +CONFIG_NET_CONFIG_MY_IPV6_ADDR="2001:db8::1" +CONFIG_NET_CONFIG_PEER_IPV6_ADDR="2001:db8::2" + +# TLS configuration +CONFIG_MBEDTLS=y +CONFIG_MBEDTLS_BUILTIN=y +CONFIG_MBEDTLS_ENABLE_HEAP=y +CONFIG_MBEDTLS_HEAP_SIZE=60000 +CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=2048 +CONFIG_NET_SOCKETS_SOCKOPT_TLS=y +CONFIG_NET_SOCKETS_TLS_MAX_CONTEXTS=6 +CONFIG_TLS_CREDENTIALS=y +CONFIG_TLS_MAX_CREDENTIALS_NUMBER=5 + +# Networking tweaks +# Required to handle large number of consecutive connections, +# e.g. when testing with ApacheBench. +CONFIG_NET_TCP_TIME_WAIT_DELAY=0 diff --git a/samples/net/sockets/http_server/sample.yaml b/samples/net/sockets/http_server/sample.yaml new file mode 100644 index 0000000000000..eeaca47d386ed --- /dev/null +++ b/samples/net/sockets/http_server/sample.yaml @@ -0,0 +1,16 @@ +sample: + description: HTTP Server Sample + name: http_server_sample +common: + harness: net + min_ram: 192 + tags: + - http + - net + - server + - socket + platform_exclude: + - native_posix + - native_posix/native/64 +tests: + sample.net.sockets.http.server: {} diff --git a/samples/net/sockets/http_server/sections-rom.ld b/samples/net/sockets/http_server/sections-rom.ld new file mode 100644 index 0000000000000..d51cad087f3e9 --- /dev/null +++ b/samples/net/sockets/http_server/sections-rom.ld @@ -0,0 +1,4 @@ +#include + +ITERABLE_SECTION_ROM(http_resource_desc_test_http_service, Z_LINK_ITERABLE_SUBALIGN) +ITERABLE_SECTION_ROM(http_resource_desc_test_https_service, Z_LINK_ITERABLE_SUBALIGN) diff --git a/samples/net/sockets/http_server/src/ca.der b/samples/net/sockets/http_server/src/ca.der new file mode 100644 index 0000000000000000000000000000000000000000..b1d3e097cadcea344d9b172b4a540ddd57dae71e GIT binary patch literal 783 zcmXqLV&*nzV*I>-nTe5!NkrQ5sj}%5hTSG>R`_Lq-*o88&x{EMylk9WZ60mkc^MhG zSs4sO4228?*qB3En0Yu;D-v@Ha#Ecg4HU$AjSLJ74b2TKfs|#G1iz7?p{1dbkqJ}) zhrT98C1eX2Ss9p{82K51ZsKBUVq|34wV`KTh|5~Fr}HK2;*u6Ee|T=)1e1!xi;caz z-Z<-YoAX;FSjv^Y>1nk1mK&woeDe9}%lcDyWX(QRwR~w^i;nQdPj^{11~o3t{h`P~MygMx|HOPX%tXsnEOA>fFBedeU33Yx~_59X84`-hUap=ymBqc8_=e zXJ0AHhzvS^yv$SG(`m0>x{E^8F`u&eJ(a)Lt@-kAO77R3LtU3@X0!0{EwG4T-8z42 z@`rzOzWi!tInektcgxn$j9`t&6D!)di-I|~?Tzus+tW+_OU zBOPJG^i2Jk=Y-?xkG9WkdAoLt*%`^MJMCs?Hr>7-wUvpPk%4h>utA`KEHKPv`B=nQ zL;`E>*2qT+TP*1l-lo3#WLEad32P1HLDI@B5(Z)o*cI@D6bLgi{%2t|UYw?JA%`W?xT7A$?XFl} zX+CG(vMCB{-fr{pd|lDHbz#Hxjy)3f&IdyKXSc+pI<5$Nwf$h;y-8pD0*`A~XmRRD S-t=3uW{SGEL-b8SsSE(&xI?V~ literal 0 HcmV?d00001 diff --git a/samples/net/sockets/http_server/src/certificate.h b/samples/net/sockets/http_server/src/certificate.h new file mode 100644 index 0000000000000..52a3fa9c8ea18 --- /dev/null +++ b/samples/net/sockets/http_server/src/certificate.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __CERTIFICATE_H__ +#define __CERTIFICATE_H__ + +enum tls_tag { + /** The Certificate Authority public key */ + HTTP_SERVER_CA_CERTIFICATE_TAG, + /** Used for both the public and private server keys */ + HTTP_SERVER_CERTIFICATE_TAG, + /** Used for both the public and private client keys */ + HTTP_SERVER_CLIENT_CERTIFICATE_TAG, + PSK_TAG, +}; + +#if !defined(CONFIG_NET_SAMPLE_CERTS_WITH_SC) +static const unsigned char server_certificate[] = { +#include "https-server-cert.der.inc" +}; + +/* This is the private key in pkcs#8 format. */ +static const unsigned char private_key[] = { +#include "https-server-key.der.inc" +}; + +#else + +static const unsigned char ca_certificate[] = { +#include "ca.der.inc" +}; + +static const unsigned char server_certificate[] = { +#include "server.der.inc" +}; + +/* This is the private key in pkcs#8 format. */ +static const unsigned char private_key[] = { +#include "server_privkey.der.inc" +}; +#endif + +#if defined(CONFIG_MBEDTLS_KEY_EXCHANGE_PSK_ENABLED) +#include CONFIG_NET_SAMPLE_PSK_HEADER_FILE +#endif + +#endif /* __CERTIFICATE_H__ */ diff --git a/samples/net/sockets/http_server/src/dummy_psk.h b/samples/net/sockets/http_server/src/dummy_psk.h new file mode 100644 index 0000000000000..e67107266fda4 --- /dev/null +++ b/samples/net/sockets/http_server/src/dummy_psk.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2019 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __DUMMY_PSK_H__ +#define __DUMMY_PSK_H__ + +static const unsigned char psk[] = {0x01, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, +0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}; +static const char psk_id[] = "PSK_identity"; + +#endif /* __DUMMY_PSK_H__ */ diff --git a/samples/net/sockets/http_server/src/https-server-cert.der b/samples/net/sockets/http_server/src/https-server-cert.der new file mode 100644 index 0000000000000000000000000000000000000000..bfcb335e31c8c37fd5c964276c42a3554abc3f4e GIT binary patch literal 767 zcmXqLV)|{+#Q1mtGZP~d6DPwv0r`WU3|LlA&)ap-DdR6;hMk(GhDiIJZH z=nO8VCPqevV+_^27sZ{kS1zxd!{1vz@zQs9)6L?K?!L`s{JC+`NsopH^5?fNiT_~s zYX3vyA1jYOyCT`$q&%G?~a`7n%!BhJR8NxJ8LeAXMj8@Z}=^MXr+0N72oA?D7SXK(^cx;@x^i)my z(tRQdLffsY{O=sUs>_nL`zz1ck4Bc)1848L{c-s}-C57(WQW}Pc-Q0S^$(^seJXV> z`k(h(@=aU)(3QD6qd7+wwtn5CeIu6c$fmF-&;}Dq9gkyFl=eiRu;X}cf0k(j@>)) zviDuwwgnHgTeUi?cV#qa|IX8EcwRM~bF02W-`hPi@~2*nwpEi<6R~VsvBJr1b>w9C z=I@pJi?_zcR{Tp^^LD$O*V@A~EolM$ literal 0 HcmV?d00001 diff --git a/samples/net/sockets/http_server/src/https-server-key.der b/samples/net/sockets/http_server/src/https-server-key.der new file mode 100644 index 0000000000000000000000000000000000000000..5a4d67372ea41873b1c69e5e9371f6f9d2c5a4bd GIT binary patch literal 1218 zcmV;z1U>sOf&{(-0RS)!1_>&LNQUrs4#*Aqyhl|0)hbn0LB1&4bc}v zYpJJsoDYq6k<#}^HM1Au-R*4w`LUA8NPyrU&$pys@HXnd;WPND#pcu*i-IND8FX-Y z?8a!x@6H;j@V5aqk^j?mZUVXnnkuZ%BEKsi!E!hvHR{@L-DjdJ88{gZMA30Lv~4DZ z*2ccUZ#?d=lspAiPOVdci_{}AX>uo%v^uOK=n$^;p9`jL({sug5z4-C09Gk9RLt5b zTP7))O<$p=xyviE4-fzZsSzwlv6-dHd}pP;6d)3}J9YgF3t-AMV@@HKpnBz{CM^S?O`maE}K1B+DL;kFThAp!#d009Dm0RaH7 z0UN?WjiWr2jsEeC*@o6{wYkmT#(VLVd8DRNY9H7lcyumSAs^->(9OQQ2?gklP=v-L`Gx}l}Epd9hrmzrWPB^HgY2; zPe4Raxg5}uhi0bmA2T-mx#s85P@au1X1#k-Aozb#I!LTKGTvnxtemE5>_qMcl?C!j z#SDE>AF8y#xd(^;D<~3x>O7vYf$#m);|U+hoAc_SeGMv2ZJY+*hf(xP2JocW!N5Gh>(fq?+ts}+mI(T~AV*HlNM#ec4c%-zD89*-5W zoj3kXL$XrmvJT)MNZtpI|8%|ly(cPqz-9@rTLkUAoS)`Hqn^ieJ#j$+4frJ&sg!>B1V-0 zfq+K~=0jzRp|HDkq&((1 z4^sTRXxZnW?SQnSc0LySo5cU}$nFBvF(&`13#hnd=8Im5cx&cpoOA&>{Ra-O67MCI z&{4EyJ}s*>fq2TSrW{4Sogwp8<_}h%jHv>FfdJOeMWN~48AK`JI_MJJDU z8=_kU!4}(t3vFMBFF}{=yak@NV(~@!ROfCqUOUOBjX|tSjTWBzA{$rPt$=l?X&Xg< zGCkVbG5nX724gmcghG9WqLN(tyh?m2u) + + + Zephyr HTTP Server + + +

Welcome to Zephyr HTTP Server!

+

This is a simple HTML file.

+ + diff --git a/samples/net/sockets/http_server/src/main.c b/samples/net/sockets/http_server/src/main.c new file mode 100644 index 0000000000000..dac4499029d4d --- /dev/null +++ b/samples/net/sockets/http_server/src/main.c @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2023, Emna Rekik + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(net_http_server_sample, LOG_LEVEL_DBG); + +static uint8_t index_html_gz[] = { +#include "index.html.gz.inc" +}; + +#if defined(CONFIG_NET_SAMPLE_HTTP_SERVICE) +static uint16_t test_http_service_port = CONFIG_NET_SAMPLE_HTTP_SERVER_SERVICE_PORT; +HTTP_SERVICE_DEFINE(test_http_service, CONFIG_NET_CONFIG_MY_IPV4_ADDR, &test_http_service_port, 1, + 10, NULL); + +struct http_resource_detail_static index_html_gz_resource_detail = { + .common = { + .type = HTTP_RESOURCE_TYPE_STATIC, + .bitmask_of_supported_http_methods = BIT(HTTP_GET), + .content_encoding = "gzip", + }, + .static_data = index_html_gz, + .static_data_len = sizeof(index_html_gz), +}; + +HTTP_RESOURCE_DEFINE(index_html_gz_resource, test_http_service, "/", + &index_html_gz_resource_detail); + +static uint8_t recv_buffer[1024]; + +static int dyn_handler(struct http_client_ctx *client, enum http_data_status status, + uint8_t *buffer, size_t len, void *user_data) +{ +#define MAX_TEMP_PRINT_LEN 32 + static char print_str[MAX_TEMP_PRINT_LEN]; + enum http_method method = client->method; + static size_t processed; + + __ASSERT_NO_MSG(buffer != NULL); + + if (status == HTTP_SERVER_DATA_ABORTED) { + LOG_DBG("Transaction aborted after %zd bytes.", processed); + processed = 0; + return 0; + } + + processed += len; + + snprintf(print_str, sizeof(print_str), "%s received (%zd bytes)", + http_method_str(method), len); + LOG_HEXDUMP_DBG(buffer, len, print_str); + + if (status == HTTP_SERVER_DATA_FINAL) { + LOG_DBG("All data received (%zd bytes).", processed); + processed = 0; + } + + /* This will echo data back to client as the buffer and recv_buffer + * point to same area. + */ + return len; +} + +struct http_resource_detail_dynamic dyn_resource_detail = { + .common = { + .type = HTTP_RESOURCE_TYPE_DYNAMIC, + .bitmask_of_supported_http_methods = + BIT(HTTP_GET) | BIT(HTTP_POST), + }, + .cb = dyn_handler, + .data_buffer = recv_buffer, + .data_buffer_len = sizeof(recv_buffer), + .user_data = NULL, +}; + +HTTP_RESOURCE_DEFINE(dyn_resource, test_http_service, "/dynamic", + &dyn_resource_detail); + +#endif /* CONFIG_NET_SAMPLE_HTTP_SERVICE */ + +#if defined(CONFIG_NET_SAMPLE_HTTPS_SERVICE) +#include "certificate.h" + +static const sec_tag_t sec_tag_list_verify_none[] = { + HTTP_SERVER_CERTIFICATE_TAG, +#if defined(CONFIG_MBEDTLS_KEY_EXCHANGE_PSK_ENABLED) + PSK_TAG, +#endif + }; + +static uint16_t test_https_service_port = CONFIG_NET_SAMPLE_HTTPS_SERVER_SERVICE_PORT; +HTTPS_SERVICE_DEFINE(test_https_service, CONFIG_NET_CONFIG_MY_IPV4_ADDR, + &test_https_service_port, 1, 10, NULL, + sec_tag_list_verify_none, sizeof(sec_tag_list_verify_none)); + +static struct http_resource_detail_static index_html_gz_resource_detail_https = { + .common = { + .type = HTTP_RESOURCE_TYPE_STATIC, + .bitmask_of_supported_http_methods = BIT(HTTP_GET), + .content_encoding = "gzip", + }, + .static_data = index_html_gz, + .static_data_len = sizeof(index_html_gz), +}; + +HTTP_RESOURCE_DEFINE(index_html_gz_resource_https, test_https_service, "/", + &index_html_gz_resource_detail_https); + +#endif /* CONFIG_NET_SAMPLE_HTTPS_SERVICE */ + +static void setup_tls(void) +{ +#if defined(CONFIG_NET_SAMPLE_HTTPS_SERVICE) +#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) + int err; + +#if defined(CONFIG_NET_SAMPLE_CERTS_WITH_SC) + err = tls_credential_add(HTTP_SERVER_CERTIFICATE_TAG, + TLS_CREDENTIAL_CA_CERTIFICATE, + ca_certificate, + sizeof(ca_certificate)); + if (err < 0) { + LOG_ERR("Failed to register CA certificate: %d", err); + } +#endif /* defined(CONFIG_NET_SAMPLE_CERTS_WITH_SC) */ + + err = tls_credential_add(HTTP_SERVER_CERTIFICATE_TAG, + TLS_CREDENTIAL_SERVER_CERTIFICATE, + server_certificate, + sizeof(server_certificate)); + if (err < 0) { + LOG_ERR("Failed to register public certificate: %d", err); + } + + err = tls_credential_add(HTTP_SERVER_CERTIFICATE_TAG, + TLS_CREDENTIAL_PRIVATE_KEY, + private_key, sizeof(private_key)); + if (err < 0) { + LOG_ERR("Failed to register private key: %d", err); + } + +#if defined(CONFIG_MBEDTLS_KEY_EXCHANGE_PSK_ENABLED) + err = tls_credential_add(PSK_TAG, + TLS_CREDENTIAL_PSK, + psk, + sizeof(psk)); + if (err < 0) { + LOG_ERR("Failed to register PSK: %d", err); + } + + err = tls_credential_add(PSK_TAG, + TLS_CREDENTIAL_PSK_ID, + psk_id, + sizeof(psk_id) - 1); + if (err < 0) { + LOG_ERR("Failed to register PSK ID: %d", err); + } +#endif /* defined(CONFIG_MBEDTLS_KEY_EXCHANGE_PSK_ENABLED) */ +#endif /* defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) */ +#endif /* defined(CONFIG_NET_SAMPLE_HTTPS_SERVICE) */ +} + +int main(void) +{ + setup_tls(); + http_server_start(); + return 0; +} diff --git a/samples/net/sockets/http_server/src/not_found_page.html b/samples/net/sockets/http_server/src/not_found_page.html new file mode 100644 index 0000000000000..c4bf66f08ee1e --- /dev/null +++ b/samples/net/sockets/http_server/src/not_found_page.html @@ -0,0 +1,10 @@ + + + + 404 Not Found + + +

404 Not Found

+

The requested resource was not found.

+ + diff --git a/samples/net/sockets/http_server/src/server.der b/samples/net/sockets/http_server/src/server.der new file mode 100644 index 0000000000000000000000000000000000000000..2b664a4bdb2ce64d9e2f92d88aa163d09fd0a073 GIT binary patch literal 693 zcmXqLV%liX#5j{lBd#AN85K^Mn-N{1_Kd8 zAp-$6=1>-99?sN?#N2|MRA)y61#w;@0|P@ta|25QLnDJI34S9(LrX&=BNM0qioTru zik^@kDXYAcf~pQKuP)zo+30&- zdsL`W?2aE_c8Ub||GIFX(J^B};2(|hvNbb#i*={IPiDy3J$tJ&ci@?4VMWV=4&VCe zed*i}zE-9BtHZF9qVy(`sT+oy{ksJnS;amZ=bj_*v&j0}v(Aq0#9UNL&?l7Jj%zSgmElFXmI`$*a$X#J<0sy{~v`PEt_XN{9Kv4~>gL4f1N!%QHKI zrQ!?c2J27zSdg}K;<*B6sk_I_f9h;pVn08MKhh_xqvX6o-GpVLVd2xY%A(A-@$z5W zA6>qdW5xTj>8pMAgl1+(2pQNNu1#6e`Z4|)(;s_#+qPfTUHR2lm>=v4@vgY1wJ2i3 MBGv5t+z!hv0H2m3zyJUM literal 0 HcmV?d00001 diff --git a/samples/net/sockets/http_server/src/server_privkey.der b/samples/net/sockets/http_server/src/server_privkey.der new file mode 100644 index 0000000000000000000000000000000000000000..2269293fe790f2276d24bb62e5347e2d6e5b9cdf GIT binary patch literal 1219 zcmV;!1U&mNf&{+;0RS)!1_>&LNQUrsW5^Br2+u}0)hbn0Nqb)D1e?a zp8&aImt7ZC<#fYJP1_)U~2r>KV;Sh&|uxsbgd+Wqim>*RQP17 zN+MEv!KZt7vePp6ZiiY`B3-!n^tlvMkNVKSfk9}HQT!-(cC48Vb1jwcV*qTso3%p= zQOxF6a;8$l+WAY$!e4q1-E5KRYMu^m^R8*?f@AY^R3J|xb1OnZyf)d%@7jF-n#bq{w z_V0-_an4m}EhQL0a0eyJLfgYUma~AbF4TKUx0e^de%Z>SRLcd3_W}a}009Dm0RaG! zDnpyyq31e$(;f_OH=9&LUdAGWmcg)_U4%E0mb$XCnj(|v8LAHpDTtP?vr%m06^$EJ zZpnTE;H3hyxV~h-V=#o!93n{(=c4FC>CFRKQ-|zrZ1|YaIP9Ik-N}{ zy2<3cWREtdn{P+&|LN!)p7{mIkU|!Zt`2R(;Pg#_N=yr%0Vbp2xs8}fq?+{ zc_*4}atI@kiq43z4w}weDdyNNVAbB4NkS;27(x^wl z9J%b|Zc?z3;9TOiJK1`TB(hH8#oFnIv_f(!BjM<1Tj6}HlvN;ezG8sA3fPIW5j-m0 zrtNkO#a&BWxBBTNvSLcW2Sl27{uX5dfq?+z1E4J%6tye))MkM!X;Uf?8bH~Szs0u6 z*)cirwi2tZjOy@!RH&>XpF<1p??`R2z_I*@ponczcJ58;AN9n=lmO7)}?cPxq}S7K$e| z%_M>YTZQ&9fy?JR7<=cE*V;m7sn;&-g+MhbC^OY4hZM-K;ApehPU?Alz@8rI#vgD* ziCnquP~ol5;^064q^Ud%WP58WWvLp2efKx^7FEPDLiY hlYUM(QL<8v)b323|FJ#?NUg;s4_k!y09su`yV-?yRI&g7 literal 0 HcmV?d00001