diff --git a/include/net/http.h b/include/net/http.h index 8de5694c5ba..dcd5e697b1c 100644 --- a/include/net/http.h +++ b/include/net/http.h @@ -7,13 +7,42 @@ #ifndef __HTTP_H__ #define __HTTP_H__ +#if defined(CONFIG_HTTPS) +#if defined(CONFIG_MBEDTLS) +#if !defined(CONFIG_MBEDTLS_CFG_FILE) +#include "mbedtls/config.h" +#else +#include CONFIG_MBEDTLS_CFG_FILE +#endif /* CONFIG_MBEDTLS_CFG_FILE */ + +#if defined(MBEDTLS_PLATFORM_C) +#include "mbedtls/platform.h" +#else +#include +#define mbedtls_time_t time_t +#define MBEDTLS_EXIT_SUCCESS EXIT_SUCCESS +#define MBEDTLS_EXIT_FAILURE EXIT_FAILURE +#endif /* MBEDTLS_PLATFORM_C */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif /* CONFIG_MBEDTLS */ +#endif /* CONFIG_HTTPS */ + +#define HTTP_CRLF "\r\n" + #if defined(CONFIG_HTTP_CLIENT) #include #include -#define HTTP_CRLF "\r\n" - /* Is there more data to come */ enum http_final_call { HTTP_DATA_MORE = 0, @@ -395,20 +424,17 @@ int http_client_init(struct http_client_ctx *http_ctx, * @param http_ctx HTTP context. */ void http_client_release(struct http_client_ctx *http_ctx); -#endif +#endif /* CONFIG_HTTP_CLIENT */ #if defined(CONFIG_HTTP_SERVER) #include - -#if defined(CONFIG_HTTP_PARSER) #include -#endif -/* HTTP server context state */ -enum HTTP_CTX_STATE { - HTTP_CTX_FREE = 0, - HTTP_CTX_IN_USE +struct http_server_ctx; + +enum http_url_flags { + HTTP_URL_STANDARD = 0, }; /* HTTP header fields struct */ @@ -417,54 +443,441 @@ struct http_field_value { * containing the HTTP field name */ const char *key; - /** Length of the field name */ - u16_t key_len; /** Value, this variable will point to the beginning of the string * containing the field value */ const char *value; + + /** Length of the field name */ + u16_t key_len; + /** Length of the field value */ u16_t value_len; }; +typedef int (*http_url_cb_t)(struct http_server_ctx *ctx); + +/* HTTP root URL struct, used for pattern matching */ +struct http_root_url { + /** URL */ + const char *root; + + /** Callback that is called when this URL is received */ + http_url_cb_t write_cb; + + /** Length of the URL */ + u16_t root_len; + + /** Flags for this URL (values are from enum http_url_flags) */ + u8_t flags; + + /** Is this URL resource used or not */ + u8_t is_used; +}; + +/* Collection of URLs that this server will handle */ +struct http_server_urls { + /* First item is the default handler and it is always there. + */ + struct http_root_url default_url; + struct http_root_url urls[CONFIG_HTTP_SERVER_NUM_URLS]; +}; + +#if defined(CONFIG_HTTPS) +/* Internal information for managing HTTPS data */ +struct https_context { + struct net_pkt *rx_pkt; + struct net_buf *frag; + struct k_sem tx_sem; + struct k_fifo rx_fifo; + int remaining; +}; + +/** + * @typedef https_server_cert_cb_t + * @brief Callback used when the API user needs to setup the + * HTTPS certs. + * + * @param ctx HTTPS server context. + * @param cert MBEDTLS certificate + * @param pkey MBEDTLS private key + * + * @return 0 if ok, <0 if there is an error + */ +typedef int (*https_server_cert_cb_t)(struct http_server_ctx *ctx, + mbedtls_x509_crt *cert, + mbedtls_pk_context *pkey); + +/** + * @typedef https_entropy_src_cb_t + * @brief Callback used when the API user needs to setup the entropy source. + * @detail This is the same as mbedtls_entropy_f_source_ptr callback. + * + * @param data Callback-specific data pointer + * @param output Data to fill + * @param len Maximum size to provide + * @param olen The actual amount of bytes put into the buffer (Can be 0) + */ +typedef int (*https_entropy_src_cb_t)(void *data, unsigned char *output, + size_t len, size_t *olen); +#endif /* CONFIG_HTTPS */ + +typedef int (*http_send_data_t)(struct net_pkt *pkt, + net_context_send_cb_t cb, + s32_t timeout, + void *token, + void *user_data); + /* The HTTP server context struct */ struct http_server_ctx { - u8_t state; + /** Collection of URLs that this server context will handle */ + struct http_server_urls *urls; - /** Collection of header fields */ - struct http_field_value field_values[CONFIG_HTTP_HEADER_FIELD_ITEMS]; - /** Number of header field elements */ - u16_t field_values_ctr; +#if defined(CONFIG_NET_IPV4) + /** IPv4 stack network context for listening */ + struct net_context *net_ipv4_ctx; +#endif +#if defined(CONFIG_NET_IPV6) + /** IPv6 stack network context for listening */ + struct net_context *net_ipv6_ctx; +#endif - /** HTTP Request URL */ - const char *url; - /** URL's length */ - u16_t url_len; + /** Function that is called when data is received from network. */ + net_context_recv_cb_t recv_cb; - /**IP stack network context */ - struct net_context *net_ctx; + /** Function that is called when data is sent to network. */ + http_send_data_t send_data; /** Network timeout */ s32_t timeout; -#if defined(CONFIG_HTTP_PARSER) - /** HTTP parser */ - struct http_parser parser; - /** HTTP parser settings */ - struct http_parser_settings parser_settings; -#endif + /** Running status of the server. If true, then the server is enabled. + * If false then it is disabled and will not serve clients. + * The server is disabled by default after initialization and will need + * to be enabled manually. + */ + bool enabled; + + /** Is this instance HTTPS or not. + */ + bool is_https; + + struct { + /** From which net_context the request came from */ + struct net_context *net_ctx; + + /** HTTP parser */ + struct http_parser parser; + + /** HTTP parser settings */ + struct http_parser_settings settings; + + /** Collection of header fields */ + struct http_field_value + field_values[CONFIG_HTTP_HEADER_FIELD_ITEMS]; + + /** HTTP Request URL */ + const char *url; + + /** Where the request is stored, this is to be provided + * by the user. + */ + u8_t *request_buf; + + /** Request buffer maximum length */ + size_t request_buf_len; + + /** Length of the data in the request buf. */ + size_t data_len; + + /** Number of header field elements */ + u16_t field_values_ctr; + + /** URL's length */ + u16_t url_len; + } req; + +#if defined(CONFIG_HTTPS) + struct { + /** HTTPS stack for mbedtls library. */ + u8_t *stack; + + /** HTTPS stack size. */ + int stack_size; + + /** HTTPS thread id */ + k_tid_t tid; + + /** HTTPS thread */ + struct k_thread thread; + + /** Memory pool for RX data */ + struct k_mem_pool *pool; + + /** mbedtls related configuration. */ + struct { + struct https_context ssl_ctx; + https_server_cert_cb_t cert_cb; + https_entropy_src_cb_t entropy_src_cb; + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; + mbedtls_ssl_context ssl; + mbedtls_ssl_config conf; + mbedtls_x509_crt srvcert; + mbedtls_pk_context pkey; + u8_t *personalization_data; + size_t personalization_data_len; + } mbedtls; + } https; +#endif /* CONFIG_HTTPS */ }; +/** + * @brief Initialize user supplied HTTP context. + * + * @detail Caller can set the various callback fields in http_ctx and + * http_ctx.req.parser after this call if needed. + * + * @param http_ctx HTTP context. + * @param urls Array of URLs that the server instance will serve. If the + * server receives a HTTP request into one of the URLs, it will call user + * supplied callback. If no such URL is registered, a default handler will + * be called (if set by the user). If no data handler is found, the request + * is dropped. + * @param server_addr Socket address of the local network interface and TCP + * port where the data is being waited. If the socket family is set to + * AF_UNSPEC, then both IPv4 and IPv6 is started to be listened. If the + * address is set to be INADDR_ANY (for IPv4) or unspecified address (all bits + * zeros for IPv6), then the HTTP server will select proper IP address to bind + * to. If caller has not specified HTTP listening port, then port 80 is being + * listened. The parameter can be left NULL in which case a listener to port 80 + * using IPv4 and IPv6 is created. Note that if IPv4 or IPv6 is disabled, then + * the corresponding disabled service listener is not created. + * @param request_buf Caller supplied buffer where the HTTP request will be + * stored + * @param request_buf_len Length of the caller suppied buffer. + * @param server_banner Print information about started service. This is only + * printed if HTTP debugging is activated. The parameter can be set to NULL if + * no extra prints are needed. + * + * @return Return 0 if ok, <0 if error. + */ +int http_server_init(struct http_server_ctx *http_ctx, + struct http_server_urls *urls, + struct sockaddr *server_addr, + u8_t *request_buf, + size_t request_buf_len, + const char *server_banner); + +#if defined(CONFIG_HTTPS) +/** + * @brief Initialize user supplied HTTP context. This function must be + * used if HTTPS server is created. + * + * @detail Caller can set the various callback fields in http_ctx and + * http_ctx.req.parser after this call if needed. + * + * @param http_ctx HTTP context. + * @param urls Array of URLs that the server instance will serve. If the + * server receives a HTTP request into one of the URLs, it will call user + * supplied callback. If no such URL is registered, a default handler will + * be called (if set by the user). If no data handler is found, the request + * is dropped. + * @param server_addr Socket address of the local network interface and TCP + * port where the data is being waited. If the socket family is set to + * AF_UNSPEC, then both IPv4 and IPv6 is started to be listened. If the + * address is set to be INADDR_ANY (for IPv4) or unspecified address (all bits + * zeros for IPv6), then the HTTP server will select proper IP address to bind + * to. If caller has not specified HTTP listening port, then port 80 is being + * listened. The parameter can be left NULL in which case a listener to port 80 + * using IPv4 and IPv6 is created. Note that if IPv4 or IPv6 is disabled, then + * the corresponding disabled service listener is not created. + * @param request_buf Caller supplied buffer where the HTTP request will be + * stored + * @param request_buf_len Length of the caller suppied buffer. + * @param server_banner Print information about started service. This is only + * printed if HTTP debugging is activated. The parameter can be set to NULL if + * no extra prints are needed. + * @param personalization_data Personalization data (Device specific + * identifiers) for random number generator. (Can be NULL). + * @param personalization_data_len Length of the personalization data. + * @param cert_cb User supplied callback that setups the certifacates. + * @param entropy_src_cb User supplied callback that setup the entropy. This + * can be set to NULL, in which case default entropy source is used. + * @param pool Memory pool for RX data reads. + * @param https_stack HTTPS thread stack. + * @param https_stack_len HTTP thread stack size. + * + * @return Return 0 if ok, <0 if error. + */ +int https_server_init(struct http_server_ctx *http_ctx, + struct http_server_urls *urls, + struct sockaddr *server_addr, + u8_t *request_buf, + size_t request_buf_len, + const char *server_banner, + u8_t *personalization_data, + size_t personalization_data_len, + https_server_cert_cb_t cert_cb, + https_entropy_src_cb_t entropy_src_cb, + struct k_mem_pool *pool, + u8_t *https_stack, + size_t https_stack_len); +#endif /* CONFIG_HTTPS */ + +/** + * @brief Release all the resources allocated for HTTP context. + * + * @param http_ctx HTTP context. + */ +void http_server_release(struct http_server_ctx *http_ctx); + +/** + * @brief Enable HTTP server that is related to this context. + * + * @detail The HTTP server will start to serve request after this. + * + * @param http_ctx HTTP context. + * + * @return Previous status of the server. + */ +bool http_server_enable(struct http_server_ctx *http_ctx); + +/** + * @brief Disable HTTP server that is related to this context. + * + * @detail The HTTP server will stop to serve request after this. + * + * @param http_ctx HTTP context. + * + * @return Previous status of the server. + */ +bool http_server_disable(struct http_server_ctx *http_ctx); + +/** + * @brief Helper function that can be used to fill the server (local) sockaddr + * struct. + * + * @param addr Sockaddr struct to be filled. + * @param myaddr Address that the local IP address. If left NULL, then the + * proper system address is used. + * @param port TCP port to use. + * + * @return 0 if ok, <0 if error. + */ +int http_server_set_local_addr(struct sockaddr *addr, const char *myaddr, + u16_t port); + +/** + * @brief Add a handler for a given URL. + * + * @detail Register a handler which is called if the server receives a + * request to a given URL. + * + * @param urls URL struct that will contain all the URLs the user wants to + * register. + * @param url URL string. + * @param flags Flags for the URL. + * @param write_cb Callback that is called when URL is requested. + * + * @return NULL if the URL is already registered, pointer to URL if + * registering was ok. + */ +struct http_root_url *http_server_add_url(struct http_server_urls *urls, + const char *url, u8_t flags, + http_url_cb_t write_cb); + +/** + * @brief Delete the handler for a given URL. + * + * @detail Unregister the previously registered URL handler. + * + * @param urls URL struct that will contain all the URLs the user has + * registered. + * @param url URL string. + * + * @return 0 if ok, <0 if error. + */ +int http_server_del_url(struct http_server_urls *urls, const char *url); + +/** + * @brief Add default URL handler. + * + * @detail If no URL handler is found, then call this handler. There can + * be only one default handler in the URL struct. + * + * @param urls URL struct that will contain all the URLs the user has + * registered. + * @param write_cb Callback that is called when non-registered URL is + * requested. + * + * @return NULL if default URL is already registered, pointer to default + * URL if registering was ok. + */ +struct http_root_url *http_server_add_default(struct http_server_urls *urls, + http_url_cb_t write_cb); + +/** + * @brief Delete the default URL handler. + * + * @detail Unregister the previously registered default URL handler. + * + * @param urls URL struct that will contain all the URLs the user has + * registered. + * + * @return 0 if ok, <0 if error. + */ +int http_server_del_default(struct http_server_urls *urls); + +/** + * @brief Send HTTP response to client. + * + * @param ctx HTTP context. + * @param http_header HTTP headers to send. + * @param html_payload HTML payload to send. + * + * @return 0 if ok, <0 if error. + */ int http_response(struct http_server_ctx *ctx, const char *http_header, const char *html_payload); +/** + * @brief Send HTTP 400 response to client. + * + * @detail HTTP 400 error code indicates a Bad Request + * + * @param ctx HTTP context. + * @param html_payload HTML payload to send. + * + * @return 0 if ok, <0 if error. + */ int http_response_400(struct http_server_ctx *ctx, const char *html_payload); +/** + * @brief Send HTTP 403 response to client. + * + * @detail HTTP 403 error code indicates a Forbidden Request + * + * @param ctx HTTP context. + * @param html_payload HTML payload to send. + * + * @return 0 if ok, <0 if error. + */ int http_response_403(struct http_server_ctx *ctx, const char *html_payload); +/** + * @brief Send HTTP 404 response to client. + * + * @detail HTTP 404 error code indicates a Not Found resource. + * + * @param ctx HTTP context. + * @param html_payload HTML payload to send. + * + * @return 0 if ok, <0 if error. + */ int http_response_404(struct http_server_ctx *ctx, const char *html_payload); -#endif +#endif /* CONFIG_HTTP_SERVER */ -#endif +#endif /* __HTTP_H__ */ diff --git a/samples/net/http_server/Makefile b/samples/net/http_server/Makefile index 30424074560..049778d9bc0 100644 --- a/samples/net/http_server/Makefile +++ b/samples/net/http_server/Makefile @@ -4,10 +4,8 @@ # SPDX-License-Identifier: Apache-2.0 # -BOARD ?= frdm_k64f +BOARD ?= qemu_x86 CONF_FILE ?= prj_$(BOARD).conf include $(ZEPHYR_BASE)/Makefile.inc -ifeq ($(BOARD), qemu_x86) - include $(ZEPHYR_BASE)/samples/net/common/Makefile.ipstack -endif +include $(ZEPHYR_BASE)/samples/net/common/Makefile.ipstack diff --git a/samples/net/http_server/README.rst b/samples/net/http_server/README.rst index c6be31e5a9e..66ba16a1861 100644 --- a/samples/net/http_server/README.rst +++ b/samples/net/http_server/README.rst @@ -7,14 +7,13 @@ Overview ******** The HTTP Server sample application for Zephyr implements a basic TCP server -on top of the HTTP Parser Library that is able to receive HTTP 1.1 requests, +on top of the HTTP Server Library that is able to receive HTTP 1.1 requests, parse them and write back the responses. -This sample code generates HTTP 1.1 responses dynamically -and does not serve content from a file system. The source code includes -examples on how to write HTTP 1.1 responses: 200 OK, 400 Bad Request, -403 Forbidden, 404 Not Found and soft HTTP errors like 200 OK with a 404 -Not Found HTML message. +This sample code generates HTTP 1.1 responses dynamically and does not serve +content from a file system. The source code includes examples on how to write +HTTP 1.1 responses: 200 OK, 400 Bad Request, 403 Forbidden, 404 Not Found and +soft HTTP errors like 200 OK with a 404 Not Found HTML message. The source code for this sample application can be found at: :file:`samples/net/http_server`. @@ -23,39 +22,24 @@ Requirements ************ - Linux machine with wget and the screen terminal emulator -- Freedom Board (FRDM-K64F) +- Either QEMU or real device like Freedom Board (FRDM-K64F) - LAN for testing purposes (Ethernet) Building and Running ******************** -Currently, the HTTP Server application is configured to listen at port 80, -This value is defined in the :file:`samples/net/http_server/src/config.h` -file: +Currently, the HTTP Server application is configured to listen at port 80. +If you want to modify the http-server sample application, please check +the configuration settings in :file:`samples/net/http_server/src/main.c` file +and also in the :file:`samples/net/http_server/src/config.h` file. -.. code-block:: c - - #define ZEPHYR_PORT 80 - -Open the project configuration file for your platform, for example the -:file:`prj_frdm_k64f.conf` file is the configuration file for the -:ref:`frdm_k64f` board. For IPv4 networks, set the following variables: - -.. code-block:: console - - CONFIG_NET_IPV4=y - CONFIG_NET_IPV6=n - -IPv6 is the preferred routing technology for this sample application, -if CONFIG_NET_IPV6=y is set, the value of CONFIG_NET_IPV4=y is ignored. - -This sample code only supports static IP addresses that are defined in the -project configuration file: +This sample code supports both static and dynamic (DHCPv4) IP addresses that +can be defined in the project configuration file: .. code-block:: console CONFIG_NET_APP_MY_IPV6_ADDR="2001:db8::1" - CONFIG_NET_APP_MY_IPV4_ADDR="192.168.1.101" + CONFIG_NET_APP_MY_IPV4_ADDR="192.0.2.1" Adding URLs =========== @@ -66,9 +50,11 @@ following lines: .. code-block:: c - http_url_default_handler(http_write_soft_404_not_found); - http_url_add("/headers", HTTP_URL_STANDARD, http_write_header_fields); - http_url_add("/index.html", HTTP_URL_STANDARD, http_write_it_works); + http_server_add_default(&http_urls, http_response_soft_404); + http_server_add_url(&http_urls, "/headers", HTTP_URL_STANDARD, + http_response_header_fields); + http_server_add_url(&http_urls, "/index.html", HTTP_URL_STANDARD, + http_response_it_works); The first line defines how Zephyr will deal with unknown URLs. In this case, it will respond with a soft HTTP 404 status code, i.e. an HTTP 200 OK status @@ -114,19 +100,20 @@ serial console under other operating systems. Sample Output ============= -Assume that this HTTP server is configured to listen at 192.168.1.101 port 80. +Assume in this example that this HTTP server is configured to listen at +IPv4 address 192.168.1.120 and IPv6 address 2001:db8::1 port 80. On your Linux host computer, open a terminal window and type: .. code-block:: console - wget 192.168.1.101/index.html + wget 192.168.1.120/index.html wget will show: .. code-block:: console - --2017-01-17 00:37:44-- http://192.168.1.101/ - Connecting to 192.168.1.101:80... connected. + --2017-01-17 00:37:44-- http://192.168.1.120/ + Connecting to 192.168.1.120:80... connected. HTTP request sent, awaiting response... 200 OK Length: unspecified [text/html] Saving to: 'index.html' @@ -146,32 +133,38 @@ The screen application will display the following information: .. code-block:: console - [dev/eth_mcux] [INF] eth_0_init: Enabled 100M full-duplex mode. - [dev/eth_mcux] [DBG] eth_0_init: MAC 00:04:9f:c9:29:6e - Zephyr HTTP Server - Address: 192.168.1.101, port: 80 - - ---------------------------------------------------- - [print_client_banner:42] Connection accepted - Address: 192.168.1.10, port: 54327 - [http_ctx_get:268] Free ctx found, index: 0 - [http_write:59] net_pkt_get_tx, rc: 0 - [http_write:82] net_context_send: 0 - [http_rx_tx:86] Connection closed by peer + [dev/eth_mcux] [DBG] eth_0_init: MAC 00:04:9f:52:44:02 + [sample/net] [INF] net_sample_app_init: Run HTTPS server + [sample/net] [INF] setup_dhcpv4: Running + [dev/eth_mcux] [DBG] eth_0_init: MAC 00:04:9f:f1:80:9e + [sample/net] [INF] net_sample_app_init: Run HTTPS server + [sample/net] [INF] setup_dhcpv4: Running dhcpv4 client... + [sample/net] [INF] ipv6_event_handler: IPv6 address: 2001:db8::1 + [dev/eth_mcux] [INF] eth_mcux_phy_event: Enabled 10M half-duplex mode. + [sample/net] [INF] ipv4_addr_add_handler: IPv4 address: 192.168.1.120 + [sample/net] [INF] ipv4_addr_add_handler: Lease time: 86400 seconds + [sample/net] [INF] ipv4_addr_add_handler: Subnet: 255.255.255.0 + [sample/net] [INF] ipv4_addr_add_handler: Router: 192.168.1.1 + [https/server] [INF] new_server: Zephyr HTTPS Server (0x20002370) + [https/server] [DBG] https_handler: (0x2000b5b4): HTTPS handler starting + [https/server] [INF] new_server: Zephyr HTTP Server (0x20001840) + [https/server] [INF] new_client: HTTP connection from 192.168.1.107:44410 (0x20006af4) + [https/server] [DBG] http_recv: (0x2000d6b4): Received 336 bytes data + [https/server] [DBG] http_process_recv: (0x2000d6b4): Calling handler 0x00000d89 context 0x20001840 To obtain the HTTP Header Fields web page, use the following command: .. code-block:: console - wget 192.168.1.101/headers -O index.html + wget 192.168.1.120/headers -O index.html wget will show: .. code-block:: console - --2017-01-19 22:09:55-- http://192.168.1.101/headers - Connecting to 192.168.1.101:80... connected. + --2017-01-19 22:09:55-- http://192.168.1.120/headers + Connecting to 192.168.1.120:80... connected. HTTP request sent, awaiting response... 200 OK Length: unspecified [text/html] Saving to: 'index.html' @@ -190,7 +183,7 @@ This is the HTML file generated by Zephyr and downloaded by wget:
  • User-Agent: Wget/1.16 (linux-gnu)
  • Accept: */*
  • -
  • Host: 192.168.1.101
  • +
  • Host: 192.168.1.120
  • Connection: Keep-Alive

HTTP Method: GET

@@ -203,7 +196,7 @@ To test the 404 Not Found soft error, use the following command: .. code-block:: console - wget 192.168.1.101/not_found -O index.html + wget 192.168.1.120/not_found -O index.html Zephyr will generate an HTTP response with the following header: @@ -224,32 +217,11 @@ and this is the HTML message that wget will save:

404 Not Found

-To test the HTTP Basic Authentication functionality, use the -following command: - -.. code-block:: console - - wget 192.168.1.101/auth -O index.html --user=zephyr --password=0123456789 - -the :file:`index.html` file will contain the following information: - -.. code-block:: html - - - - Zephyr HTTP Server - - -

Zephyr HTTP server

-

user: zephyr, password: 0123456789

- - - HTTPS Server ============ The sample code also includes a HTTPS (HTTP over TLS) server example -running side by side with the HTTP server, this server runs on qemu. +running side by side with the HTTP server, this server runs on QEMU. In order to compile and run the code execute: .. code-block:: console @@ -266,25 +238,23 @@ The app will show the following on the screen: .. code-block:: console - Zephyr HTTP Server - Address: 192.0.2.1, port: 80 - Zephyr HTTPS Server - Address: 192.0.2.1, port: 443 - failed - ! mbedtls_ssl_handshake returned -29312 + [https/server] [INF] new_client: HTTPS connection from 192.168.1.107:35982 (0x20006b4c) + [https/server] [DBG] https_handler: (0x2000b5b4): Read HTTPS request + [https/server] [DBG] https_handler: (0x2000b5b4): Write HTTPS response + [https/server] [DBG] http_process_recv: (0x2000b5b4): Calling handler 0x00000ce9 context 0x20002370 Now execute the following command on a different terminal window .. code-block:: console - wget https://192.0.2.1 --no-check-certificate + wget https://192.168.1.120 --no-check-certificate This will be shown on the screen .. code-block:: console - Connecting to 192.0.2.1:443... connected. - WARNING: cannot verify 192.0.2.1's certificate + Connecting to 192.168.1.120:443... connected. + WARNING: cannot verify 192.168.1.120's certificate Unable to locally verify the issuer's authority. HTTP request sent, awaiting response... 200 OK Length: unspecified [text/html] @@ -304,7 +274,4 @@ Known Issues and Limitations - Currently, this sample application only generates HTTP responses in chunk transfer mode. -- Clients must close the connection to allow the HTTP server to release - the network context and accept another connection. -- The use of mbedTLS and IPv6 takes more than the available ram for the - emulation platform, so only IPv4 works for now in QEMU. +- Basic authentication is not yet implemented. diff --git a/samples/net/http_server/prj_arduino_101.conf b/samples/net/http_server/prj_arduino_101.conf index 17a36bcc680..52c6313fd0c 100644 --- a/samples/net/http_server/prj_arduino_101.conf +++ b/samples/net/http_server/prj_arduino_101.conf @@ -11,26 +11,38 @@ CONFIG_NET_PKT_TX_COUNT=16 CONFIG_NET_BUF_RX_COUNT=16 CONFIG_NET_BUF_TX_COUNT=16 -CONFIG_NET_IPV6_RA_RDNSS=y -CONFIG_NET_IFACE_UNICAST_IPV4_ADDR_COUNT=3 +CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=2 +CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=4 + +CONFIG_NET_MAX_CONTEXTS=16 CONFIG_TEST_RANDOM_GENERATOR=y CONFIG_STDOUT_CONSOLE=y -CONFIG_HTTP_SERVER=y -CONFIG_HTTP_PARSER=y +CONFIG_SYS_LOG_SHOW_COLOR=y +CONFIG_SYS_LOG_NET_LEVEL=4 +CONFIG_NET_DEBUG_HTTP=y + +CONFIG_HTTP_SERVER=y -# Enable IPv6 support CONFIG_NET_IPV6=y -# Enable IPv4 support -CONFIG_NET_IPV4=n +CONFIG_NET_IPV4=y +#CONFIG_NET_DHCPV4=y + +CONFIG_HTTPS=y +CONFIG_MBEDTLS=y +CONFIG_MBEDTLS_BUILTIN=y +CONFIG_MBEDTLS_CFG_FILE="config-mini-tls1_2.h" CONFIG_NET_APP_SETTINGS=y CONFIG_NET_APP_MY_IPV6_ADDR="2001:db8::1" -CONFIG_NET_APP_MY_IPV4_ADDR="192.168.1.101" +CONFIG_NET_APP_MY_IPV4_ADDR="192.0.2.1" -CONFIG_NET_MAX_CONTEXTS=16 +CONFIG_NET_SHELL=y + +CONFIG_NET_MGMT=y +CONFIG_NET_MGMT_EVENT=y # ENC28J60 Ethernet Device CONFIG_ETH_ENC28J60=y @@ -42,4 +54,3 @@ CONFIG_ETH_ENC28J60_0_MAC5=0x36 # Arduino 101 CONFIG_SPI=y - diff --git a/samples/net/http_server/prj_bt.conf b/samples/net/http_server/prj_bt.conf index f3830947c5d..713e4d85cee 100644 --- a/samples/net/http_server/prj_bt.conf +++ b/samples/net/http_server/prj_bt.conf @@ -1,7 +1,6 @@ CONFIG_NETWORKING=y CONFIG_NET_TCP=y CONFIG_RANDOM_GENERATOR=y -CONFIG_NET_ARP=y CONFIG_NET_LOG=y CONFIG_INIT_STACKS=y @@ -10,24 +9,32 @@ CONFIG_NET_PKT_TX_COUNT=16 CONFIG_NET_BUF_RX_COUNT=16 CONFIG_NET_BUF_TX_COUNT=16 -CONFIG_NET_IPV6_RA_RDNSS=y -CONFIG_NET_IFACE_UNICAST_IPV4_ADDR_COUNT=3 +CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=3 +CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=4 CONFIG_STDOUT_CONSOLE=y -CONFIG_HTTP_SERVER=y -CONFIG_HTTP_PARSER=y +CONFIG_SYS_LOG_SHOW_COLOR=y +CONFIG_SYS_LOG_NET_LEVEL=4 +CONFIG_NET_DEBUG_HTTP=y + +CONFIG_HTTP_SERVER=y -# Enable IPv6 support CONFIG_NET_IPV6=y -# Enable IPv4 support CONFIG_NET_IPV4=n +CONFIG_HTTPS=y +CONFIG_MBEDTLS=y +CONFIG_MBEDTLS_BUILTIN=y +CONFIG_MBEDTLS_CFG_FILE="config-mini-tls1_2.h" + CONFIG_NET_APP_SETTINGS=y CONFIG_NET_APP_MY_IPV6_ADDR="2001:db8::1" -CONFIG_NET_APP_MY_IPV4_ADDR="192.168.1.101" -CONFIG_NET_MAX_CONTEXTS=16 +CONFIG_NET_SHELL=y + +CONFIG_NET_MGMT=y +CONFIG_NET_MGMT_EVENT=y CONFIG_BLUETOOTH=y CONFIG_BLUETOOTH_DEBUG_LOG=y @@ -40,7 +47,7 @@ CONFIG_TEST_RANDOM_GENERATOR=y CONFIG_NET_L2_BLUETOOTH=y CONFIG_NET_L2_BLUETOOTH_ZEP1656=y CONFIG_NET_DEBUG_L2_BLUETOOTH=y -CONFIG_SYS_LOG_SHOW_COLOR=y CONFIG_NET_STATISTICS=y -CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=3 -CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=2 + +# This sample is meant to be compilable in QEMU only +CONFIG_RAM_SIZE=300 diff --git a/samples/net/http_server/prj_frdm_k64f.conf b/samples/net/http_server/prj_frdm_k64f.conf index 1975cc10097..c6336a4d247 100644 --- a/samples/net/http_server/prj_frdm_k64f.conf +++ b/samples/net/http_server/prj_frdm_k64f.conf @@ -11,21 +11,33 @@ CONFIG_NET_PKT_TX_COUNT=16 CONFIG_NET_BUF_RX_COUNT=16 CONFIG_NET_BUF_TX_COUNT=16 -CONFIG_NET_IPV6_RA_RDNSS=y -CONFIG_NET_IFACE_UNICAST_IPV4_ADDR_COUNT=3 +CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=2 +CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=4 + +CONFIG_NET_MAX_CONTEXTS=16 CONFIG_STDOUT_CONSOLE=y -CONFIG_HTTP_SERVER=y -CONFIG_HTTP_PARSER=y +CONFIG_SYS_LOG_SHOW_COLOR=y +CONFIG_SYS_LOG_NET_LEVEL=4 +CONFIG_NET_DEBUG_HTTP=y -# Enable IPv6 support -CONFIG_NET_IPV6=n -# Enable IPv4 support +CONFIG_HTTP_SERVER=y + +CONFIG_NET_IPV6=y CONFIG_NET_IPV4=y +CONFIG_NET_DHCPV4=y + +CONFIG_HTTPS=y +CONFIG_MBEDTLS=y +CONFIG_MBEDTLS_BUILTIN=y +CONFIG_MBEDTLS_CFG_FILE="config-mini-tls1_2.h" CONFIG_NET_APP_SETTINGS=y CONFIG_NET_APP_MY_IPV6_ADDR="2001:db8::1" -CONFIG_NET_APP_MY_IPV4_ADDR="192.168.1.101" +CONFIG_NET_APP_MY_IPV4_ADDR="192.0.2.1" -CONFIG_NET_MAX_CONTEXTS=16 +CONFIG_NET_SHELL=y + +CONFIG_NET_MGMT=y +CONFIG_NET_MGMT_EVENT=y diff --git a/samples/net/http_server/prj_qemu_x86.conf b/samples/net/http_server/prj_qemu_x86.conf index 574a68848d7..6cb2cde9edd 100644 --- a/samples/net/http_server/prj_qemu_x86.conf +++ b/samples/net/http_server/prj_qemu_x86.conf @@ -8,29 +8,35 @@ CONFIG_INIT_STACKS=y CONFIG_NET_PKT_RX_COUNT=16 CONFIG_NET_PKT_TX_COUNT=16 -CONFIG_NET_BUF_RX_COUNT=8 -CONFIG_NET_BUF_TX_COUNT=8 +CONFIG_NET_BUF_RX_COUNT=16 +CONFIG_NET_BUF_TX_COUNT=16 -CONFIG_NET_IPV6_RA_RDNSS=y -CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=3 -CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=2 +CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=2 +CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=4 -CONFIG_STDOUT_CONSOLE=y +CONFIG_NET_MAX_CONTEXTS=16 + +CONFIG_SYS_LOG_SHOW_COLOR=y +CONFIG_SYS_LOG_NET_LEVEL=2 +CONFIG_NET_DEBUG_HTTP=y CONFIG_HTTP_SERVER=y -CONFIG_HTTP_PARSER=y -# Enable IPv6 support -CONFIG_NET_IPV6=n -# Enable IPv4 support +CONFIG_NET_IPV6=y CONFIG_NET_IPV4=y +#CONFIG_NET_DHCPV4=y + +CONFIG_HTTPS=y +CONFIG_MBEDTLS=y +CONFIG_MBEDTLS_BUILTIN=y +CONFIG_MBEDTLS_CFG_FILE="config-mini-tls1_2.h" CONFIG_NET_APP_SETTINGS=y CONFIG_NET_APP_MY_IPV6_ADDR="2001:db8::1" CONFIG_NET_APP_MY_IPV4_ADDR="192.0.2.1" -CONFIG_NET_MAX_CONTEXTS=16 +CONFIG_NET_SHELL=y +CONFIG_NET_STATISTICS=y -#CONFIG_MBEDTLS=y -#CONFIG_MBEDTLS_BUILTIN=y -#CONFIG_MBEDTLS_CFG_FILE="config-mini-tls1_2.h" +CONFIG_NET_MGMT=y +CONFIG_NET_MGMT_EVENT=y diff --git a/samples/net/http_server/src/Makefile b/samples/net/http_server/src/Makefile index adbf24d6ec9..6bede6e4ed9 100644 --- a/samples/net/http_server/src/Makefile +++ b/samples/net/http_server/src/Makefile @@ -4,15 +4,6 @@ # SPDX-License-Identifier: Apache-2.0 # -obj-y += main.o -obj-y += http_utils.o -obj-y += http_server.o -obj-y += http_write_utils.o -ifdef CONFIG_MBEDTLS -obj-y += https_server.o ssl_utils.o -endif +include $(ZEPHYR_BASE)/samples/net/common/Makefile.common -ifeq ($(CONFIG_NET_L2_BLUETOOTH), y) -ccflags-y +=-I${ZEPHYR_BASE}/samples/bluetooth/ -obj-y += ../../../bluetooth/gatt/ipss.o -endif +obj-y += main.o diff --git a/samples/net/http_server/src/config.h b/samples/net/http_server/src/config.h index 610796a5bb2..475ed0221bc 100644 --- a/samples/net/http_server/src/config.h +++ b/samples/net/http_server/src/config.h @@ -7,35 +7,27 @@ #ifndef _CONFIG_H_ #define _CONFIG_H_ +/* The startup time needs to be longish if DHCP is enabled as setting + * DHCP up takes some time. + */ +#define APP_STARTUP_TIME K_SECONDS(20) + #ifdef CONFIG_NET_APP_SETTINGS #ifdef CONFIG_NET_IPV6 -#define ZEPHYR_ADDR CONFIG_NET_APP_MY_IPV6_ADDR +#define ZEPHYR_ADDR CONFIG_NET_APP_MY_IPV6_ADDR #else -#define ZEPHYR_ADDR CONFIG_NET_APP_MY_IPV4_ADDR +#define ZEPHYR_ADDR CONFIG_NET_APP_MY_IPV4_ADDR #endif #else #ifdef CONFIG_NET_IPV6 -#define ZEPHYR_ADDR "2001:db8::1" +#define ZEPHYR_ADDR "2001:db8::1" #else -#define ZEPHYR_ADDR "192.168.1.101" +#define ZEPHYR_ADDR "192.0.2.1" #endif #endif -#define ZEPHYR_PORT 80 - -#define HTTP_AUTH_URL "/auth" -#define HTTP_AUTH_TYPE "Basic" - -/* HTTP Basic Auth, see https://tools.ietf.org/html/rfc7617 */ -#define HTTP_AUTH_REALM "Zephyr" -#define HTTP_AUTH_USERNAME "zephyr" -#define HTTP_AUTH_PASSWORD "0123456789" -#define HTTP_AUTH_CREDENTIALS "emVwaHlyOjAxMjM0NTY3ODk=" - -#define APP_SLEEP_MSECS 500 - -#ifdef CONFIG_MBEDTLS -#define SERVER_PORT 443 +#ifndef ZEPHYR_PORT +#define ZEPHYR_PORT 8080 #endif #endif diff --git a/samples/net/http_server/src/http_server.c b/samples/net/http_server/src/http_server.c index 77f8467d329..45e3eebf0bd 100644 --- a/samples/net/http_server/src/http_server.c +++ b/samples/net/http_server/src/http_server.c @@ -14,57 +14,10 @@ #include #include -#define URL_DEFAULT_HANDLER_INDEX 0 #define HTTP_BUF_CTR HTTP_MAX_NUMBER_SERVER_CTX #define HTTP_BUF_SIZE 1024 -NET_BUF_POOL_DEFINE(http_msg_pool, HTTP_BUF_CTR, HTTP_BUF_SIZE, 0, NULL); - -void http_accept_cb(struct net_context *net_ctx, struct sockaddr *addr, - socklen_t addr_len, int status, void *data) -{ - struct http_server_ctx *http_ctx = NULL; - - ARG_UNUSED(addr_len); - ARG_UNUSED(data); - - if (status != 0) { - net_context_put(net_ctx); - return; - } - - print_client_banner(addr); - - http_ctx = http_ctx_get(); - if (!http_ctx) { - net_context_put(net_ctx); - return; - } - - http_ctx_set(http_ctx, net_ctx); - - net_context_recv(net_ctx, http_rx_tx, K_NO_WAIT, http_ctx); -} - -/** - * @brief http_ctx_release Releases an HTTP context - * @return 0, future versions may return error codes - */ -static -int http_ctx_release(struct http_server_ctx *http_ctx); - -/** - * @brief parser_init Initializes some parser-related fields at the - * http_server_ctx structure - * @param ctx HTTP server context - * @param net_ctx Network context - * @return 0 on success - * @return -EINVAL on error - */ -static -int parser_init(struct http_server_ctx *ctx); - /** * @brief parser_parse_request Parses an HTTP REQUEST * @param ctx HTTP server context @@ -82,359 +35,17 @@ static int http_url_cmp(const char *url, u16_t url_len, static void http_tx(struct http_server_ctx *http_ctx); -void http_rx_tx(struct net_context *net_ctx, struct net_pkt *rx, int status, - void *user_data) -{ - struct http_server_ctx *http_ctx = NULL; - struct net_buf *data = NULL; - u16_t rcv_len; - u16_t offset; - int parsed_len; - int rc; - - if (status) { - printf("[%s:%d] Status code: %d, <%s>\n", - __func__, __LINE__, status, RC_STR(status)); - goto lb_exit; - } - - if (!user_data) { - printf("[%s:%d] User data is null\n", __func__, __LINE__); - goto lb_exit; - } - - http_ctx = (struct http_server_ctx *)user_data; - if (http_ctx->net_ctx != net_ctx) { - printf("[%s:%d] Wrong network context received\n", - __func__, __LINE__); - goto lb_exit; - } - - if (!rx) { - printf("[%s:%d] Connection closed by peer\n", - __func__, __LINE__); - goto lb_exit; - } - - rcv_len = net_pkt_appdatalen(rx); - if (rcv_len == 0) { - /* don't print info about zero-length app data buffers */ - goto lb_exit; - } - - data = net_buf_alloc(&http_msg_pool, APP_SLEEP_MSECS); - if (data == NULL) { - printf("[%s:%d] Data buffer alloc error\n", __func__, __LINE__); - goto lb_exit; - } - - offset = net_pkt_get_len(rx) - rcv_len; - rc = net_frag_linear_copy(data, rx->frags, offset, rcv_len); - if (rc != 0) { - printf("[%s:%d] Linear copy error\n", __func__, __LINE__); - goto lb_exit; - } - - data->data[min(data->size - 1, rcv_len)] = 0; - - parser_init(http_ctx); - parsed_len = parser_parse_request(http_ctx, data); - if (parsed_len <= 0) { - printf("[%s:%d] Received: %u bytes, only parsed: %d bytes\n", - __func__, __LINE__, rcv_len, parsed_len); - } - - if (http_ctx->parser.http_errno != HPE_OK) { - http_response_400(http_ctx, NULL); - } else { - http_tx(http_ctx); - } - -lb_exit: - net_pkt_frag_unref(data); - net_pkt_unref(rx); - - http_ctx_release(http_ctx); -} - -/** - * @brief on_header_field HTTP Parser callback for header fields - * @param parser HTTP Parser - * @param at Points to where the field begins - * @param length Field's length - * @return 0 (always) - */ -static -int on_header_field(struct http_parser *parser, const char *at, size_t length) -{ - struct http_server_ctx *ctx = (struct http_server_ctx *)parser->data; - - if (ctx->field_values_ctr >= HTTP_PARSER_MAX_FIELD_VALUES) { - return 0; - } - - ctx->field_values[ctx->field_values_ctr].key = at; - ctx->field_values[ctx->field_values_ctr].key_len = length; - - return 0; -} - -/** - * @brief on_header_value HTTP Parser callback for header values - * @param parser HTTP Parser - * @param at Points to where the value begins - * @param length Value's length - * @return 0 (always) - */ -static -int on_header_value(struct http_parser *parser, const char *at, size_t length) -{ - struct http_server_ctx *ctx = (struct http_server_ctx *)parser->data; - - if (ctx->field_values_ctr >= HTTP_PARSER_MAX_FIELD_VALUES) { - return 0; - } - - ctx->field_values[ctx->field_values_ctr].value = at; - ctx->field_values[ctx->field_values_ctr].value_len = length; - - ctx->field_values_ctr++; - - return 0; -} - -/** - * @brief on_url HTTP Parser callback for URLs - * @param parser HTTP Parser - * @param at Points to where the value begins - * @param length Value's length - * @return 0 (always) - */ -static -int on_url(struct http_parser *parser, const char *at, size_t length) -{ - struct http_server_ctx *ctx = (struct http_server_ctx *)parser->data; - - ctx->url = at; - ctx->url_len = length; - - return 0; -} - -static -int parser_init(struct http_server_ctx *ctx) -{ - memset(ctx->field_values, 0x00, sizeof(ctx->field_values)); - - ctx->parser_settings.on_header_field = on_header_field; - ctx->parser_settings.on_header_value = on_header_value; - ctx->parser_settings.on_url = on_url; - - http_parser_init(&ctx->parser, HTTP_REQUEST); - - /* This circular reference is useful when some parser callbacks - * want to access some internal data structures - */ - ctx->parser.data = ctx; - - return 0; -} - -static -int parser_parse_request(struct http_server_ctx *ctx, struct net_buf *rx) -{ - int rc; - - ctx->field_values_ctr = 0; - rc = http_parser_execute(&ctx->parser, &ctx->parser_settings, - rx->data, rx->len); - if (rc < 0) { - printf("[%s:%d] http_parser_execute: %s\n\t%s\n", - __func__, __LINE__, - http_errno_name(ctx->parser.http_errno), - http_errno_description(ctx->parser.http_errno)); - - rc = -EINVAL; - goto exit_routine; - } - -exit_routine: - return rc; -} /** * @brief server_collection This is a collection of server ctx structs */ -static struct http_server_ctx server_collection[HTTP_MAX_NUMBER_SERVER_CTX]; +static struct http_server_ctx server_ctx[CONFIG_HTTP_SERVER_CONNECTIONS]; /** * @brief http_url_ctx Just one URL context per application */ static struct http_url_ctx url_ctx; -int http_ctx_init(void) -{ - memset(server_collection, 0x00, sizeof(server_collection)); - - memset(&url_ctx, 0x00, sizeof(url_ctx)); - - /* 0 is reserved for the default handler */ - url_ctx.urls_ctr = 1; - - return 0; -} - -struct http_server_ctx *http_ctx_get(void) -{ - int i; - - for (i = 0; i < HTTP_MAX_NUMBER_SERVER_CTX; i++) { - - if (server_collection[i].state == HTTP_CTX_FREE) { - - printf("[%s:%d] Free ctx found, index: %d\n", - __func__, __LINE__, i); - - memset(server_collection + i, 0x00, - sizeof(struct http_server_ctx)); - - return server_collection + i; - } - } - - return NULL; -} - -int http_ctx_set(struct http_server_ctx *http_ctx, struct net_context *net_ctx) -{ - if (http_ctx == NULL || net_ctx == NULL) { - return -EINVAL; - } - - http_ctx->state = HTTP_CTX_IN_USE; - http_ctx->net_ctx = net_ctx; - - return 0; -} - -static int http_ctx_release(struct http_server_ctx *http_ctx) -{ - if (http_ctx == NULL) { - return 0; - } - - http_ctx->state = HTTP_CTX_FREE; - - return 0; -} - -int http_url_default_handler(int (*write_cb)(struct http_server_ctx *)) -{ - if (write_cb == NULL) { - return -EINVAL; - } - - url_ctx.urls[URL_DEFAULT_HANDLER_INDEX].flags = 0x00; - url_ctx.urls[URL_DEFAULT_HANDLER_INDEX].root = NULL; - url_ctx.urls[URL_DEFAULT_HANDLER_INDEX].root_len = 0; - url_ctx.urls[URL_DEFAULT_HANDLER_INDEX].write_cb = write_cb; - - return 0; -} - -int http_url_add(const char *url, u8_t flags, - int (*write_cb)(struct http_server_ctx *http_ctx)) -{ - struct http_root_url *root = NULL; - - if (url_ctx.urls_ctr >= HTTP_MAX_NUMBER_URL) { - return -ENOMEM; - } - - root = &url_ctx.urls[url_ctx.urls_ctr]; - - root->root = url; - /* this will speed-up some future operations */ - root->root_len = strlen(url); - root->flags = flags; - root->write_cb = write_cb; - - url_ctx.urls_ctr++; - - return 0; -} - -static int http_url_cmp(const char *url, u16_t url_len, - const char *root_url, u16_t root_url_len) -{ - if (url_len < root_url_len) { - return -EINVAL; - } - - if (memcmp(url, root_url, root_url_len) == 0) { - if (url_len == root_url_len) { - return 0; - } - - /* Here we evlaute the following conditions: - * root_url = /images, url = /images/ -> OK - * root_url = /images/, url = /images/img.png -> OK - * root_url = /images/, url = /images_and_docs -> ERROR - */ - if (url_len > root_url_len) { - if (root_url[root_url_len - 1] == '/') { - return 0; - } - - if (url[root_url_len] == '/') { - return 0; - } - } - } - - return -EINVAL; -} - -static struct http_root_url *http_url_find(struct http_server_ctx *http_ctx) -{ - u16_t url_len = http_ctx->url_len; - const char *url = http_ctx->url; - struct http_root_url *root_url; - u8_t i; - int rc; - - /* at some point we must come up with something better */ - for (i = 1; i < url_ctx.urls_ctr; i++) { - root_url = &url_ctx.urls[i]; - - rc = http_url_cmp(url, url_len, - root_url->root, root_url->root_len); - if (rc == 0) { - return root_url; - } - } - - return NULL; -} - -static void http_tx(struct http_server_ctx *http_ctx) -{ - struct http_root_url *root_url; - - root_url = http_url_find(http_ctx); - if (!root_url) { - root_url = &url_ctx.urls[URL_DEFAULT_HANDLER_INDEX]; - } - - if (root_url->write_cb) { - root_url->write_cb(http_ctx); - } else { - printf("[%s:%d] No default handler for %.*s\n", - __func__, __LINE__, - http_ctx->url_len, http_ctx->url); - } -} - int http_auth(struct http_server_ctx *ctx) { const char *auth_str = "Authorization: Basic "HTTP_AUTH_CREDENTIALS; diff --git a/samples/net/http_server/src/http_types.h b/samples/net/http_server/src/http_types.h deleted file mode 100644 index 86dc34566cb..00000000000 --- a/samples/net/http_server/src/http_types.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2017 Intel Corporation - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#ifndef _HTTP_TYPES_H_ -#define _HTTP_TYPES_H_ - -#include -#include - -/* Max number of HTTP header field-value pairs */ -#define HTTP_PARSER_MAX_FIELD_VALUES CONFIG_HTTP_HEADER_FIELD_ITEMS - -/* Max number of HTTP server context available to this app */ -#define HTTP_MAX_NUMBER_SERVER_CTX CONFIG_NET_MAX_CONTEXTS - -/* Max number of URLs this server will handle */ -#define HTTP_MAX_NUMBER_URL 16 - -enum HTTP_URL_FLAGS { - HTTP_URL_STANDARD = 0 -}; - -/** - * @brief http_root_url HTTP root URL struct, used for pattern matching - */ -struct http_root_url { - const char *root; - u16_t root_len; - - u8_t flags; - int (*write_cb)(struct http_server_ctx *http_ctx); -}; - -/** - * @brief http_url_ctx Collection of URLs that this server will handle - */ -struct http_url_ctx { - struct http_root_url urls[HTTP_MAX_NUMBER_URL]; - u8_t urls_ctr; -}; - -#endif diff --git a/samples/net/http_server/src/http_utils.c b/samples/net/http_server/src/http_utils.c deleted file mode 100644 index 6669f51fac8..00000000000 --- a/samples/net/http_server/src/http_utils.c +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2017 Intel Corporation - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "http_utils.h" -#include - -#include "config.h" - -#define IP6_SIZE 16 -#define IP4_SIZE 4 - -#define STR_IP6_ADDR 64 -#define STR_IP4_ADDR 16 - -static -void print_ipaddr(const struct sockaddr *ip_addr) -{ -#ifdef CONFIG_NET_IPV6 - struct sockaddr_in6 *addr = (struct sockaddr_in6 *)ip_addr; - void *raw = (void *)&addr->sin6_addr; - u16_t port = addr->sin6_port; - sa_family_t family = AF_INET6; - char str[STR_IP6_ADDR]; -#else - struct sockaddr_in *addr = (struct sockaddr_in *)ip_addr; - void *raw = (void *)&addr->sin_addr; - u16_t port = addr->sin_port; - sa_family_t family = AF_INET; - char str[STR_IP4_ADDR]; -#endif - - net_addr_ntop(family, raw, str, sizeof(str)); - printf("Address: %s, port: %d\n", str, ntohs(port)); -} - -void print_client_banner(const struct sockaddr *addr) -{ - printf("\n----------------------------------------------------\n"); - printf("[%s:%d] Connection accepted\n", __func__, __LINE__); - print_ipaddr(addr); -} - - -void print_server_banner(const struct sockaddr *addr) -{ - printf("Zephyr HTTP Server\n"); - print_ipaddr(addr); -} diff --git a/samples/net/http_server/src/http_utils.h b/samples/net/http_server/src/http_utils.h deleted file mode 100644 index 9fb3bb69a5b..00000000000 --- a/samples/net/http_server/src/http_utils.h +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c) 2017 Intel Corporation - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#ifndef __HTTP_UTILS_H__ -#define __HTTP_UTILS_H__ - -#include - -#define RC_STR(rc) (rc == 0 ? "OK" : "ERROR") - -void print_client_banner(const struct sockaddr *addr); - -void print_server_banner(const struct sockaddr *addr); - -#endif diff --git a/samples/net/http_server/src/http_write_utils.c b/samples/net/http_server/src/http_write_utils.c index a4e5ce65467..c2c1e9c398c 100644 --- a/samples/net/http_server/src/http_write_utils.c +++ b/samples/net/http_server/src/http_write_utils.c @@ -11,100 +11,11 @@ #include -#define HTTP_STATUS_200_OK "HTTP/1.1 200 OK\r\n" \ - "Content-Type: text/html\r\n" \ - "Transfer-Encoding: chunked\r\n" \ - "\r\n" - -#define HTTP_401_STATUS_US "HTTP/1.1 401 Unauthorized status\r\n" \ - "WWW-Authenticate: Basic realm=" \ - "\""HTTP_AUTH_REALM"\"\r\n\r\n" - -#define HTML_HEADER "" \ - "Zephyr HTTP Server" \ - "

