diff --git a/include/zephyr/linker/common-rom/common-rom-net.ld b/include/zephyr/linker/common-rom/common-rom-net.ld index e73addfd16655..41a481d8feff7 100644 --- a/include/zephyr/linker/common-rom/common-rom-net.ld +++ b/include/zephyr/linker/common-rom/common-rom-net.ld @@ -15,6 +15,7 @@ #endif #if defined(CONFIG_HTTP_SERVER) + ITERABLE_SECTION_ROM(http_resource_desc, 4) ITERABLE_SECTION_ROM(http_service_desc, 4) #endif 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/hpack.h b/include/zephyr/net/http/hpack.h new file mode 100644 index 0000000000000..dae14d2ca21c6 --- /dev/null +++ b/include/zephyr/net/http/hpack.h @@ -0,0 +1,134 @@ +/** @file + * @brief HTTP HPACK + */ + +/* + * Copyright (c) 2023 Emna Rekik + * + * 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/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..bb3962ba5b1a1 --- /dev/null +++ b/include/zephyr/net/http/server.h @@ -0,0 +1,175 @@ +/* + * 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, + HTTP_RESOURCE_TYPE_REST +}; + +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 b6997334de46b..21081103ed1cf 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" { @@ -42,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 { @@ -53,10 +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) \ + _res_end, ...) \ static const STRUCT_SECTION_ITERABLE(http_service_desc, _name) = { \ .host = _host, \ .port = (uint16_t *)(_port), \ @@ -65,6 +71,12 @@ struct http_service_desc { .backlog = (_backlog), \ .res_begin = (_res_begin), \ .res_end = (_res_end), \ + 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__))))), ())\ } /** @@ -88,6 +100,31 @@ 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) + /** * @brief Define an HTTP service with static resources. * @@ -113,6 +150,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. * diff --git a/samples/net/sockets/http_server/CMakeLists.txt b/samples/net/sockets/http_server/CMakeLists.txt new file mode 100644 index 0000000000000..4b538193ab76f --- /dev/null +++ b/samples/net/sockets/http_server/CMakeLists.txt @@ -0,0 +1,57 @@ +# 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 4) +zephyr_linker_section_ifdef(CONFIG_NET_SAMPLE_HTTP_SERVICE NAME + http_resource_desc_test_http_service + KVMA RAM_REGION GROUP RODATA_REGION SUBALIGN 4) + +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..512bd855be8b7 --- /dev/null +++ b/samples/net/sockets/http_server/README.rst @@ -0,0 +1,97 @@ +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 and can also be upgraded to HTTP/2. + +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:8080/`` +- Using curl: ``curl -v --compressed http://192.0.2.1:8080/`` +- Using ab (Apache Bench): ``ab -n10 http://192.0.2.1:8080/`` + +**With HTTP/2:** + +- Using nghttp client: ``nghttp -v --no-dep http://192.0.2.1:8080/`` +- Using curl: ``curl --http2 -v --compressed http://192.0.2.1:8080/`` +- Using h2load: ``h2load -n10 http://192.0.2.1:8080/`` + +Server Customization +--------------------- + +The server contains several parameters that can be customized based on the requirements. These are the configurable parameters: + +- ``HTTP_SERVER_SERVICE_PORT``: Configures Service Port. + +- ``HTTP_SERVER_MAX_CLIENTS``: Defines the maximum number of HTTP/2 clients that the server can handle simultaneously. + +- ``HTTP_SERVER_MAX_STREAMS``: Specifies the maximum number of HTTP/2 streams that can be established per client. + +- ``HTTP_SERVER_CLIENT_BUFFER_SIZE``: Defines the buffer size allocated for each client. + +- ``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 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..55b028acd2476 --- /dev/null +++ b/samples/net/sockets/http_server/sample.yaml @@ -0,0 +1,17 @@ +sample: + description: HTTP Server Sample + name: http_server_sample +common: + filter: TOOLCHAIN_HAS_NEWLIB == 1 + 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..09a6b9398f670 --- /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, 4) +ITERABLE_SECTION_ROM(http_resource_desc_test_https_service, 4) 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 0000000000000..b1d3e097cadce Binary files /dev/null and b/samples/net/sockets/http_server/src/ca.der differ 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 0000000000000..bfcb335e31c8c Binary files /dev/null and b/samples/net/sockets/http_server/src/https-server-cert.der differ 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 0000000000000..5a4d67372ea41 Binary files /dev/null and b/samples/net/sockets/http_server/src/https-server-key.der differ diff --git a/samples/net/sockets/http_server/src/index.html b/samples/net/sockets/http_server/src/index.html new file mode 100644 index 0000000000000..ad654e6a3051b --- /dev/null +++ b/samples/net/sockets/http_server/src/index.html @@ -0,0 +1,10 @@ + + + + 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..dbd123df6d530 --- /dev/null +++ b/samples/net/sockets/http_server/src/main.c @@ -0,0 +1,188 @@ +/* + * 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); + +struct http_resource_detail_rest add_two_numbers_detail = { + .common = { + .type = HTTP_RESOURCE_TYPE_REST, + .bitmask_of_supported_http_methods = BIT(HTTP_POST), + }, +}; + +HTTP_RESOURCE_DEFINE(add_two_numbers, test_http_service, "/add", &add_two_numbers_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 0000000000000..2b664a4bdb2ce Binary files /dev/null and b/samples/net/sockets/http_server/src/server.der differ 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 0000000000000..2269293fe790f Binary files /dev/null and b/samples/net/sockets/http_server/src/server_privkey.der differ diff --git a/subsys/net/lib/CMakeLists.txt b/subsys/net/lib/CMakeLists.txt index 40795ac5a1696..78c6a44fbccbc 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_PARSER_URL OR CONFIG_HTTP_PARSER OR CONFIG_HTTP_CLIENT OR CONFIG_HTTP_SERVER) add_subdirectory(http) endif() diff --git a/subsys/net/lib/http/CMakeLists.txt b/subsys/net/lib/http/CMakeLists.txt index 5d9d4a06a3254..663c45d498c96 100644 --- a/subsys/net/lib/http/CMakeLists.txt +++ b/subsys/net/lib/http/CMakeLists.txt @@ -11,3 +11,11 @@ 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..d017c9e1c0d30 100644 --- a/subsys/net/lib/http/Kconfig +++ b/subsys/net/lib/http/Kconfig @@ -32,13 +32,90 @@ 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 + 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 64 + 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 - HTTP server support. - Note: this is a work-in-progress + 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 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_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; +} 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..f3e48dbc587f9 --- /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) { + dynamic_detail = + (struct http_resource_detail_dynamic *)detail; + + if (dynamic_detail->holder == client) { + /* 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) { + 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..52429ed52edd5 --- /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; +} 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: {}