" \ - "
Zephyr HTTP server

\r\n" - -#define HTML_FOOTER "\r\n" - -/* Prints the received HTTP header fields as an HTML list */ -static void print_http_headers(struct http_server_ctx *ctx, - char *str, u16_t size) -{ - struct http_parser *parser = &ctx->parser; - u16_t offset = 0; - - offset = snprintf(str, size, - HTML_HEADER - "

HTTP Header Fields

\r\n
    \r\n"); - if (offset >= size) { - return; - } - - for (u8_t i = 0; i < ctx->field_values_ctr; i++) { - struct http_field_value *kv = &ctx->field_values[i]; - - offset += snprintf(str + offset, size - offset, - "
  • %.*s: %.*s
  • \r\n", - kv->key_len, kv->key, - kv->value_len, kv->value); - if (offset >= size) { - return; - } - } - - offset += snprintf(str + offset, size - offset, "
\r\n"); - if (offset >= size) { - return; - } - - offset += snprintf(str + offset, size - offset, - "

HTTP Method: %s

\r\n", - http_method_str(parser->method)); - if (offset >= size) { - return; - } - - offset += snprintf(str + offset, size - offset, - "

URL: %.*s

\r\n", - ctx->url_len, ctx->url); - if (offset >= size) { - return; - } - - snprintf(str + offset, size - offset, - "

Server: %s

"HTML_FOOTER, CONFIG_ARCH); -} - -#define HTTP_MAX_BODY_STR_SIZE 512 -static char html_body[HTTP_MAX_BODY_STR_SIZE]; - -int http_response_header_fields(struct http_server_ctx *ctx) -{ - print_http_headers(ctx, html_body, HTTP_MAX_BODY_STR_SIZE); - - return http_response(ctx, HTTP_STATUS_200_OK, html_body); -} - -int http_response_it_works(struct http_server_ctx *ctx) -{ - return http_response(ctx, HTTP_STATUS_200_OK, HTML_HEADER - "

It Works!

" - HTML_FOOTER); -} - int http_response_401(struct http_server_ctx *ctx) { return http_response(ctx, HTTP_401_STATUS_US, NULL); } -int http_response_soft_404(struct http_server_ctx *ctx) -{ - return http_response(ctx, HTTP_STATUS_200_OK, HTML_HEADER - "

404 Not Found

" - HTML_FOOTER); -} - int http_response_auth(struct http_server_ctx *ctx) { return http_response(ctx, HTTP_STATUS_200_OK, diff --git a/samples/net/http_server/src/https_server.c b/samples/net/http_server/src/https_server.c deleted file mode 100644 index 53ba6c9b6d9..00000000000 --- a/samples/net/http_server/src/https_server.c +++ /dev/null @@ -1,407 +0,0 @@ -/* Minimal TLS server. - * - * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved - * - * SPDX-License-Identifier: Apache-2.0 - * - * This file is part of mbed TLS (https://tls.mbed.org) - */ - -#include -#include - -#include -#include - -#if !defined(CONFIG_MBEDTLS_CFG_FILE) -#include "mbedtls/config.h" -#else -#include CONFIG_MBEDTLS_CFG_FILE -#endif - -#if defined(MBEDTLS_PLATFORM_C) -#include "mbedtls/platform.h" -#else -#include -#define mbedtls_time_t time_t -#define MBEDTLS_EXIT_SUCCESS EXIT_SUCCESS -#define MBEDTLS_EXIT_FAILURE EXIT_FAILURE -#endif - -#include -#include -#include -#include "config.h" -#include "ssl_utils.h" -#include "test_certs.h" - -#include "mbedtls/ssl_cookie.h" -#include "mbedtls/entropy.h" -#include "mbedtls/ctr_drbg.h" -#include "mbedtls/net_sockets.h" -#include "mbedtls/x509.h" -#include "mbedtls/ssl.h" -#include "mbedtls/error.h" -#include "mbedtls/debug.h" - -#if defined(MBEDTLS_DEBUG_C) -#include "mbedtls/debug.h" -#define DEBUG_THRESHOLD 0 -#endif - -#if defined(MBEDTLS_MEMORY_BUFFER_ALLOC_C) -#include "mbedtls/memory_buffer_alloc.h" -static unsigned char heap[12000]; -#endif - -/* - * Hardcoded values for server host and port - */ - -static const char *pers = "tls_server"; - -#if defined(CONFIG_NET_IPV6) -static struct in6_addr server_addr; -#else -static struct in_addr server_addr; -#endif - -struct parsed_url { - const char *url; - u16_t url_len; -}; - -#define HTTP_RESPONSE \ - "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n" \ - "

Zephyr TLS Test Server

\r\n" \ - "

Successful connection

\r\n" - -#define HTTP_NOT_FOUND \ - "HTTP/1.0 404 NOT FOUND\r\nContent-Type: text/html\r\n\r\n" \ - "

Zephyr TLS page not found

\r\n" \ - "

Successful connection

\r\n" - -static void my_debug(void *ctx, int level, - const char *file, int line, const char *str) -{ - const char *p, *basename; - - ARG_UNUSED(ctx); - - /* Extract basename from file */ - for (p = basename = file; *p != '\0'; p++) { - if (*p == '/' || *p == '\\') { - basename = p + 1; - } - - } - - mbedtls_printf("%s:%04d: |%d| %s", basename, line, level, str); -} - -static int entropy_source(void *data, unsigned char *output, size_t len, - size_t *olen) -{ - u32_t seed; - - ARG_UNUSED(data); - - seed = sys_rand32_get(); - - if (len > sizeof(seed)) { - len = sizeof(seed); - } - - memcpy(output, &seed, len); - - *olen = len; - return 0; -} - -static -int on_url(struct http_parser *parser, const char *at, size_t length) -{ - struct parsed_url *req = (struct parsed_url *)parser->data; - - req->url = at; - req->url_len = length; - - return 0; -} - -static unsigned char payload[256]; - -void https_server(void) -{ - struct ssl_context ctx; - struct parsed_url request; - int ret, len = 0; - - mbedtls_entropy_context entropy; - mbedtls_ctr_drbg_context ctr_drbg; - mbedtls_ssl_context ssl; - mbedtls_ssl_config conf; - mbedtls_x509_crt srvcert; - mbedtls_pk_context pkey; - - mbedtls_platform_set_printf(printk); - -#if defined(MBEDTLS_MEMORY_BUFFER_ALLOC_C) - mbedtls_memory_buffer_alloc_init(heap, sizeof(heap)); -#endif - -#if defined(MBEDTLS_DEBUG_C) - mbedtls_debug_set_threshold(DEBUG_THRESHOLD); - mbedtls_ssl_conf_dbg(&conf, my_debug, NULL); -#endif - mbedtls_x509_crt_init(&srvcert); - mbedtls_pk_init(&pkey); - mbedtls_ssl_init(&ssl); - mbedtls_ssl_config_init(&conf); - mbedtls_entropy_init(&entropy); - mbedtls_ctr_drbg_init(&ctr_drbg); - - /* - * 1. Load the certificates and private RSA key - */ - - ret = mbedtls_x509_crt_parse(&srvcert, rsa_example_cert_der, - rsa_example_cert_der_len); - if (ret != 0) { - mbedtls_printf(" failed\n !" - " mbedtls_x509_crt_parse returned %d\n\n", ret); - goto exit; - } - - ret = mbedtls_pk_parse_key(&pkey, rsa_example_keypair_der, - rsa_example_keypair_der_len, NULL, 0); - if (ret != 0) { - mbedtls_printf(" failed\n !" - " mbedtls_pk_parse_key returned %d\n\n", ret); - goto exit; - } - - /* - * 3. Seed the RNG - */ - - mbedtls_entropy_add_source(&entropy, entropy_source, NULL, - MBEDTLS_ENTROPY_MAX_GATHER, - MBEDTLS_ENTROPY_SOURCE_STRONG); - - ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, - (const unsigned char *)pers, strlen(pers)); - if (ret != 0) { - mbedtls_printf(" failed\n !" - " mbedtls_ctr_drbg_seed returned %d\n", ret); - goto exit; - } - - /* - * 4. Setup stuff - */ - ret = mbedtls_ssl_config_defaults(&conf, - MBEDTLS_SSL_IS_SERVER, - MBEDTLS_SSL_TRANSPORT_STREAM, - MBEDTLS_SSL_PRESET_DEFAULT); - if (ret != 0) { - mbedtls_printf(" failed\n !" - " mbedtls_ssl_config_defaults returned %d\n\n", - ret); - goto exit; - } - - mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg); - mbedtls_ssl_conf_dbg(&conf, my_debug, NULL); - - mbedtls_ssl_conf_ca_chain(&conf, srvcert.next, NULL); - ret = mbedtls_ssl_conf_own_cert(&conf, &srvcert, &pkey); - if (ret != 0) { - mbedtls_printf(" failed\n !" - " mbedtls_ssl_conf_own_cert returned %d\n\n", - ret); - goto exit; - } - - ret = mbedtls_ssl_setup(&ssl, &conf); - if (ret != 0) { - mbedtls_printf(" failed\n !" - " mbedtls_ssl_setup returned %d\n\n", ret); - goto exit; - } - - /* - * 3. Wait until a client connects - */ - ret = ssl_init(&ctx, &server_addr); - if (ret != 0) { - mbedtls_printf(" failed\n ! ssl_init returned %d\n\n", ret); - goto exit; - - } - - /* - * 4. Prepare http parser - */ - http_parser_init(&ctx.parser, HTTP_REQUEST); - http_parser_settings_init(&ctx.parser_settings); - ctx.parser.data = &request; - ctx.parser_settings.on_url = on_url; - - mbedtls_printf("Zephyr HTTPS Server\n"); - mbedtls_printf("Address: %s, port: %d\n", ZEPHYR_ADDR, SERVER_PORT); -reset: - mbedtls_ssl_session_reset(&ssl); - mbedtls_ssl_set_bio(&ssl, &ctx, ssl_tx, ssl_rx, NULL); - /* - * 5. Handshake - */ - do { - ret = mbedtls_ssl_handshake(&ssl); - if (ret != MBEDTLS_ERR_SSL_WANT_READ && - ret != MBEDTLS_ERR_SSL_WANT_WRITE) { - if (ret < 0) { - mbedtls_printf(" failed\n !" - " mbedtls_ssl_handshake returned %d\n\n", - ret); - goto reset; - } - } - } while (ret != 0); - - /* - * 6. Read the HTTPS Request - */ - mbedtls_printf("Read HTTPS request\n"); - do { - len = sizeof(payload) - 1; - memset(payload, 0, sizeof(payload)); - ret = mbedtls_ssl_read(&ssl, payload, len); - - if (ret == MBEDTLS_ERR_SSL_WANT_READ || - ret == MBEDTLS_ERR_SSL_WANT_WRITE) { - continue; - } - - if (ret <= 0) { - switch (ret) { - case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY: - mbedtls_printf(" connection was" - " closed gracefully\n"); - goto close; - - case MBEDTLS_ERR_NET_CONN_RESET: - mbedtls_printf(" connection was" - " reset by peer\n"); - break; - - default: - mbedtls_printf - (" mbedtls_ssl_read returned -0x%x\n", - -ret); - break; - } - - break; - } - - len = ret; - ret = http_parser_execute(&ctx.parser, &ctx.parser_settings, - payload, len); - if (ret < 0) { - } - } while (ret < 0); - - /* - * 7. Write the Response - */ - mbedtls_printf("Write HTTPS response\n"); - - if (!strncmp("/index.html", request.url, request.url_len)) { - len = snprintf((char *)payload, sizeof(payload), - HTTP_RESPONSE); - } else { - - len = snprintf((char *)payload, sizeof(payload), - HTTP_NOT_FOUND); - } - - do { - ret = mbedtls_ssl_write(&ssl, payload, len); - if (ret == MBEDTLS_ERR_NET_CONN_RESET) { - mbedtls_printf(" failed\n !" - " peer closed the connection\n"); - goto reset; - } - - if (ret != MBEDTLS_ERR_SSL_WANT_READ && - ret != MBEDTLS_ERR_SSL_WANT_WRITE) { - if (ret < 0) { - mbedtls_printf(" failed\n !" - " mbedtls_ssl_write" - " returned %d\n", ret); - goto exit; - } - } - } while (ret <= 0); - -close: - - mbedtls_ssl_close_notify(&ssl); - ret = 0; - goto reset; - -exit: -#ifdef MBEDTLS_ERROR_C - if (ret != 0) { - mbedtls_strerror(ret, payload, 100); - mbedtls_printf("Last error was: %d - %s\n", ret, payload); - } -#endif - - mbedtls_ssl_free(&ssl); - mbedtls_ssl_config_free(&conf); - mbedtls_ctr_drbg_free(&ctr_drbg); - mbedtls_entropy_free(&entropy); -} - -#define STACK_SIZE 8192 -u8_t stack[STACK_SIZE]; -struct k_thread https_thread; - -static inline int init_app(void) -{ -#if defined(CONFIG_NET_IPV6) - if (net_addr_pton(AF_INET6, ZEPHYR_ADDR, &server_addr) < 0) { - mbedtls_printf("Invalid IPv6 address %s", ZEPHYR_ADDR); - } - - if (!net_if_ipv6_addr_add(net_if_get_default(), &server_addr, - NET_ADDR_MANUAL, 0)) { - return -EIO; - } -#else - if (net_addr_pton(AF_INET, ZEPHYR_ADDR, &server_addr) < 0) { - mbedtls_printf("Invalid IPv4 address %s", ZEPHYR_ADDR); - } - - if (!net_if_ipv4_addr_add(net_if_get_default(), &server_addr, - NET_ADDR_MANUAL, 0)) { - return -EIO; - } -#endif - - return 0; -} - -void https_server_start(void) -{ - if (init_app() != 0) { - printk("Cannot initialize network\n"); - return; - } - - k_thread_create(&https_thread, stack, STACK_SIZE, - (k_thread_entry_t) https_server, - NULL, NULL, NULL, K_PRIO_COOP(7), 0, 0); -} diff --git a/samples/net/http_server/src/main.c b/samples/net/http_server/src/main.c index 29f7433b5fe..28e091cc233 100644 --- a/samples/net/http_server/src/main.c +++ b/samples/net/http_server/src/main.c @@ -4,144 +4,305 @@ * SPDX-License-Identifier: Apache-2.0 */ -#include -#include - -#include - -#if defined(CONFIG_NET_L2_BLUETOOTH) -#include -#include +#if 1 +#if defined(CONFIG_HTTPS) +#define SYS_LOG_DOMAIN "https-server" +#else +#define SYS_LOG_DOMAIN "http-server" +#endif +#define NET_SYS_LOG_LEVEL SYS_LOG_LEVEL_DEBUG +#define NET_LOG_ENABLED 1 #endif -#include "http_types.h" -#include "http_server.h" -#include "http_utils.h" -#include "http_write_utils.h" +#include +#include + +#include +#include + +#include + #include "config.h" /* Sets the network parameters */ -static -int network_setup(struct net_context **net_ctx, net_tcp_accept_cb_t accept_cb, - const char *addr, u16_t port); -#if defined(CONFIG_MBEDTLS) -#include "ssl_utils.h" -#endif +#define RESULT_BUF_SIZE 1024 +static u8_t http_result[RESULT_BUF_SIZE]; + +#if defined(CONFIG_HTTPS) +#if !defined(CONFIG_HTTPS_STACK_SIZE) +#define CONFIG_HTTPS_STACK_SIZE 8192 +#endif /* CONFIG_HTTPS_STACK_SIZE */ + +#define APP_BANNER "Run HTTPS server" +#define INSTANCE_INFO "Zephyr HTTPS example server #1" + +/* Note that each HTTPS context needs its own stack as there will be + * a separate thread for each HTTPS context. + */ +NET_STACK_DEFINE(HTTPS, https_stack, CONFIG_HTTPS_STACK_SIZE, + CONFIG_HTTPS_STACK_SIZE); + +#define RX_FIFO_DEPTH 4 +K_MEM_POOL_DEFINE(ssl_rx_pool, 4, 64, RX_FIFO_DEPTH, 4); + +static struct http_server_ctx https_ctx; + +static u8_t https_result[RESULT_BUF_SIZE]; + +#else /* CONFIG_HTTPS */ +#define APP_BANNER "Run HTTP server" +#endif /* CONFIG_HTTPS */ + +/* + * Note that the http_server_ctx and http_server_urls are quite large so be + * careful if those are allocated from stack. + */ +static struct http_server_ctx http_ctx; +static struct http_server_urls http_urls; + +void panic(const char *msg) +{ + if (msg) { + NET_ERR("%s", msg); + } + + for (;;) { + k_sleep(K_FOREVER); + } +} + +#define HTTP_STATUS_200_OK "HTTP/1.1 200 OK\r\n" \ + "Content-Type: text/html\r\n" \ + "Transfer-Encoding: chunked\r\n" \ + "\r\n" + +#define HTML_HEADER "" \ + "Zephyr HTTP Server" \ + "

" \ + "
Zephyr HTTP server

\r\n" + +#define HTML_FOOTER "\r\n" + +/* Prints the received HTTP header fields as an HTML list */ +static void print_http_headers(struct http_server_ctx *ctx, + char *str, int size) +{ + int offset; + int ret; + + ret = snprintf(str, size, + HTML_HEADER + "

HTTP Header Fields

\r\n
    \r\n"); + if (ret < 0 || ret >= size) { + return; + } + + offset = ret; + + for (u8_t i = 0; i < ctx->req.field_values_ctr; i++) { + struct http_field_value *kv = &ctx->req.field_values[i]; + + ret = snprintf(str + offset, size - offset, + "
  • %.*s: %.*s
  • \r\n", + kv->key_len, kv->key, + kv->value_len, kv->value); + if (ret < 0 || ret >= size) { + return; + } + + offset += ret; + } + + ret = snprintf(str + offset, size - offset, "
\r\n"); + if (ret < 0 || ret >= size) { + return; + } + + offset += ret; + + ret = snprintf(str + offset, size - offset, + "

HTTP Method: %s

\r\n", + http_method_str(ctx->req.parser.method)); + if (ret < 0 || ret >= size) { + return; + } + + offset += ret; + + ret = snprintf(str + offset, size - offset, + "

URL: %.*s

\r\n", + ctx->req.url_len, ctx->req.url); + if (ret < 0 || ret >= size) { + return; + } + + offset += ret; + + snprintf(str + offset, size - offset, + "

Server: %s

"HTML_FOOTER, CONFIG_ARCH); +} + +int http_response_header_fields(struct http_server_ctx *ctx) +{ +#define HTTP_MAX_BODY_STR_SIZE 1024 + static char html_body[HTTP_MAX_BODY_STR_SIZE]; + + print_http_headers(ctx, html_body, HTTP_MAX_BODY_STR_SIZE); + + return http_response(ctx, HTTP_STATUS_200_OK, html_body); +} + +static int http_response_it_works(struct http_server_ctx *ctx) +{ + return http_response(ctx, HTTP_STATUS_200_OK, HTML_HEADER + "

It Works!

" + HTML_FOOTER); +} + +static int http_response_soft_404(struct http_server_ctx *ctx) +{ + return http_response(ctx, HTTP_STATUS_200_OK, HTML_HEADER + "

404 Not Found

" + HTML_FOOTER); +} + +#if defined(CONFIG_HTTPS) +/* Load the certificates and private RSA key. */ + +#include "test_certs.h" + +static int setup_cert(struct http_server_ctx *ctx, + mbedtls_x509_crt *cert, + mbedtls_pk_context *pkey) +{ + int ret; + + ret = mbedtls_x509_crt_parse(cert, rsa_example_cert_der, + rsa_example_cert_der_len); + if (ret != 0) { + NET_ERR("mbedtls_x509_crt_parse returned %d", ret); + return ret; + } + + ret = mbedtls_pk_parse_key(pkey, rsa_example_keypair_der, + rsa_example_keypair_der_len, NULL, 0); + if (ret != 0) { + NET_ERR("mbedtls_pk_parse_key returned %d", ret); + return ret; + } + + return 0; +} +#endif /* CONFIG_HTTPS */ void main(void) { -#if defined(CONFIG_NET_L2_BLUETOOTH) - int err; - - err = bt_enable(NULL); - if (err) { - printk("Bluetooth init failed (err %d)\n", err); - return; - } - - printk("Bluetooth initialized\n"); - - ipss_init(); - - err = ipss_advertise(); - if (err) { - printk("Advertising failed to start (err %d)\n", err); - return; - } - - printk("Advertising successfully started\n"); -#endif - - struct net_context *net_ctx = NULL; - - http_ctx_init(); - - http_url_default_handler(http_response_soft_404); - http_url_add(HTTP_AUTH_URL, HTTP_URL_STANDARD, http_auth); - http_url_add("/headers", HTTP_URL_STANDARD, - http_response_header_fields); - http_url_add("/index.html", HTTP_URL_STANDARD, http_response_it_works); - - network_setup(&net_ctx, http_accept_cb, ZEPHYR_ADDR, ZEPHYR_PORT); -} - -static -int network_setup(struct net_context **net_ctx, net_tcp_accept_cb_t accept_cb, - const char *addr, u16_t port) -{ - struct sockaddr local_sock; - void *ptr; - int rc; - - *net_ctx = NULL; - -#ifdef CONFIG_NET_IPV6 - net_sin6(&local_sock)->sin6_port = htons(port); - local_sock.family = AF_INET6; - ptr = &(net_sin6(&local_sock)->sin6_addr); - rc = net_addr_pton(AF_INET6, addr, ptr); -#else - net_sin(&local_sock)->sin_port = htons(port); - local_sock.family = AF_INET; - ptr = &(net_sin(&local_sock)->sin_addr); - rc = net_addr_pton(AF_INET, addr, ptr); -#endif - - if (rc) { - printk("Invalid IP address/Port: %s, %d\n", addr, port); - return rc; - } - -#ifdef CONFIG_NET_IPV6 - ptr = net_if_ipv6_addr_add(net_if_get_default(), - &net_sin6(&local_sock)->sin6_addr, - NET_ADDR_MANUAL, 0); -#else - ptr = net_if_ipv4_addr_add(net_if_get_default(), - &net_sin(&local_sock)->sin_addr, - NET_ADDR_MANUAL, 0); -#endif + struct sockaddr addr, *server_addr; + u32_t flags = 0; + int ret; + /* + * If we have both IPv6 and IPv4 enabled, then set the + * startup flags so that we wait until both are ready + * before continuing. + */ #if defined(CONFIG_NET_IPV6) - rc = net_context_get(AF_INET6, SOCK_STREAM, IPPROTO_TCP, net_ctx); + flags |= NET_SAMPLE_NEED_IPV6; +#endif +#if defined(CONFIG_NET_IPV4) + flags |= NET_SAMPLE_NEED_IPV4; +#endif + + ret = net_sample_app_init(APP_BANNER, flags, APP_STARTUP_TIME); + if (ret < 0) { + panic("Application init failed"); + } + + /* + * There are several options here for binding to local address. + * 1) The server address can be left empty in which case the + * library will bind to both IPv4 and IPv6 addresses and to + * port 80 which is the default. + * 2) The server address can be partially filled, meaning that + * the address can be left to 0 and port can be set if a value + * other than 80 is desired. If the protocol family in sockaddr + * is set to AF_UNSPEC, then both IPv4 and IPv6 socket is bound. + * 3) The address can be set to some real value. There is a helper + * function that can be used to fill the socket address struct. + */ +#define ADDR_OPTION 1 + +#if ADDR_OPTION == 1 + server_addr = NULL; + + ARG_UNUSED(addr); + +#elif ADDR_OPTION == 2 + /* Accept any local listening address */ + memset(&addr, 0, sizeof(addr)); + + net_sin(&addr)->sin_port = htons(ZEPHYR_PORT); + + /* In this example, listen only IPv4 */ + addr.family = AF_INET; + + server_addr = &addr; + +#elif ADDR_OPTION == 3 + /* Set the bind address according to your configuration */ + memset(&addr, 0, sizeof(addr)); + + /* In this example, listen only IPv6 */ + addr.family = AF_INET6; + + ret = http_server_set_local_addr(&addr, ZEPHYR_ADDR, ZEPHYR_PORT); + if (ret < 0) { + NET_ERR("Cannot set local address (%d)", ret); + panic(NULL); + } + + server_addr = &addr; + #else - rc = net_context_get(AF_INET, SOCK_STREAM, IPPROTO_TCP, net_ctx); + server_addr = NULL; + + ARG_UNUSED(addr); #endif - if (rc != 0) { - printk("net_context_get error\n"); - return rc; + + http_server_add_default(&http_urls, http_response_soft_404); + http_server_add_url(&http_urls, "/headers", HTTP_URL_STANDARD, + http_response_header_fields); + http_server_add_url(&http_urls, "/index.html", HTTP_URL_STANDARD, + http_response_it_works); + +#if defined(CONFIG_HTTPS) + ret = https_server_init(&https_ctx, &http_urls, server_addr, + https_result, sizeof(https_result), + "Zephyr HTTPS Server", + INSTANCE_INFO, strlen(INSTANCE_INFO), + setup_cert, NULL, &ssl_rx_pool, + https_stack, sizeof(https_stack)); + if (ret < 0) { + NET_ERR("Cannot initialize HTTPS server (%d)", ret); + panic(NULL); } - rc = net_context_bind(*net_ctx, (const struct sockaddr *)&local_sock, - sizeof(local_sock)); - if (rc != 0) { - printk("net_context_bind error\n"); - goto lb_error; - } - - rc = net_context_listen(*net_ctx, 0); - if (rc != 0) { - printk("net_context_listen error\n"); - goto lb_error; - } - - rc = net_context_accept(*net_ctx, accept_cb, 0, NULL); - if (rc != 0) { - printk("net_context_accept error\n"); - goto lb_error; - } - - print_server_banner(&local_sock); - -#if defined(CONFIG_MBEDTLS) - https_server_start(); + http_server_enable(&https_ctx); #endif - return 0; -lb_error: - net_context_put(*net_ctx); - *net_ctx = NULL; + ret = http_server_init(&http_ctx, &http_urls, server_addr, http_result, + sizeof(http_result), "Zephyr HTTP Server"); + if (ret < 0) { + NET_ERR("Cannot initialize HTTP server (%d)", ret); + panic(NULL); + } - return rc; + /* + * If needed, the HTTP parser callbacks can be set according to + * applications own needs before enabling the server. In this example + * we use the default callbacks defined in HTTP server API. + */ + + http_server_enable(&http_ctx); } diff --git a/samples/net/http_server/src/ssl_utils.c b/samples/net/http_server/src/ssl_utils.c deleted file mode 100644 index 9f421686953..00000000000 --- a/samples/net/http_server/src/ssl_utils.c +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright (c) 2017 Intel Corporation - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#if !defined(CONFIG_MBEDTLS_CFG_FILE) -#include "mbedtls/config.h" -#else -#include CONFIG_MBEDTLS_CFG_FILE -#endif - -#include "mbedtls/ssl.h" - -#include "config.h" -#include "ssl_utils.h" - -#define RX_FIFO_DEPTH 4 - -K_MEM_POOL_DEFINE(rx_pkts, 4, 64, RX_FIFO_DEPTH, 4); - -static void ssl_received(struct net_context *context, - struct net_buf *buf, int status, void *user_data) -{ - struct ssl_context *ctx = user_data; - struct rx_fifo_block *rx_data = NULL; - struct k_mem_block block; - - ARG_UNUSED(context); - ARG_UNUSED(status); - - if (!net_pkt_appdatalen(buf)) { - net_pkt_unref(buf); - return; - } - - k_mem_pool_alloc(&rx_pkts, &block, - sizeof(struct rx_fifo_block), K_FOREVER); - rx_data = block.data; - rx_data->buf = buf; - - /* For freeing memory later */ - memcpy(&rx_data->block, &block, sizeof(struct k_mem_block)); - k_fifo_put(&ctx->rx_fifo, (void *)rx_data); -} - -static inline void ssl_sent(struct net_context *context, - int status, void *token, void *user_data) -{ - struct ssl_context *ctx = user_data; - - k_sem_give(&ctx->tx_sem); -} - -int ssl_tx(void *context, const unsigned char *buf, size_t size) -{ - struct ssl_context *ctx = context; - struct net_context *net_ctx; - struct net_buf *send_buf; - - int rc, len; - - net_ctx = ctx->net_ctx; - - send_buf = net_pkt_get_tx(net_ctx, K_NO_WAIT); - if (!send_buf) { - return MBEDTLS_ERR_SSL_ALLOC_FAILED; - } - - len = net_pkt_append(send_buf, size, (u8_t *) buf, K_FOREVER); - - rc = net_context_send(send_buf, ssl_sent, K_NO_WAIT, NULL, ctx); - - if (rc < 0) { - net_pkt_unref(send_buf); - return MBEDTLS_ERR_SSL_INTERNAL_ERROR; - } - - k_sem_take(&ctx->tx_sem, K_FOREVER); - return len; -} - -int ssl_rx(void *context, unsigned char *buf, size_t size) -{ - struct ssl_context *ctx = context; - u16_t read_bytes; - struct rx_fifo_block *rx_data; - u8_t *ptr; - int pos; - int len; - int rc = 0; - - if (ctx->frag == NULL) { - rx_data = k_fifo_get(&ctx->rx_fifo, K_FOREVER); - ctx->rx_pkt = rx_data->buf; - k_mem_pool_free(&rx_data->block); - - read_bytes = net_pkt_appdatalen(ctx->rx_pkt); - - ctx->remaining = read_bytes; - ctx->frag = ctx->rx_pkt->frags; - ptr = net_pkt_appdata(ctx->rx_pkt); - - len = ptr - ctx->frag->data; - net_buf_pull(ctx->frag, len); - } else { - read_bytes = ctx->remaining; - ptr = ctx->frag->data; - } - - len = ctx->frag->len; - pos = 0; - if (read_bytes > size) { - while (ctx->frag) { - read_bytes = len < (size - pos) ? len : (size - pos); - memcpy(buf + pos, ptr, read_bytes); - pos += read_bytes; - if (pos < size) { - ctx->frag = ctx->frag->frags; - ptr = ctx->frag->data; - len = ctx->frag->len; - } else { - if (read_bytes == len) { - ctx->frag = ctx->frag->frags; - } else { - net_buf_pull(ctx->frag, read_bytes); - } - - ctx->remaining -= size; - return size; - } - } - } else { - while (ctx->frag) { - memcpy(buf + pos, ptr, len); - pos += len; - ctx->frag = ctx->frag->frags; - if (!ctx->frag) { - break; - } - - ptr = ctx->frag->data; - len = ctx->frag->len; - } - - net_pkt_unref(ctx->rx_pkt); - ctx->rx_pkt = NULL; - ctx->frag = NULL; - ctx->remaining = 0; - - if (read_bytes != pos) { - return -EIO; - } - - rc = read_bytes; - } - - return rc; -} - -static void ssl_accepted(struct net_context *context, - struct sockaddr *addr, - socklen_t addrlen, int error, void *user_data) -{ - int ret; - struct ssl_context *ctx = user_data; - - ctx->net_ctx = context; - ret = net_context_recv(context, ssl_received, 0, user_data); - if (ret < 0) { - printk("Cannot receive TCP packet (family %d)", - net_context_get_family(context)); - } - -} - -#if defined(CONFIG_NET_IPV6) -int ssl_init(struct ssl_context *ctx, void *addr) -{ - struct net_context *tcp_ctx = { 0 }; - struct sockaddr_in6 my_addr = { 0 }; - struct in6_addr *server_addr = addr; - int rc; - - k_sem_init(&ctx->tx_sem, 0, UINT_MAX); - k_fifo_init(&ctx->rx_fifo); - - my_mcast_addr.sin6_family = AF_INET6; - - net_ipaddr_copy(&my_addr.sin6_addr, server_addr); - my_addr.sin6_family = AF_INET6; - my_addr.sin6_port = htons(SERVER_PORT); - - rc = net_context_get(AF_INET6, SOCK_STREAM, IPPROTO_TCP, &tcp_ctx); - if (rc < 0) { - printk("Cannot get network context for IPv6 TCP (%d)", rc); - return -EIO; - } - - rc = net_context_bind(tcp_ctx, (struct sockaddr *)&my_addr, - sizeof(struct sockaddr_in6)); - if (rc < 0) { - printk("Cannot bind IPv6 TCP port %d (%d)", SERVER_PORT, rc); - goto error; - } - - ctx->rx_pkt = NULL; - ctx->remaining = 0; - ctx->net_ctx = tcp_ctx; - - rc = net_context_listen(ctx->net_ctx, 0); - if (rc < 0) { - printk("Cannot listen IPv6 TCP (%d)", rc); - return -EIO; - } - - rc = net_context_accept(ctx->net_ctx, ssl_accepted, 0, ctx); - if (rc < 0) { - printk("Cannot accept IPv4 (%d)", rc); - return -EIO; - } - - return 0; - -error: - net_context_put(tcp_ctx); - return -EINVAL; -} - -#else -int ssl_init(struct ssl_context *ctx, void *addr) -{ - struct net_context *tcp_ctx = { 0 }; - struct sockaddr_in my_addr4 = { 0 }; - struct in_addr *server_addr = addr; - int rc; - - k_sem_init(&ctx->tx_sem, 0, UINT_MAX); - k_fifo_init(&ctx->rx_fifo); - - net_ipaddr_copy(&my_addr4.sin_addr, server_addr); - my_addr4.sin_family = AF_INET; - my_addr4.sin_port = htons(SERVER_PORT); - - rc = net_context_get(AF_INET, SOCK_STREAM, IPPROTO_TCP, &tcp_ctx); - if (rc < 0) { - printk("Cannot get network context for IPv4 TCP (%d)", rc); - return -EIO; - } - - rc = net_context_bind(tcp_ctx, (struct sockaddr *)&my_addr4, - sizeof(struct sockaddr_in)); - if (rc < 0) { - printk("Cannot bind IPv4 TCP port %d (%d)", SERVER_PORT, rc); - goto error; - } - - ctx->rx_pkt = NULL; - ctx->remaining = 0; - ctx->net_ctx = tcp_ctx; - - rc = net_context_listen(ctx->net_ctx, 0); - if (rc < 0) { - printk("Cannot listen IPv4 (%d)", rc); - return -EIO; - } - - rc = net_context_accept(ctx->net_ctx, ssl_accepted, 0, ctx); - if (rc < 0) { - printk("Cannot accept IPv4 (%d)", rc); - return -EIO; - } - - return 0; - -error: - net_context_put(tcp_ctx); - return -EINVAL; -} -#endif diff --git a/samples/net/http_server/src/ssl_utils.h b/samples/net/http_server/src/ssl_utils.h deleted file mode 100644 index 34dedb5f2e7..00000000000 --- a/samples/net/http_server/src/ssl_utils.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2017 Intel Corporation - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#ifndef _SSL_UTILS_H_ -#define _SSL_UTILS_H_ - -#include -#include - -struct rx_fifo_block { - sys_snode_t snode; - struct k_mem_block block; - struct net_buf *buf; -}; - -struct ssl_context { - struct net_context *net_ctx; - struct net_buf *rx_pkt; - struct net_buf *frag; - struct k_sem tx_sem; - struct k_fifo rx_fifo; - struct http_parser_settings parser_settings; - struct http_parser parser; - - int remaining; -}; - -int ssl_init(struct ssl_context *ctx, void *addr); -int ssl_tx(void *ctx, const unsigned char *buf, size_t size); -int ssl_rx(void *ctx, unsigned char *buf, size_t size); - -void https_server_start(void); - -#endif diff --git a/samples/net/http_server/testcase.ini b/samples/net/http_server/testcase.ini index 3112d0bd212..6795e2df4d8 100644 --- a/samples/net/http_server/testcase.ini +++ b/samples/net/http_server/testcase.ini @@ -1,10 +1,10 @@ [test] tags = net http build_only = true -platform_whitelist = frdm_k64f qemu_x86 arduino_101 +platform_whitelist = frdm_k64f qemu_x86 [test_bt] tags = net bluetooth build_only = true extra_args = CONF_FILE="prj_bt.conf" -platform_whitelist = arduino_101 +platform_whitelist = qemu_x86 diff --git a/subsys/net/lib/http/Kconfig b/subsys/net/lib/http/Kconfig index f5fa39d5b58..12ae829da11 100644 --- a/subsys/net/lib/http/Kconfig +++ b/subsys/net/lib/http/Kconfig @@ -13,9 +13,25 @@ config HTTP_SERVER bool "HTTP server support" default n select HTTP + select HTTP_PARSER help Enables HTTP server routines +config HTTP_SERVER_CONNECTIONS + int "Max number of HTTP server connections" + default NET_MAX_CONTEXTS + depends on HTTP_SERVER + help + This value determines how many simultaneous HTTP connections the + HTTP server can serve. + +config HTTP_SERVER_NUM_URLS + int "Max number of URLs that the HTTP server will handle" + default 8 + depends on HTTP_SERVER + help + This value determines how many URLs this HTTP server can handle. + config HTTP_HEADER_FIELD_ITEMS int "HTTP header field max number of items" depends on HTTP_SERVER @@ -48,6 +64,33 @@ config HTTP_PARSER_STRICT help This option enables the strict parsing option +config HTTPS + bool "HTTPS support" + default n + depends on HTTP + depends on MBEDTLS + help + Enables HTTPS support. + +config HTTPS_STACK_SIZE + int "HTTPS thread stack size" + default 8192 + depends on HTTPS + help + HTTPS thread stack size. The mbedtls routines will use this stack + thus it is by default very large. + +config HTTPS_HEAP_SIZE + int "HTTPS heap size for mbedtls" + default 12000 + depends on HTTPS + depends on MBEDTLS + help + HTTPS heap size. The mbedtls routines will use this heap if enabled. + See ext/lib/crypto/mbedtls/include/mbedtls/config.h and + MBEDTLS_MEMORY_BUFFER_ALLOC_C option for details. This option is not + enabled by default. + config NET_DEBUG_HTTP bool "Debug HTTP" default n diff --git a/subsys/net/lib/http/http_server.c b/subsys/net/lib/http/http_server.c index 1d026058297..97a8742c24f 100644 --- a/subsys/net/lib/http/http_server.c +++ b/subsys/net/lib/http/http_server.c @@ -4,11 +4,45 @@ * SPDX-License-Identifier: Apache-2.0 */ -#include +#if defined(CONFIG_NET_DEBUG_HTTP) +#if defined(CONFIG_HTTPS) +#define SYS_LOG_DOMAIN "https/server" +#else +#define SYS_LOG_DOMAIN "http/server" +#endif +#define NET_LOG_ENABLED 1 +#endif + +#define RX_EXTRA_DEBUG 0 + #include + +#include #include #include +#include + +#if defined(CONFIG_HTTPS) +static void https_enable(struct http_server_ctx *ctx); +static void https_disable(struct http_server_ctx *ctx); + +#if defined(MBEDTLS_DEBUG_C) +#include +#define DEBUG_THRESHOLD 0 +#endif + +#if defined(MBEDTLS_MEMORY_BUFFER_ALLOC_C) +#include +static unsigned char heap[CONFIG_HTTPS_HEAP_SIZE]; +#endif +#endif + +#define HTTP_DEFAULT_PORT 80 +#define HTTPS_DEFAULT_PORT 443 + +#define RC_STR(rc) (rc == 0 ? "OK" : "ERROR") + #define HTTP_STATUS_200_OK "HTTP/1.1 200 OK\r\n" \ "Content-Type: text/html\r\n" \ "Transfer-Encoding: chunked\r\n" \ @@ -32,83 +66,100 @@ static inline u16_t http_strlen(const char *str) return 0; } -static int http_add_header(struct net_pkt *tx, s32_t timeout, const char *str) +static int http_add_header(struct net_pkt *pkt, s32_t timeout, const char *str) { - if (net_pkt_append_all(tx, strlen(str), (u8_t *)str, timeout)) { + if (net_pkt_append_all(pkt, strlen(str), (u8_t *)str, timeout)) { return 0; } return -ENOMEM; } -static int http_add_chunk(struct net_pkt *tx, s32_t timeout, const char *str) +static int http_add_chunk(struct net_pkt *pkt, s32_t timeout, const char *str) { char chunk_header[16]; - char *rn = "\r\n"; u16_t str_len; + bool ret; str_len = http_strlen(str); snprintk(chunk_header, sizeof(chunk_header), "%x\r\n", str_len); - if (!net_pkt_append_all(tx, strlen(chunk_header), chunk_header, - timeout)) { + ret = net_pkt_append_all(pkt, strlen(chunk_header), chunk_header, + timeout); + if (!ret) { return -ENOMEM; } if (str_len > 0) { - if (!net_pkt_append_all(tx, str_len, (u8_t *)str, timeout)) { + ret = net_pkt_append_all(pkt, str_len, (u8_t *)str, timeout); + if (!ret) { return -ENOMEM; } } - if (!net_pkt_append_all(tx, strlen(rn), rn, timeout)) { + ret = net_pkt_append_all(pkt, sizeof(HTTP_CRLF) - 1, HTTP_CRLF, + timeout); + if (!ret) { return -ENOMEM; } return 0; } +static void pkt_sent(struct net_context *context, + int status, + void *token, + void *user_data) +{ + /* We can just close the context after the packet is sent. */ + net_context_unref(context); +} + int http_response(struct http_server_ctx *ctx, const char *http_header, const char *html_payload) { - struct net_pkt *tx; - int rc = -EINVAL; + struct net_pkt *pkt; + int ret = -EINVAL; - tx = net_pkt_get_tx(ctx->net_ctx, ctx->timeout); - if (!tx) { - return rc; + pkt = net_pkt_get_tx(ctx->req.net_ctx, ctx->timeout); + if (!pkt) { + return ret; } - rc = http_add_header(tx, ctx->timeout, http_header); - if (rc != 0) { + ret = http_add_header(pkt, ctx->timeout, http_header); + if (ret != 0) { goto exit_routine; } if (html_payload) { - rc = http_add_chunk(tx, ctx->timeout, html_payload); - if (rc != 0) { + ret = http_add_chunk(pkt, ctx->timeout, html_payload); + if (ret != 0) { goto exit_routine; } /* like EOF */ - rc = http_add_chunk(tx, ctx->timeout, NULL); - if (rc != 0) { + ret = http_add_chunk(pkt, ctx->timeout, NULL); + if (ret != 0) { goto exit_routine; } } - rc = net_context_send(tx, NULL, 0, NULL, NULL); - if (rc != 0) { + net_pkt_set_appdatalen(pkt, net_buf_frags_len(pkt->frags)); + + ret = ctx->send_data(pkt, pkt_sent, 0, NULL, ctx); + if (ret != 0) { goto exit_routine; } - tx = NULL; + pkt = NULL; exit_routine: - net_pkt_unref(tx); + if (pkt) { + net_pkt_unref(pkt); + } - return rc; + return ret; } int http_response_400(struct http_server_ctx *ctx, const char *html_payload) @@ -125,3 +176,1342 @@ int http_response_404(struct http_server_ctx *ctx, const char *html_payload) { return http_response(ctx, HTTP_STATUS_404_NF, html_payload); } + +int http_server_set_local_addr(struct sockaddr *addr, const char *myaddr, + u16_t port) +{ + if (!addr) { + return -EINVAL; + } + + if (myaddr) { + void *inaddr; + + if (addr->family == AF_INET) { +#if defined(CONFIG_NET_IPV4) + inaddr = &net_sin(addr)->sin_addr; + net_sin(addr)->sin_port = htons(port); +#else + return -EPFNOSUPPORT; +#endif + } else if (addr->family == AF_INET6) { +#if defined(CONFIG_NET_IPV6) + inaddr = &net_sin6(addr)->sin6_addr; + net_sin6(addr)->sin6_port = htons(port); +#else + return -EPFNOSUPPORT; +#endif + } else { + return -EAFNOSUPPORT; + } + + return net_addr_pton(addr->family, myaddr, inaddr); + } + + /* If the caller did not supply the address where to bind, then + * try to figure it out ourselves. + */ + if (addr->family == AF_INET6) { +#if defined(CONFIG_NET_IPV6) + net_ipaddr_copy(&net_sin6(addr)->sin6_addr, + net_if_ipv6_select_src_addr(NULL, + (struct in6_addr *) + net_ipv6_unspecified_address())); +#else + return -EPFNOSUPPORT; +#endif + } else if (addr->family == AF_INET) { +#if defined(CONFIG_NET_IPV4) + struct net_if *iface = net_if_get_default(); + + /* For IPv4 we take the first address in the interface */ + net_ipaddr_copy(&net_sin(addr)->sin_addr, + &iface->ipv4.unicast[0].address.in_addr); +#else + return -EPFNOSUPPORT; +#endif + } + + return 0; +} + +struct http_root_url *http_server_add_url(struct http_server_urls *my, + const char *url, u8_t flags, + http_url_cb_t write_cb) +{ + int i; + + for (i = 0; i < CONFIG_HTTP_SERVER_NUM_URLS; i++) { + if (my->urls[i].is_used) { + continue; + } + + my->urls[i].is_used = true; + my->urls[i].root = url; + + /* This will speed-up some future operations */ + my->urls[i].root_len = strlen(url); + my->urls[i].flags = flags; + my->urls[i].write_cb = write_cb; + + return &my->urls[i]; + } + + return NULL; +} + +int http_server_del_url(struct http_server_urls *my, const char *url) +{ + int i; + + for (i = 0; i < CONFIG_HTTP_SERVER_NUM_URLS; i++) { + if (!my->urls[i].is_used) { + continue; + } + + if (strncmp(my->urls[i].root, url, my->urls[i].root_len)) { + continue; + } + + my->urls[i].is_used = false; + my->urls[i].root = NULL; + + return 0; + } + + return -ENOENT; +} + +struct http_root_url *http_server_add_default(struct http_server_urls *my, + http_url_cb_t write_cb) +{ + if (my->default_url.is_used) { + return NULL; + } + + my->default_url.is_used = true; + my->default_url.root = NULL; + my->default_url.root_len = 0; + my->default_url.flags = 0; + my->default_url.write_cb = write_cb; + + return &my->default_url; +} + +int http_server_del_default(struct http_server_urls *my) +{ + if (!my->default_url.is_used) { + return -ENOENT; + } + + my->default_url.is_used = false; + + return 0; +} + +#if defined(CONFIG_NET_DEBUG_HTTP) && (CONFIG_SYS_LOG_NET_LEVEL > 2) +static char *sprint_ipaddr(char *buf, int buflen, const struct sockaddr *addr) +{ + if (addr->family == AF_INET6) { +#if defined(CONFIG_NET_IPV6) + char ipaddr[NET_IPV6_ADDR_LEN]; + + net_addr_ntop(addr->family, + &net_sin6(addr)->sin6_addr, + ipaddr, sizeof(ipaddr)); + snprintk(buf, buflen, "[%s]:%u", ipaddr, + ntohs(net_sin6(addr)->sin6_port)); +#endif + } else if (addr->family == AF_INET) { +#if defined(CONFIG_NET_IPV4) + char ipaddr[NET_IPV4_ADDR_LEN]; + + net_addr_ntop(addr->family, + &net_sin(addr)->sin_addr, + ipaddr, sizeof(ipaddr)); + snprintk(buf, buflen, "%s:%u", ipaddr, + ntohs(net_sin(addr)->sin_port)); +#endif + } + + return buf; +} +#endif /* CONFIG_NET_DEBUG_HTTP */ + +static inline void new_client(struct http_server_ctx *http_ctx, + struct net_context *net_ctx, + const struct sockaddr *addr) +{ +#if defined(CONFIG_NET_DEBUG_HTTP) && (CONFIG_SYS_LOG_NET_LEVEL > 2) +#if defined(CONFIG_NET_IPV6) +#define PORT_STR sizeof("[]:xxxxx") + char buf[NET_IPV6_ADDR_LEN + PORT_STR]; +#elif defined(CONFIG_NET_IPV4) && !defined(CONFIG_NET_IPV6) +#define PORT_STR sizeof(":xxxxx") + char buf[NET_IPV4_ADDR_LEN + PORT_STR]; +#endif + + NET_INFO("%s connection from %s (%p)", + http_ctx->is_https ? "HTTPS" : "HTTP", + sprint_ipaddr(buf, sizeof(buf), addr), + net_ctx); +#endif /* CONFIG_NET_DEBUG_HTTP */ +} + +static inline void new_server(struct http_server_ctx *ctx, + const char *server_banner, + const struct sockaddr *addr) +{ +#if defined(CONFIG_NET_DEBUG_HTTP) && (CONFIG_SYS_LOG_NET_LEVEL > 2) +#if defined(CONFIG_NET_IPV6) +#define PORT_STR sizeof("[]:xxxxx") + char buf[NET_IPV6_ADDR_LEN + PORT_STR]; +#elif defined(CONFIG_NET_IPV4) && !defined(CONFIG_NET_IPV6) +#define PORT_STR sizeof(":xxxxx") + char buf[NET_IPV4_ADDR_LEN + PORT_STR]; +#endif + + if (addr) { + NET_INFO("%s %s (%p)", server_banner, + sprint_ipaddr(buf, sizeof(buf), addr), ctx); + } else { + NET_INFO("%s (%p)", server_banner, ctx); + } +#endif /* CONFIG_NET_DEBUG_HTTP */ +} + +static int on_header_field(struct http_parser *parser, + const char *at, size_t length) +{ + struct http_server_ctx *ctx = parser->data; + + if (ctx->req.field_values_ctr >= CONFIG_HTTP_HEADER_FIELD_ITEMS) { + return 0; + } + + ctx->req.field_values[ctx->req.field_values_ctr].key = at; + ctx->req.field_values[ctx->req.field_values_ctr].key_len = length; + + return 0; +} + +static int on_header_value(struct http_parser *parser, + const char *at, size_t length) +{ + struct http_server_ctx *ctx = parser->data; + + if (ctx->req.field_values_ctr >= CONFIG_HTTP_HEADER_FIELD_ITEMS) { + return 0; + } + + ctx->req.field_values[ctx->req.field_values_ctr].value = at; + ctx->req.field_values[ctx->req.field_values_ctr].value_len = length; + + ctx->req.field_values_ctr++; + + return 0; +} + +static int on_url(struct http_parser *parser, const char *at, size_t length) +{ + struct http_server_ctx *ctx = parser->data; + + ctx->req.url = at; + ctx->req.url_len = length; + + return 0; +} + +static int parser_init(struct http_server_ctx *ctx) +{ + memset(ctx->req.field_values, 0, sizeof(ctx->req.field_values)); + + ctx->req.settings.on_header_field = on_header_field; + ctx->req.settings.on_header_value = on_header_value; + ctx->req.settings.on_url = on_url; + + http_parser_init(&ctx->req.parser, HTTP_REQUEST); + + ctx->req.parser.data = ctx; + + return 0; +} + +static int http_url_cmp(const char *url, u16_t url_len, + const char *root_url, u16_t root_url_len) +{ + if (url_len < root_url_len) { + return -EINVAL; + } + + if (memcmp(url, root_url, root_url_len) == 0) { + if (url_len == root_url_len) { + return 0; + } + + /* Here we evaluate the following conditions: + * root_url = /images, url = /images/ -> OK + * root_url = /images/, url = /images/img.png -> OK + * root_url = /images/, url = /images_and_docs -> ERROR + */ + if (url_len > root_url_len) { + if (root_url[root_url_len - 1] == '/') { + return 0; + } + + if (url[root_url_len] == '/') { + return 0; + } + } + } + + return -EINVAL; +} + +static struct http_root_url *http_url_find(struct http_server_ctx *http_ctx) +{ + u16_t url_len = http_ctx->req.url_len; + const char *url = http_ctx->req.url; + struct http_root_url *root_url; + u8_t i; + int ret; + + for (i = 0; i < CONFIG_HTTP_SERVER_NUM_URLS; i++) { + root_url = &http_ctx->urls->urls[i]; + if (!root_url->is_used) { + continue; + } + + ret = http_url_cmp(url, url_len, + root_url->root, root_url->root_len); + if (!ret) { + return root_url; + } + } + + return NULL; +} + +static int http_process_recv(struct http_server_ctx *http_ctx) +{ + struct http_root_url *root_url; + int ret; + + root_url = http_url_find(http_ctx); + if (!root_url) { + root_url = &http_ctx->urls->default_url; + if (!root_url->is_used) { + NET_DBG("No default handler found (%p)", http_ctx); + ret = -ENOENT; + goto out; + } + } + + if (root_url->write_cb) { + NET_DBG("Calling handler %p context %p", + root_url->write_cb, http_ctx); + ret = root_url->write_cb(http_ctx); + } else { + NET_ERR("No handler for %s", http_ctx->req.url); + ret = -ENOENT; + } + +out: + return ret; +} + +static void http_recv(struct net_context *net_ctx, + struct net_pkt *pkt, int status, + void *user_data) +{ + struct http_server_ctx *http_ctx = user_data; + struct net_buf *frag; + size_t start = http_ctx->req.data_len; + int parsed_len; + int header_len; + u16_t len = 0, recv_len; + + if (!pkt) { + NET_DBG("Connection closed by peer"); + return; + } + + if (!http_ctx->enabled) { + goto quit; + } + + recv_len = net_pkt_appdatalen(pkt); + if (recv_len == 0) { + /* don't print info about zero-length app data buffers */ + goto quit; + } + + if (status) { + NET_DBG("Status %d <%s>", status, RC_STR(status)); + goto out; + } + + /* Get rid of possible IP headers in the first fragment. */ + frag = pkt->frags; + + header_len = net_pkt_appdata(pkt) - frag->data; + + NET_DBG("Received %d bytes data", net_pkt_appdatalen(pkt)); + + /* After this pull, the frag->data points directly to application data. + */ + net_buf_pull(frag, header_len); + + while (frag) { + /* If this fragment cannot be copied to result buf, + * then parse what we have which will cause the callback to be + * called in function on_body(), and continue copying. + */ + if (http_ctx->req.data_len + frag->len > + http_ctx->req.request_buf_len) { + + /* If the caller has not supplied a callback, then + * we cannot really continue if the request buffer + * overflows. Set the data_len to mark how many bytes + * should be needed in the response_buf. + */ + if (http_process_recv(http_ctx) < 0) { + http_ctx->req.data_len = net_pkt_get_len(pkt); + goto out; + } + + parsed_len = + http_parser_execute(&http_ctx->req.parser, + &http_ctx->req.settings, + http_ctx->req.request_buf + + start, + len); + if (parsed_len <= 0) { + goto fail; + } + + http_ctx->req.data_len = 0; + len = 0; + start = 0; + } + + memcpy(http_ctx->req.request_buf + http_ctx->req.data_len, + frag->data, frag->len); + + http_ctx->req.data_len += frag->len; + len += frag->len; + frag = frag->frags; + } + +out: + parsed_len = http_parser_execute(&http_ctx->req.parser, + &http_ctx->req.settings, + http_ctx->req.request_buf + start, + len); + if (parsed_len < 0) { +fail: + NET_DBG("Received %u bytes, only parsed %d bytes (%s %s)", + recv_len, parsed_len, + http_errno_name(http_ctx->req.parser.http_errno), + http_errno_description( + http_ctx->req.parser.http_errno)); + } + + if (http_ctx->req.parser.http_errno != HPE_OK) { + http_response_400(http_ctx, NULL); + } else { + http_process_recv(http_ctx); + } + +quit: + http_ctx->req.data_len = 0; + net_pkt_unref(pkt); +} + +static void accept_cb(struct net_context *net_ctx, + struct sockaddr *addr, socklen_t addrlen, + int status, void *data) +{ + struct http_server_ctx *http_ctx = data; + + ARG_UNUSED(addr); + ARG_UNUSED(addrlen); + + if (status != 0) { + net_context_put(net_ctx); + return; + } + + http_ctx->req.net_ctx = net_ctx; + + new_client(http_ctx, net_ctx, addr); + + net_context_recv(net_ctx, http_ctx->recv_cb, K_NO_WAIT, http_ctx); +} + +static int set_net_ctx(struct http_server_ctx *http_ctx, + struct net_context *ctx, + struct sockaddr *addr, + socklen_t socklen) +{ + int ret; + + ret = net_context_bind(ctx, addr, socklen); + if (ret < 0) { + NET_ERR("Cannot bind context (%d)", ret); + goto out; + } + + ret = net_context_listen(ctx, 0); + if (ret < 0) { + NET_ERR("Cannot listen context (%d)", ret); + goto out; + } + + ret = net_context_accept(ctx, accept_cb, 0, http_ctx); + if (ret < 0) { + NET_ERR("Cannot accept context (%d)", ret); + goto out; + } + +out: + return ret; +} + +#if defined(CONFIG_NET_IPV4) +static int setup_ipv4_ctx(struct http_server_ctx *http_ctx, + struct sockaddr *addr) +{ + socklen_t socklen; + int ret; + + socklen = sizeof(struct sockaddr_in); + + ret = net_context_get(AF_INET, SOCK_STREAM, IPPROTO_TCP, + &http_ctx->net_ipv4_ctx); + if (ret < 0) { + NET_ERR("Cannot get network context (%d)", ret); + http_ctx->net_ipv4_ctx = NULL; + return ret; + } + + if (addr->family == AF_UNSPEC) { + addr->family = AF_INET; + + http_server_set_local_addr(addr, NULL, + net_sin(addr)->sin_port); + } + + ret = set_net_ctx(http_ctx, http_ctx->net_ipv4_ctx, + addr, socklen); + if (ret < 0) { + net_context_put(http_ctx->net_ipv4_ctx); + http_ctx->net_ipv4_ctx = NULL; + } + + return ret; +} +#endif + +#if defined(CONFIG_NET_IPV6) +int setup_ipv6_ctx(struct http_server_ctx *http_ctx, struct sockaddr *addr) +{ + socklen_t socklen; + int ret; + + socklen = sizeof(struct sockaddr_in6); + + ret = net_context_get(AF_INET6, SOCK_STREAM, IPPROTO_TCP, + &http_ctx->net_ipv6_ctx); + if (ret < 0) { + NET_ERR("Cannot get network context (%d)", ret); + http_ctx->net_ipv6_ctx = NULL; + return ret; + } + + if (addr->family == AF_UNSPEC) { + addr->family = AF_INET6; + + http_server_set_local_addr(addr, NULL, + net_sin6(addr)->sin6_port); + } + + ret = set_net_ctx(http_ctx, http_ctx->net_ipv6_ctx, + addr, socklen); + if (ret < 0) { + net_context_put(http_ctx->net_ipv6_ctx); + http_ctx->net_ipv6_ctx = NULL; + } + + return ret; +} +#endif + +static int init_net(struct http_server_ctx *ctx, + struct sockaddr *server_addr, + u16_t port) +{ + struct sockaddr addr; + int ret; + + memset(&addr, 0, sizeof(addr)); + + if (server_addr) { + memcpy(&addr, server_addr, sizeof(addr)); + } else { + addr.family = AF_UNSPEC; + net_sin(&addr)->sin_port = htons(port); + } + + if (addr.family == AF_INET6) { +#if defined(CONFIG_NET_IPV6) + ret = setup_ipv6_ctx(ctx, &addr); +#else + return -EPFNOSUPPORT; +#endif + } else if (addr.family == AF_INET) { +#if defined(CONFIG_NET_IPV4) + ret = setup_ipv4_ctx(ctx, &addr); +#else + return -EPFNOSUPPORT; +#endif + } else if (addr.family == AF_UNSPEC) { +#if defined(CONFIG_NET_IPV4) + ret = setup_ipv4_ctx(ctx, &addr); +#endif + /* We ignore the IPv4 error if IPv6 is enabled */ +#if defined(CONFIG_NET_IPV6) + memset(&addr, 0, sizeof(addr)); + addr.family = AF_UNSPEC; + net_sin6(&addr)->sin6_port = htons(port); + + ret = setup_ipv6_ctx(ctx, &addr); +#endif + } else { + return -EINVAL; + } + + return ret; +} + +bool http_server_enable(struct http_server_ctx *http_ctx) +{ + bool old; + + NET_ASSERT(http_ctx); + + old = http_ctx->enabled; + + http_ctx->enabled = true; + +#if defined(CONFIG_HTTPS) + if (http_ctx->is_https) { + https_enable(http_ctx); + } +#endif + return old; +} + +bool http_server_disable(struct http_server_ctx *http_ctx) +{ + bool old; + + NET_ASSERT(http_ctx); + + old = http_ctx->enabled; + + http_ctx->enabled = false; + +#if defined(CONFIG_HTTPS) + if (http_ctx->is_https) { + https_disable(http_ctx); + } +#endif + return old; +} + +int http_server_init(struct http_server_ctx *http_ctx, + struct http_server_urls *urls, + struct sockaddr *server_addr, + u8_t *request_buf, + size_t request_buf_len, + const char *server_banner) +{ + int ret; + + if (http_ctx->urls) { + NET_ERR("Server context %p already initialized", http_ctx); + return -EINVAL; + } + + if (!request_buf || request_buf_len == 0) { + NET_ERR("Request buf must be set"); + return -EINVAL; + } + + ret = init_net(http_ctx, server_addr, HTTP_DEFAULT_PORT); + if (ret < 0) { + return ret; + } + + if (server_banner) { + new_server(http_ctx, server_banner, server_addr); + } + + http_ctx->req.request_buf = request_buf; + http_ctx->req.request_buf_len = request_buf_len; + http_ctx->req.data_len = 0; + http_ctx->urls = urls; + http_ctx->recv_cb = http_recv; + http_ctx->send_data = net_context_send; + + parser_init(http_ctx); + + return 0; +} + +void http_server_release(struct http_server_ctx *http_ctx) +{ + if (!http_ctx->urls) { + return; + } + + http_server_disable(http_ctx); + +#if defined(CONFIG_NET_IPV4) + if (http_ctx->net_ipv4_ctx) { + net_context_put(http_ctx->net_ipv4_ctx); + http_ctx->net_ipv4_ctx = NULL; + } +#endif +#if defined(CONFIG_NET_IPV6) + if (http_ctx->net_ipv6_ctx) { + net_context_put(http_ctx->net_ipv6_ctx); + http_ctx->net_ipv6_ctx = NULL; + } +#endif + + http_ctx->req.net_ctx = NULL; + http_ctx->urls = NULL; +} + +#if defined(CONFIG_HTTPS) +struct rx_fifo_block { + sys_snode_t snode; + struct k_mem_block block; + struct net_pkt *pkt; +}; + +#if defined(MBEDTLS_DEBUG_C) && defined(CONFIG_NET_DEBUG_HTTP) +static void my_debug(void *ctx, int level, + const char *file, int line, const char *str) +{ + const char *p, *basename; + + ARG_UNUSED(ctx); + + /* Extract basename from file */ + for (p = basename = file; *p != '\0'; p++) { + if (*p == '/' || *p == '\\') { + basename = p + 1; + } + + } + + NET_DBG("%s:%04d: |%d| %s", basename, line, level, str); +} +#endif /* MBEDTLS_DEBUG_C && CONFIG_NET_DEBUG_HTTP */ + +#if defined(MBEDTLS_ERROR_C) +#define print_error(fmt, ret) \ + do { \ + char error[64]; \ + \ + mbedtls_strerror(ret, error, sizeof(error)); \ + \ + NET_ERR(fmt " (%s)", -ret, error); \ + } while (0) +#else +#define print_error(fmt, ret) \ + do { \ + NET_ERR(fmt, -ret); \ + } while (0) +#endif + +#define BUF_ALLOC_TIMEOUT 100 + +/* Receive encrypted data from network. Put that data into fifo + * that will be read by https thread. + */ +static void ssl_received(struct net_context *context, + struct net_pkt *pkt, + int status, + void *user_data) +{ + struct http_server_ctx *http_ctx = user_data; + struct rx_fifo_block *rx_data = NULL; + struct k_mem_block block; + int ret; + + ARG_UNUSED(context); + ARG_UNUSED(status); + + if (!pkt) { + return; + } + + if (!net_pkt_appdatalen(pkt)) { + net_pkt_unref(pkt); + return; + } + + ret = k_mem_pool_alloc(http_ctx->https.pool, &block, + sizeof(struct rx_fifo_block), + BUF_ALLOC_TIMEOUT); + if (ret < 0) { + net_pkt_unref(pkt); + return; + } + + rx_data = block.data; + rx_data->pkt = pkt; + + /* For freeing memory later */ + memcpy(&rx_data->block, &block, sizeof(struct k_mem_block)); + + k_fifo_put(&http_ctx->https.mbedtls.ssl_ctx.rx_fifo, (void *)rx_data); +} + +/* This will copy data from received net_pkt buf into mbedtls internal buffers. + */ +static int ssl_rx(void *context, unsigned char *buf, size_t size) +{ + struct http_server_ctx *ctx = context; + struct rx_fifo_block *rx_data; + u16_t read_bytes; + u8_t *ptr; + int pos; + int len; + int ret = 0; + + if (!ctx->https.mbedtls.ssl_ctx.frag) { + rx_data = k_fifo_get(&ctx->https.mbedtls.ssl_ctx.rx_fifo, + K_FOREVER); + + ctx->https.mbedtls.ssl_ctx.rx_pkt = rx_data->pkt; + + k_mem_pool_free(&rx_data->block); + + read_bytes = net_pkt_appdatalen( + ctx->https.mbedtls.ssl_ctx.rx_pkt); + + ctx->https.mbedtls.ssl_ctx.remaining = read_bytes; + ctx->https.mbedtls.ssl_ctx.frag = + ctx->https.mbedtls.ssl_ctx.rx_pkt->frags; + + ptr = net_pkt_appdata(ctx->https.mbedtls.ssl_ctx.rx_pkt); + len = ptr - ctx->https.mbedtls.ssl_ctx.frag->data; + + if (len > ctx->https.mbedtls.ssl_ctx.frag->size) { + NET_ERR("Buf overflow (%d > %u)", len, + ctx->https.mbedtls.ssl_ctx.frag->size); + return -EINVAL; + } else { + /* This will get rid of IP header */ + net_buf_pull(ctx->https.mbedtls.ssl_ctx.frag, len); + } + } else { + read_bytes = ctx->https.mbedtls.ssl_ctx.remaining; + ptr = ctx->https.mbedtls.ssl_ctx.frag->data; + } + + len = ctx->https.mbedtls.ssl_ctx.frag->len; + pos = 0; + if (read_bytes > size) { + while (ctx->https.mbedtls.ssl_ctx.frag) { + read_bytes = len < (size - pos) ? len : (size - pos); + +#if RX_EXTRA_DEBUG == 1 + NET_DBG("Copying %d bytes", read_bytes); +#endif + + memcpy(buf + pos, ptr, read_bytes); + + pos += read_bytes; + if (pos < size) { + ctx->https.mbedtls.ssl_ctx.frag = + ctx->https.mbedtls.ssl_ctx.frag->frags; + ptr = ctx->https.mbedtls.ssl_ctx.frag->data; + len = ctx->https.mbedtls.ssl_ctx.frag->len; + } else { + if (read_bytes == len) { + ctx->https.mbedtls.ssl_ctx.frag = + ctx->https.mbedtls.ssl_ctx.frag->frags; + } else { + net_buf_pull( + ctx->https.mbedtls.ssl_ctx.frag, + read_bytes); + } + + ctx->https.mbedtls.ssl_ctx.remaining -= size; + return size; + } + } + } else { + while (ctx->https.mbedtls.ssl_ctx.frag) { +#if RX_EXTRA_DEBUG == 1 + NET_DBG("Copying all %d bytes", len); +#endif + + memcpy(buf + pos, ptr, len); + + pos += len; + ctx->https.mbedtls.ssl_ctx.frag = + ctx->https.mbedtls.ssl_ctx.frag->frags; + if (!ctx->https.mbedtls.ssl_ctx.frag) { + break; + } + + ptr = ctx->https.mbedtls.ssl_ctx.frag->data; + len = ctx->https.mbedtls.ssl_ctx.frag->len; + } + + net_pkt_unref(ctx->https.mbedtls.ssl_ctx.rx_pkt); + ctx->https.mbedtls.ssl_ctx.rx_pkt = NULL; + ctx->https.mbedtls.ssl_ctx.frag = NULL; + ctx->https.mbedtls.ssl_ctx.remaining = 0; + + if (read_bytes != pos) { + return -EIO; + } + + ret = read_bytes; + } + + return ret; +} + +static void ssl_sent(struct net_context *context, + int status, void *token, void *user_data) +{ + struct http_server_ctx *http_ctx = user_data; + + k_sem_give(&http_ctx->https.mbedtls.ssl_ctx.tx_sem); +} + +/* Send encrypted data */ +static int ssl_tx(void *context, const unsigned char *buf, size_t size) +{ + struct http_server_ctx *ctx = context; + struct net_pkt *send_buf; + int ret, len; + + send_buf = net_pkt_get_tx(ctx->req.net_ctx, BUF_ALLOC_TIMEOUT); + if (!send_buf) { + return MBEDTLS_ERR_SSL_ALLOC_FAILED; + } + + ret = net_pkt_append_all(send_buf, size, (u8_t *)buf, + BUF_ALLOC_TIMEOUT); + if (!ret) { + /* Cannot append data */ + net_pkt_unref(send_buf); + return 0; + } + + len = size; + + ret = net_context_send(send_buf, ssl_sent, K_NO_WAIT, NULL, ctx); + if (ret < 0) { + net_pkt_unref(send_buf); + return MBEDTLS_ERR_SSL_INTERNAL_ERROR; + } + + k_sem_take(&ctx->https.mbedtls.ssl_ctx.tx_sem, K_FOREVER); + + return len; +} + +static int entropy_source(void *data, unsigned char *output, size_t len, + size_t *olen) +{ + u32_t seed; + + ARG_UNUSED(data); + + seed = sys_rand32_get(); + + if (len > sizeof(seed)) { + len = sizeof(seed); + } + + memcpy(output, &seed, len); + + *olen = len; + return 0; +} + +/* This gets plain data and it sends encrypted one to peer */ +static int https_send(struct net_pkt *pkt, + net_context_send_cb_t cb, + s32_t timeout, + void *token, + void *user_data) +{ + struct http_server_ctx *ctx = user_data; + int ret; + u16_t len; + + len = net_pkt_appdatalen(pkt); + + ret = net_frag_linearize(ctx->req.request_buf, + ctx->req.request_buf_len, + pkt, net_pkt_ip_hdr_len(pkt), + len); + if (ret < 0) { + NET_DBG("Cannot linearize send data (%d)", ret); + return ret; + } + + if (ret != len) { + NET_DBG("Linear copy error (%u vs %d)", len, ret); + return -EINVAL; + } + + do { + ret = mbedtls_ssl_write(&ctx->https.mbedtls.ssl, + ctx->req.request_buf, len); + if (ret == MBEDTLS_ERR_NET_CONN_RESET) { + print_error("peer closed the connection -0x%x", ret); + goto out; + } + + if (ret != MBEDTLS_ERR_SSL_WANT_READ && + ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + if (ret < 0) { + print_error("mbedtls_ssl_write returned " + "-0x%x", ret); + goto out; + } + } + } while (ret <= 0); + +out: + return ret; +} + +#if defined(MBEDTLS_MEMORY_BUFFER_ALLOC_C) +static void heap_init(struct http_server_ctx *ctx) +{ + static bool heap_init; + + if (!heap_init) { + mbedtls_memory_buffer_alloc_init(heap, sizeof(heap)); + heap_init = true; + } +} +#else +#define heap_init(...) +#endif + +static void https_handler(struct http_server_ctx *ctx) +{ + size_t len; + int ret; + + NET_DBG("HTTPS handler starting"); + + mbedtls_platform_set_printf(printk); + + heap_init(ctx); + +#if defined(MBEDTLS_DEBUG_C) && defined(CONFIG_NET_DEBUG_HTTP) + mbedtls_debug_set_threshold(DEBUG_THRESHOLD); + mbedtls_ssl_conf_dbg(&ctx->https.mbedtls.conf, my_debug, NULL); +#endif + +#if defined(MBEDTLS_X509_CRT_PARSE_C) + mbedtls_x509_crt_init(&ctx->https.mbedtls.srvcert); +#endif + + mbedtls_pk_init(&ctx->https.mbedtls.pkey); + mbedtls_ssl_init(&ctx->https.mbedtls.ssl); + mbedtls_ssl_config_init(&ctx->https.mbedtls.conf); + mbedtls_entropy_init(&ctx->https.mbedtls.entropy); + mbedtls_ctr_drbg_init(&ctx->https.mbedtls.ctr_drbg); + + /* Load the certificates and private RSA key. This needs to be done + * by the user so we call a callback that user must have provided. + */ + ret = ctx->https.mbedtls.cert_cb(ctx, &ctx->https.mbedtls.srvcert, + &ctx->https.mbedtls.pkey); + if (ret != 0) { + goto exit; + } + + /* Seed the RNG */ + mbedtls_entropy_add_source(&ctx->https.mbedtls.entropy, + ctx->https.mbedtls.entropy_src_cb, + NULL, + MBEDTLS_ENTROPY_MAX_GATHER, + MBEDTLS_ENTROPY_SOURCE_STRONG); + + ret = mbedtls_ctr_drbg_seed( + &ctx->https.mbedtls.ctr_drbg, + mbedtls_entropy_func, + &ctx->https.mbedtls.entropy, + (const unsigned char *)ctx->https.mbedtls.personalization_data, + ctx->https.mbedtls.personalization_data_len); + if (ret != 0) { + print_error("mbedtls_ctr_drbg_seed returned -0x%x", ret); + goto exit; + } + + /* Setup SSL defaults etc. */ + ret = mbedtls_ssl_config_defaults(&ctx->https.mbedtls.conf, + MBEDTLS_SSL_IS_SERVER, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT); + if (ret != 0) { + print_error("mbedtls_ssl_config_defaults returned -0x%x", ret); + goto exit; + } + + mbedtls_ssl_conf_rng(&ctx->https.mbedtls.conf, + mbedtls_ctr_drbg_random, + &ctx->https.mbedtls.ctr_drbg); + +#if defined(MBEDTLS_X509_CRT_PARSE_C) + mbedtls_ssl_conf_ca_chain(&ctx->https.mbedtls.conf, + ctx->https.mbedtls.srvcert.next, + NULL); + + ret = mbedtls_ssl_conf_own_cert(&ctx->https.mbedtls.conf, + &ctx->https.mbedtls.srvcert, + &ctx->https.mbedtls.pkey); + if (ret != 0) { + print_error("mbedtls_ssl_conf_own_cert returned -0x%x", ret); + goto exit; + } +#endif /* MBEDTLS_X509_CRT_PARSE_C */ + + ret = mbedtls_ssl_setup(&ctx->https.mbedtls.ssl, + &ctx->https.mbedtls.conf); + if (ret != 0) { + print_error("mbedtls_ssl_setup returned -0x%x", ret); + goto exit; + } + +reset: + mbedtls_ssl_session_reset(&ctx->https.mbedtls.ssl); + mbedtls_ssl_set_bio(&ctx->https.mbedtls.ssl, ctx, ssl_tx, + ssl_rx, NULL); + + /* SSL handshake. The ssl_rx() function will be called next by + * mbedtls library. The ssl_rx() will block and wait that data is + * received by ssl_received() and passed to it via fifo. After + * receiving the data, this function will then proceed with secure + * connection establishment. + */ + /* Waiting SSL handshake */ + do { + ret = mbedtls_ssl_handshake(&ctx->https.mbedtls.ssl); + if (ret != MBEDTLS_ERR_SSL_WANT_READ && + ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + if (ret < 0) { + print_error("mbedtls_ssl_handshake returned " + "-0x%x", ret); + goto reset; + } + } + } while (ret != 0); + + /* Read the HTTPS Request */ + NET_DBG("Read HTTPS request"); + do { + len = ctx->req.request_buf_len - 1; + memset(ctx->req.request_buf, 0, ctx->req.request_buf_len); + + ret = mbedtls_ssl_read(&ctx->https.mbedtls.ssl, + ctx->req.request_buf, len); + if (ret == MBEDTLS_ERR_SSL_WANT_READ || + ret == MBEDTLS_ERR_SSL_WANT_WRITE) { + continue; + } + + if (ret <= 0) { + switch (ret) { + case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY: + NET_DBG("Connection was closed gracefully"); + goto close; + + case MBEDTLS_ERR_NET_CONN_RESET: + NET_DBG("Connection was reset by peer"); + break; + + default: + print_error("mbedtls_ssl_read returned " + "-0x%x", ret); + break; + } + + goto close; + } + + ret = http_parser_execute(&ctx->req.parser, + &ctx->req.settings, + ctx->req.request_buf, + ret); + } while (ret < 0); + + /* Write the Response */ + NET_DBG("Write HTTPS response"); + + if (ctx->req.parser.http_errno != HPE_OK) { + http_response_400(ctx, NULL); + } else { + http_process_recv(ctx); + } + +close: + mbedtls_ssl_close_notify(&ctx->https.mbedtls.ssl); + goto reset; + +exit: + return; +} + +static void https_enable(struct http_server_ctx *ctx) +{ + /* Start the thread that handles HTTPS traffic. */ + if (ctx->https.tid) { + return; + } + + ctx->https.tid = k_thread_create(&ctx->https.thread, + ctx->https.stack, + ctx->https.stack_size, + (k_thread_entry_t)https_handler, + ctx, NULL, NULL, + K_PRIO_COOP(7), 0, 0); +} + +static void https_disable(struct http_server_ctx *ctx) +{ + if (!ctx->https.tid) { + return; + } + + mbedtls_ssl_free(&ctx->https.mbedtls.ssl); + mbedtls_ssl_config_free(&ctx->https.mbedtls.conf); + mbedtls_ctr_drbg_free(&ctx->https.mbedtls.ctr_drbg); + mbedtls_entropy_free(&ctx->https.mbedtls.entropy); + + /* Empty the fifo just in case there is any received packets + * still there. + */ + while (1) { + struct rx_fifo_block *rx_data; + + rx_data = k_fifo_get(&ctx->https.mbedtls.ssl_ctx.rx_fifo, + K_NO_WAIT); + if (!rx_data) { + break; + } + + net_pkt_unref(rx_data->pkt); + + k_mem_pool_free(&rx_data->block); + } + + NET_DBG("HTTPS thread %p stopped", ctx->https.tid); + + k_thread_abort(ctx->https.tid); + ctx->https.tid = 0; +} + +static int https_init(struct http_server_ctx *ctx) +{ + k_sem_init(&ctx->https.mbedtls.ssl_ctx.tx_sem, 0, UINT_MAX); + k_fifo_init(&ctx->https.mbedtls.ssl_ctx.rx_fifo); + + /* Next we return to application which must then enable the HTTPS + * service. The enable function will then start the https thread and + * do what ever further configuration needed. + * + * We do the mbedtls initialization in its own thread because it uses + * of of stack and the main stack runs out of memory very easily. + * + * See https_handler() how the things proceed from now on. + */ + + return 0; +} + +int https_server_init(struct http_server_ctx *ctx, + struct http_server_urls *urls, + struct sockaddr *server_addr, + u8_t *request_buf, + size_t request_buf_len, + const char *server_banner, + u8_t *personalization_data, + size_t personalization_data_len, + https_server_cert_cb_t cert_cb, + https_entropy_src_cb_t entropy_src_cb, + struct k_mem_pool *pool, + u8_t *https_stack, + size_t https_stack_size) +{ + int ret; + + if (ctx->urls) { + NET_ERR("Server context %p already initialized", ctx); + return -EALREADY; + } + + if (!request_buf || request_buf_len == 0) { + NET_ERR("Request buf must be set"); + return -EINVAL; + } + + if (!cert_cb) { + NET_ERR("Cert callback must be set"); + return -EINVAL; + } + + ret = init_net(ctx, server_addr, HTTPS_DEFAULT_PORT); + if (ret < 0) { + return ret; + } + + if (server_banner) { + new_server(ctx, server_banner, server_addr); + } + + ctx->req.request_buf = request_buf; + ctx->req.request_buf_len = request_buf_len; + ctx->req.data_len = 0; + ctx->urls = urls; + ctx->is_https = true; + ctx->https.stack = https_stack; + ctx->https.stack_size = https_stack_size; + ctx->https.mbedtls.cert_cb = cert_cb; + ctx->https.pool = pool; + + if (entropy_src_cb) { + ctx->https.mbedtls.entropy_src_cb = entropy_src_cb; + } else { + ctx->https.mbedtls.entropy_src_cb = entropy_source; + } + + ctx->https.mbedtls.personalization_data = personalization_data; + ctx->https.mbedtls.personalization_data_len = personalization_data_len; + ctx->send_data = https_send; + ctx->recv_cb = ssl_received; + + parser_init(ctx); + + /* Then mbedtls specific initialization */ + return https_init(ctx); +} +#endif /* CONFIG_HTTPS */