From 3af4cbb4b30100942bccacfd187f770ac39637de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eivind=20J=C3=B8lsgard?= Date: Mon, 26 Aug 2024 14:28:15 +0200 Subject: [PATCH] subsys: net: download_client: restructuring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix new data req! * Remove pseudo-states Signed-off-by: Eivind Jølsgard --- .../libraries/networking/download_client.rst | 4 +- .../releases/release-notes-2.4.0.rst | 4 +- include/net/download_client.h | 249 ++++---- lib/bin/lwm2m_carrier/os/lwm2m_os.c | 4 +- samples/net/download/prj.conf | 4 +- samples/net/download/src/main.c | 17 +- .../include/download_client_internal.h | 8 +- .../lib/download_client/src/client_socket.c | 107 ++-- subsys/net/lib/download_client/src/coap.c | 8 +- .../lib/download_client/src/download_client.c | 583 +++++++++--------- subsys/net/lib/download_client/src/http.c | 37 +- subsys/net/lib/download_client/src/shell.c | 26 +- .../net/lib/fota_download/src/fota_download.c | 4 +- .../nrf_cloud/include/nrf_cloud_download.h | 2 +- .../lib/nrf_cloud/src/nrf_cloud_download.c | 2 +- .../lib/nrf_cloud/src/nrf_cloud_pgps_utils.c | 2 +- .../subsys/net/lib/download_client/src/main.c | 2 +- .../fota_download/src/test_fota_download.c | 4 +- 18 files changed, 559 insertions(+), 508 deletions(-) diff --git a/doc/nrf/libraries/networking/download_client.rst b/doc/nrf/libraries/networking/download_client.rst index 37409f1429a7..20f19481599f 100644 --- a/doc/nrf/libraries/networking/download_client.rst +++ b/doc/nrf/libraries/networking/download_client.rst @@ -48,7 +48,7 @@ Configuring HTTP and HTTPS (TLS 1.2) Set the :kconfig:option:`CONFIG_DOWNLOAD_CLIENT_BUF_SIZE` Kconfig option, so that the buffer is large enough to accommodate the entire HTTP header of the request and the response. -Moreover, the application must provision the TLS credentials and pass the security tag to the library when using HTTPS and calling the :c:func:`download_client_set_host` function. +Moreover, the application must provision the TLS credentials and pass the security tag to the library when using HTTPS and calling the :c:func:`download_client_connect` function. To provision a TLS certificate to the modem, use :c:func:`modem_key_mgmt_write` and other :ref:`modem_key_mgmt` APIs. Configuring CoAP and CoAPS (DTLS 1.2) @@ -56,7 +56,7 @@ Configuring CoAP and CoAPS (DTLS 1.2) Make sure to configure the :kconfig:option:`CONFIG_DOWNLOAD_CLIENT_BUF_SIZE` and :kconfig:option:`CONFIG_DOWNLOAD_CLIENT_COAP_BLOCK_SIZE` Kconfig options, so that the buffer is large enough to accommodate the entire CoAP header and the CoAP block. -The application must provision the TLS credentials and pass the security tag to the library when using CoAPS and calling :c:func:`download_client_set_host`. +The application must provision the TLS credentials and pass the security tag to the library when using CoAPS and calling :c:func:`download_client_connect`. When you have modem firmware v1.3.5 or newer, you can use the :kconfig:option:`CONFIG_DOWNLOAD_CLIENT_CID` Kconfig option to enable the DTLS Connection Identifier feature in this library. diff --git a/doc/nrf/releases_and_maturity/releases/release-notes-2.4.0.rst b/doc/nrf/releases_and_maturity/releases/release-notes-2.4.0.rst index 630697cd785a..acb49db74854 100644 --- a/doc/nrf/releases_and_maturity/releases/release-notes-2.4.0.rst +++ b/doc/nrf/releases_and_maturity/releases/release-notes-2.4.0.rst @@ -814,11 +814,11 @@ Libraries for networking * :ref:`lib_download_client` library: - * Added the :c:func:`download_client_get` function that combines the functionality of functions :c:func:`download_client_set_host`, :c:func:`download_client_start`, and :c:func:`download_client_disconnect`. + * Added the :c:func:`download_client_get` function that combines the functionality of functions :c:func:`download_client_connect`, :c:func:`download_client_start`, and :c:func:`download_client_close`. * Updated: - * The ``download_client_connect`` function has been refactored to :c:func:`download_client_set_host` and made it non-blocking. + * The ``download_client_connect`` function has been refactored to :c:func:`download_client_connect` and made it non-blocking. * The configuration from one security tag to a list of security tags. * The library reports error ``ERANGE`` when HTTP range is requested but not supported by server. diff --git a/include/net/download_client.h b/include/net/download_client.h index 375466cb4e1a..dddba6f904bf 100644 --- a/include/net/download_client.h +++ b/include/net/download_client.h @@ -66,7 +66,7 @@ enum download_client_evt_id { * the download client automatically closes the connection. The application should wait for * DOWNLOAD_CLIENT_EVT_CLOSED before attempting another download. * If download is stopped, and it was started using @ref download_client_start - * the application should manually disconnect (@ref download_client_disconnect) + * the application should manually disconnect (@ref download_client_stop) * to clean up the network socket and wait for DOWNLOAD_CLIENT_EVT_CLOSED before attempting * another download. */ @@ -75,6 +75,8 @@ enum download_client_evt_id { DOWNLOAD_CLIENT_EVT_DONE, /** Connection have been closed. Client is now idle, ready for next download */ DOWNLOAD_CLIENT_EVT_CLOSED, + /** Client deinitialized. Memory can be freed. */ + DOWNLOAD_CLIENT_EVT_DEINITIALIZED, }; struct download_fragment { @@ -98,9 +100,41 @@ struct download_client_evt { }; /** - * @brief Download client configuration options. + * @brief Download client asynchronous event handler. + * + * Through this callback, the application receives events, such as + * download of a fragment, download completion, or errors. + * + * If the callback returns a non-zero value, the download stops. + * To resume the download, use @ref download_client_start(). + * + * @param[in] event The event. + * + * @return Zero to continue the download, non-zero otherwise. + */ +typedef int (*download_client_callback_t)( + const struct download_client_evt *event); + +/** + * @brief */ struct download_client_cfg { + /** Event handler. */ + download_client_callback_t callback; + /** Client buffer. */ + char *buf; + /** Client buffer size. */ + size_t buf_size; +}; + +/** + * @brief Download client configuration options. + */ +struct download_client_host_cfg { + /** Server hosting the file, null-terminated. + * The host name must be kept in scope while download is going on. + */ + const char *hostname; /** TLS security tag list. * Pass NULL to disable TLS. * The list must be kept in scope while download is going on. @@ -126,126 +160,107 @@ struct download_client_cfg { size_t range_override; /** Set hostname for TLS Server Name Indication extension */ bool set_tls_hostname; - /** Response buffer. */ - char *buf; - /** Response buffer size. */ - size_t buf_size; + /** Set socket to native TLS */ + bool set_native_tls; + /** Close connection when done */ + bool close_when_done; }; -/** - * @brief Download client asynchronous event handler. - * - * Through this callback, the application receives events, such as - * download of a fragment, download completion, or errors. - * - * If the callback returns a non-zero value, the download stops. - * To resume the download, use @ref download_client_start(). - * - * @param[in] event The event. - * - * @return Zero to continue the download, non-zero otherwise. - */ -typedef int (*download_client_callback_t)( - const struct download_client_evt *event); - /** * @brief Download client state. */ enum download_client_state { DOWNLOAD_CLIENT_IDLE, DOWNLOAD_CLIENT_CONNECTING, + DOWNLOAD_CLIENT_CONNECTED, DOWNLOAD_CLIENT_DOWNLOADING, - DOWNLOAD_CLIENT_FINISHED, - DOWNLOAD_CLIENT_CLOSING + DOWNLOAD_CLIENT_DEINITIALIZING, + DOWNLOAD_CLIENT_DEINITIALIZED, }; /** * @brief Download client instance. + * + * Members are set internally by the download client. */ struct download_client { - /** Protect shared variables. */ - struct k_mutex mutex; - - /** Socket descriptor. */ - int fd; - - /** Destination address storage */ - struct sockaddr remote_addr; - - /** Buffer offset. */ - size_t buf_offset; - - /** Size of the file being downloaded, in bytes. */ - size_t file_size; - /** Download progress, number of bytes downloaded. */ - size_t progress; + /** Client configuration options. */ + struct download_client_cfg config; + /** Host configuration options. */ + struct download_client_host_cfg host_config; - /** Server hosting the file, null-terminated. - * The host name must be kept in scope while download is going on. - */ - const char *host; /** File name, null-terminated. * The file name must be kept in scope while download is going on. */ const char *file; - /** Configuration options. */ - struct download_client_cfg config; - - /** Protocol for current download. */ - int proto; - + /** Size of the file being downloaded, in bytes. */ + size_t file_size; + /** Download progress, number of bytes downloaded. */ + size_t progress; + /** Buffer offset. */ + size_t buf_offset; /** Request new data */ bool new_data_req; - struct { - /** The server has closed the connection. */ - bool connection_close; - /** Is using ranged query. */ - bool ranged; - /** Ranged progress */ - size_t ranged_progress; - /** HTTP header */ - struct { - /** Header length */ - size_t hdr_len; - /** Status code */ - unsigned long status_code; - /** Whether the HTTP header for - * the current fragment has been processed. - */ - bool has_end; - } header; - } http; - struct { - /** CoAP block context. */ - struct coap_block_context block_ctx; + /** Socket descriptor. */ + int fd; + /** Protocol for current download. */ + int proto; + /** Socket type */ + int type; + /** Port */ + uint16_t port; + /** Destination address storage */ + struct sockaddr remote_addr; + } sock; + + /** Application protocols */ + union { + struct { + /** The server has closed the connection. */ + bool connection_close; + /** Is using ranged query. */ + bool ranged; + /** Ranged progress */ + size_t ranged_progress; + /** HTTP header */ + struct { + /** Header length */ + size_t hdr_len; + /** Status code */ + unsigned long status_code; + /** Whether the HTTP header for + * the current fragment has been processed. + */ + bool has_end; + } header; + } http; - /** CoAP pending object. */ - struct coap_pending pending; - } coap; + struct { + bool initialized; + /** CoAP block context. */ + struct coap_block_context block_ctx; - /** Internal thread ID. */ - k_tid_t tid; + /** CoAP pending object. */ + struct coap_pending pending; + } coap; + }; + + /** Protect shared variables. */ + struct k_mutex mutex; + /** Download client state. */ + enum download_client_state state; /** Internal download thread. */ struct k_thread thread; + /** Internal thread ID. */ + k_tid_t tid; /** Ensure that thread is ready for download */ struct k_sem wait_for_download; /* Internal thread stack. */ K_THREAD_STACK_MEMBER(thread_stack, CONFIG_DOWNLOAD_CLIENT_STACK_SIZE); - - /** Event handler. */ - download_client_callback_t callback; - - /** Set socket to native TLS */ - bool set_native_tls; - - /** Close the socket when finished. */ - bool close_when_done; - - enum download_client_state state; }; /** @@ -259,22 +274,20 @@ struct download_client { * * @retval int Zero on success, otherwise a negative error code. */ -int download_client_init(struct download_client *client, - download_client_callback_t callback); +int download_client_init(struct download_client *const dlc, + struct download_client_cfg *config); /** - * @brief Set a target hostname. + * @brief Deinitialize the download client. + * + * This function can only be called once in each client instance as + * it removes the background thread. * * @param[in] client Client instance. - * @param[in] host Name of the host to connect to, null-terminated. - * Can include scheme and port number, defaults to - * HTTP or HTTPS if no scheme is provided. - * @param[in] config Configuration options. * - * @retval int Zero on success, a negative error code otherwise. + * @retval int Zero on success. */ -int download_client_set_host(struct download_client *client, const char *host, - const struct download_client_cfg *config); +int download_client_deinit(struct download_client *client); /** * @brief Download a file. @@ -292,8 +305,21 @@ int download_client_set_host(struct download_client *client, const char *host, * * @retval int Zero on success, a negative error code otherwise. */ -int download_client_start(struct download_client *client, const char *file, - size_t from); +int download_client_start(struct download_client *client, + const struct download_client_host_cfg *host_config, + const char *file, size_t from); + +/** + * @brief Stop file download and disconnect from server. + * + * Request client to disconnect from the server. This does not block. + * When client have been disconnected, it send @ref DOWNLOAD_CLIENT_EVT_CLOSED event. + * + * @param[in] client Client instance. + * + * @return Zero on success, a negative error code otherwise. + */ +int download_client_stop(struct download_client *client); /** * @brief Retrieve the size of the file being downloaded, in bytes. @@ -319,28 +345,12 @@ int download_client_file_size_get(struct download_client *client, size_t *size); */ int download_client_downloaded_size_get(struct download_client *client, size_t *size); -/** - * @brief Initiate disconnection. - * - * Request client to disconnect from the server. This does not block. - * When client have been disconnected, it send @ref DOWNLOAD_CLIENT_EVT_CLOSED event. - * - * Request client to disconnect from the server. This does not block. - * When client has been disconnected, it sends @ref DOWNLOAD_CLIENT_EVT_CLOSED event. - * - * @param[in] client Client instance. - * - * @return Zero on success, a negative error code otherwise. - */ -int download_client_disconnect(struct download_client *client); - /** * @brief Download a file asynchronously. * * This initiates an asynchronous connect-download-disconnect sequence to the target * host. When only one file is required from a target server, it can be used instead of - * separate calls to download_client_set_host(), download_client_start() - * and download_client_disconnect(). + * separate calls to download_client_start() and download_client_stop(). * * Downloads are handled one at a time. If previous download is not finished * this returns -EALREADY. @@ -362,8 +372,9 @@ int download_client_disconnect(struct download_client *client); * * @retval int Zero on success, a negative error code otherwise. */ -int download_client_get(struct download_client *client, const char *host, - const struct download_client_cfg *config, const char *file, size_t from); +int download_client_get(struct download_client *client, + const struct download_client_host_cfg *config, + const char *file, size_t from); #ifdef __cplusplus } diff --git a/lib/bin/lwm2m_carrier/os/lwm2m_os.c b/lib/bin/lwm2m_carrier/os/lwm2m_os.c index 4e8f85c0a162..1a1caa164700 100644 --- a/lib/bin/lwm2m_carrier/os/lwm2m_os.c +++ b/lib/bin/lwm2m_carrier/os/lwm2m_os.c @@ -432,7 +432,7 @@ static lwm2m_os_download_callback_t lwm2m_os_lib_callback; int lwm2m_os_download_get(const char *host, const struct lwm2m_os_download_cfg *cfg, size_t from) { - struct download_client_cfg config = { + struct download_client_host_cfg config = { .sec_tag_list = cfg->sec_tag_list, .sec_tag_count = cfg->sec_tag_count, .pdn_id = cfg->pdn_id, @@ -449,7 +449,7 @@ int lwm2m_os_download_get(const char *host, const struct lwm2m_os_download_cfg * int lwm2m_os_download_disconnect(void) { - return download_client_disconnect(&http_downloader); + return download_client_close(&http_downloader); } static void download_client_evt_translate(const struct download_client_evt *event, diff --git a/samples/net/download/prj.conf b/samples/net/download/prj.conf index f583a0027c17..d832866c90ca 100644 --- a/samples/net/download/prj.conf +++ b/samples/net/download/prj.conf @@ -17,6 +17,6 @@ CONFIG_NET_IPV4=y CONFIG_HEAP_MEM_POOL_SIZE=1024 CONFIG_LOG=y -CONFIG_DOWNLOAD_CLIENT_LOG_LEVEL_DBG=y +CONFIG_DOWNLOAD_CLIENT_LOG_LEVEL_ERR=y -CONFIG_LOG_MODE_IMMEDIATE=y +#CONFIG_LOG_MODE_IMMEDIATE=y diff --git a/samples/net/download/src/main.c b/samples/net/download/src/main.c index 198938bfc8a3..e56be38cc034 100644 --- a/samples/net/download/src/main.c +++ b/samples/net/download/src/main.c @@ -51,15 +51,21 @@ BUILD_ASSERT(sizeof(cert) < KB(4), "Certificate too large"); static char dlc_buf[2048]; +static int callback(const struct download_client_evt *event); + static struct download_client downloader; static struct download_client_cfg config = { + .callback = callback, + .buf = dlc_buf, + .buf_size = sizeof(dlc_buf), +}; +static struct download_client_host_cfg host_config = { + .hostname = URL, #if CONFIG_SAMPLE_SECURE_SOCKET .sec_tag_list = sec_tag_list, .sec_tag_count = ARRAY_SIZE(sec_tag_list), .set_tls_hostname = true, #endif - .buf = dlc_buf, - .buf_size = sizeof(dlc_buf), .range_override = 0, }; @@ -252,6 +258,9 @@ static int callback(const struct download_client_evt *event) case DOWNLOAD_CLIENT_EVT_CLOSED: printk("Socket closed\n"); break; + case DOWNLOAD_CLIENT_EVT_DEINITIALIZED: + printk("Client deinitialized\n"); + break; } return 0; @@ -307,7 +316,7 @@ int main(void) printk("Network connected\n"); - err = download_client_init(&downloader, callback); + err = download_client_init(&downloader, &config); if (err) { printk("Failed to initialize the client, err %d", err); return 0; @@ -320,7 +329,7 @@ int main(void) ref_time = k_uptime_get(); - err = download_client_get(&downloader, URL, &config, URL, STARTING_OFFSET); + err = download_client_get(&downloader, &host_config, URL, STARTING_OFFSET); if (err) { printk("Failed to start the downloader, err %d", err); return 0; diff --git a/subsys/net/lib/download_client/include/download_client_internal.h b/subsys/net/lib/download_client/include/download_client_internal.h index 26fbbef106a7..a0e1c55c7363 100644 --- a/subsys/net/lib/download_client/include/download_client_internal.h +++ b/subsys/net/lib/download_client/include/download_client_internal.h @@ -12,6 +12,8 @@ #include +bool use_http(struct download_client *dlc); + int url_parse_port(const char *url, uint16_t *port); int url_parse_proto(const char *url, int *proto, int *type); int url_parse_host(const char *url, char *host, size_t len); @@ -19,13 +21,17 @@ int url_parse_file(const char *url, char *file, size_t len); int http_parse(struct download_client *client, size_t len); int http_get_request_send(struct download_client *client); +#if CONFIG_COAP +bool use_coap(struct download_client *dlc); int coap_block_init(struct download_client *client, size_t from); int coap_get_recv_timeout(struct download_client *dl); int coap_initiate_retransmission(struct download_client *dl); int coap_parse(struct download_client *client, size_t len); int coap_request_send(struct download_client *client); +#endif -int client_socket_configure_and_connect(struct download_client *dl, int type, uint16_t port); +int client_socket_configure_and_connect(struct download_client *dl); +int client_socket_close(struct download_client *dlc); int client_socket_send(const struct download_client *client, size_t len, int timeout); ssize_t client_socket_recv(struct download_client *dl); diff --git a/subsys/net/lib/download_client/src/client_socket.c b/subsys/net/lib/download_client/src/client_socket.c index 4ee1b39bffb2..745dae9043ec 100644 --- a/subsys/net/lib/download_client/src/client_socket.c +++ b/subsys/net/lib/download_client/src/client_socket.c @@ -191,35 +191,38 @@ static int host_lookup(const char *host, int family, uint8_t pdn_id, return 0; } -int client_socket_configure_and_connect(struct download_client *dlc, int type, uint16_t port) +int client_socket_configure_and_connect(struct download_client *dlc) { int err; socklen_t addrlen; err = -ENOTSUP; /* Attempt IPv6 connection if configured, fallback to IPv4 on error */ - if ((dlc->config.family == AF_UNSPEC) || (dlc->config.family == AF_INET6)) { - err = host_lookup(dlc->host, AF_INET6, dlc->config.pdn_id, &dlc->remote_addr); + if ((dlc->host_config.family == AF_UNSPEC) || (dlc->host_config.family == AF_INET6)) { + err = host_lookup(dlc->host_config.hostname, AF_INET6, + dlc->host_config.pdn_id, &dlc->sock.remote_addr); /* err is checked later */ } - if (((dlc->config.family == AF_UNSPEC) && err) || (dlc->config.family == AF_INET)) { - err = host_lookup(dlc->host, AF_INET, dlc->config.pdn_id, &dlc->remote_addr); + if (((dlc->host_config.family == AF_UNSPEC) && err) || + (dlc->host_config.family == AF_INET)) { + err = host_lookup(dlc->host_config.hostname, AF_INET, + dlc->host_config.pdn_id, &dlc->sock.remote_addr); /* err is checked later */ } if (err) { - LOG_ERR("DNS lookup failed %s", dlc->host); + LOG_ERR("DNS lookup failed %s", dlc->host_config.hostname); return err; } - switch (dlc->remote_addr.sa_family) { + switch (dlc->sock.remote_addr.sa_family) { case AF_INET6: - SIN6(&dlc->remote_addr)->sin6_port = htons(port); + SIN6(&dlc->sock.remote_addr)->sin6_port = htons(dlc->sock.port); addrlen = sizeof(struct sockaddr_in6); break; case AF_INET: - SIN(&dlc->remote_addr)->sin_port = htons(port); + SIN(&dlc->sock.remote_addr)->sin_port = htons(dlc->sock.port); addrlen = sizeof(struct sockaddr_in); break; default: @@ -228,42 +231,43 @@ int client_socket_configure_and_connect(struct download_client *dlc, int type, u } LOG_DBG("family: %d, type: %d, proto: %d", - dlc->remote_addr.sa_family, type, dlc->proto); + dlc->sock.remote_addr.sa_family, dlc->sock.type, dlc->sock.proto); - dlc->fd = socket(dlc->remote_addr.sa_family, type, dlc->proto); - if (dlc->fd < 0) { + dlc->sock.fd = socket(dlc->sock.remote_addr.sa_family, dlc->sock.type, dlc->sock.proto); + if (dlc->sock.fd < 0) { err = -errno; LOG_ERR("Failed to create socket, errno %d", -err); goto cleanup; } - if (dlc->config.pdn_id) { - err = socket_pdn_id_set(dlc->fd, dlc->config.pdn_id); + if (dlc->host_config.pdn_id) { + err = socket_pdn_id_set(dlc->sock.fd, dlc->host_config.pdn_id); if (err) { goto cleanup; } } - if ((dlc->proto == IPPROTO_TLS_1_2 || dlc->proto == IPPROTO_DTLS_1_2) && - (dlc->config.sec_tag_list != NULL) && (dlc->config.sec_tag_count > 0)) { - err = socket_sectag_set(dlc->fd, dlc->config.sec_tag_list, dlc->config.sec_tag_count); + if ((dlc->sock.proto == IPPROTO_TLS_1_2 || dlc->sock.proto == IPPROTO_DTLS_1_2) && + (dlc->host_config.sec_tag_list != NULL) && (dlc->host_config.sec_tag_count > 0)) { + err = socket_sectag_set(dlc->sock.fd, dlc->host_config.sec_tag_list, + dlc->host_config.sec_tag_count); if (err) { goto cleanup; } - if (dlc->config.set_tls_hostname) { - err = socket_tls_hostname_set(dlc->fd, dlc->host); + if (dlc->host_config.set_tls_hostname) { + err = socket_tls_hostname_set(dlc->sock.fd, dlc->host_config.hostname); if (err) { err = -errno; goto cleanup; } } - if (dlc->proto == IPPROTO_DTLS_1_2 && IS_ENABLED(CONFIG_DOWNLOAD_CLIENT_CID)) { + if (dlc->sock.proto == IPPROTO_DTLS_1_2 && IS_ENABLED(CONFIG_DOWNLOAD_CLIENT_CID)) { /* Enable connection ID */ uint32_t dtls_cid = TLS_DTLS_CID_ENABLED; - err = setsockopt(dlc->fd, SOL_TLS, TLS_DTLS_CID, &dtls_cid, + err = setsockopt(dlc->sock.fd, SOL_TLS, TLS_DTLS_CID, &dtls_cid, sizeof(dtls_cid)); if (err) { err = -errno; @@ -277,18 +281,19 @@ int client_socket_configure_and_connect(struct download_client *dlc, int type, u char ip_addr_str[NET_IPV6_ADDR_LEN]; void *sin_addr; - if (dlc->remote_addr.sa_family == AF_INET6) { - sin_addr = &((struct sockaddr_in6 *)&dlc->remote_addr)->sin6_addr; + if (dlc->sock.remote_addr.sa_family == AF_INET6) { + sin_addr = &((struct sockaddr_in6 *)&dlc->sock.remote_addr)->sin6_addr; } else { - sin_addr = &((struct sockaddr_in *)&dlc->remote_addr)->sin_addr; + sin_addr = &((struct sockaddr_in *)&dlc->sock.remote_addr)->sin_addr; } - inet_ntop(dlc->remote_addr.sa_family, sin_addr, ip_addr_str, sizeof(ip_addr_str)); + inet_ntop(dlc->sock.remote_addr.sa_family, sin_addr, + ip_addr_str, sizeof(ip_addr_str)); LOG_INF("Connecting to %s", ip_addr_str); } LOG_DBG("fd %d, addrlen %d, fam %s, port %d", - dlc->fd, addrlen, str_family(dlc->remote_addr.sa_family), port); + dlc->sock.fd, addrlen, str_family(dlc->sock.remote_addr.sa_family), dlc->sock.port); - err = connect(dlc->fd, &dlc->remote_addr, addrlen); + err = connect(dlc->sock.fd, &dlc->sock.remote_addr, addrlen); if (err) { err = -errno; LOG_ERR("Unable to connect, errno %d", -err); @@ -302,28 +307,45 @@ int client_socket_configure_and_connect(struct download_client *dlc, int type, u cleanup: if (err) { - if (dlc->fd != -1) { - close(dlc->fd); - dlc->fd = -1; + if (dlc->sock.fd != -1) { + close(dlc->sock.fd); + dlc->sock.fd = -1; } } return err; } +int client_socket_close(struct download_client *dlc) +{ + int err = 0; + + if (dlc->sock.fd != -1) { + err = close(dlc->sock.fd); + if (err && errno != EBADF) { + err = -errno; + LOG_ERR("Failed to close socket, errno %d", -err); + } + } + + dlc->sock.fd = -1; + + return err; +} + int client_socket_send(const struct download_client *dlc, size_t len, int timeout) { int err; int sent; size_t off = 0; - err = socket_send_timeout_set(dlc->fd, timeout); + err = socket_send_timeout_set(dlc->sock.fd, timeout); if (err) { return -errno; } while (len) { - sent = send(dlc->fd, dlc->config.buf + off, len, 0); + sent = send(dlc->sock.fd, dlc->config.buf + off, len, 0); if (sent < 0) { return -errno; } @@ -339,31 +361,26 @@ ssize_t client_socket_recv(struct download_client *dlc) { int err, timeout = 0; - switch (dlc->proto) { - case IPPROTO_TCP: - case IPPROTO_TLS_1_2: + if (use_http(dlc)) { timeout = CONFIG_DOWNLOAD_CLIENT_TCP_SOCK_TIMEO_MS; - break; - case IPPROTO_UDP: - case IPPROTO_DTLS_1_2: - if (IS_ENABLED(CONFIG_COAP)) { - timeout = coap_get_recv_timeout(dlc); +#if CONFIG_COAP + } else if (use_coap(dlc)) { + timeout = coap_get_recv_timeout(dlc); if (timeout == 0) { errno = ETIMEDOUT; return -1; } - break; - } - default: +#endif + } else { LOG_ERR("unhandled proto"); return -1; } - err = socket_recv_timeout_set(dlc->fd, timeout); + err = socket_recv_timeout_set(dlc->sock.fd, timeout); if (err) { return -1; } - return recv(dlc->fd, dlc->config.buf + dlc->buf_offset, + return recv(dlc->sock.fd, dlc->config.buf + dlc->buf_offset, dlc->config.buf_size - dlc->buf_offset, 0); } diff --git a/subsys/net/lib/download_client/src/coap.c b/subsys/net/lib/download_client/src/coap.c index 3327cdcdee92..128ecdbccd88 100644 --- a/subsys/net/lib/download_client/src/coap.c +++ b/subsys/net/lib/download_client/src/coap.c @@ -44,10 +44,16 @@ static bool has_pending(struct download_client *dlc) int coap_block_init(struct download_client *dlc, size_t from) { + if (dlc->coap.initialized) { + return 0; + } + coap_block_transfer_init(&dlc->coap.block_ctx, CONFIG_DOWNLOAD_CLIENT_COAP_BLOCK_SIZE, 0); dlc->coap.block_ctx.current = from; coap_pending_clear(&dlc->coap.pending); + + dlc->coap.initialized = true; return 0; } @@ -262,7 +268,7 @@ int coap_request_send(struct download_client *dlc) params.max_retransmission = CONFIG_DOWNLOAD_CLIENT_COAP_MAX_RETRANSMIT_REQUEST_COUNT; - err = coap_pending_init(&dlc->coap.pending, &request, &dlc->remote_addr, + err = coap_pending_init(&dlc->coap.pending, &request, &dlc->sock.remote_addr, ¶ms); if (err < 0) { return -EINVAL; diff --git a/subsys/net/lib/download_client/src/download_client.c b/subsys/net/lib/download_client/src/download_client.c index 0d8bfb1277c1..13bdd1b97d35 100644 --- a/subsys/net/lib/download_client/src/download_client.c +++ b/subsys/net/lib/download_client/src/download_client.c @@ -28,13 +28,18 @@ LOG_MODULE_REGISTER(download_client, CONFIG_DOWNLOAD_CLIENT_LOG_LEVEL); #define HOSTNAME_SIZE CONFIG_DOWNLOAD_CLIENT_MAX_HOSTNAME_SIZE -static int handle_disconnect(struct download_client *dlc); +static int client_close(struct download_client *dlc); static int error_evt_send(const struct download_client *dlc, int error); -static bool use_coap(struct download_client *dlc) +#if CONFIG_COAP +bool use_coap(struct download_client *dlc) { - return (IS_ENABLED(CONFIG_COAP) && - (dlc->proto == IPPROTO_UDP || dlc->proto == IPPROTO_DTLS_1_2)); + return (dlc->sock.proto == IPPROTO_UDP || dlc->sock.proto == IPPROTO_DTLS_1_2); +} +#endif + +bool use_http(struct download_client *dlc) { + return (dlc->sock.proto == IPPROTO_TCP || dlc->sock.proto == IPPROTO_TLS_1_2); } static bool is_state(struct download_client *dlc, enum download_client_state state) @@ -47,113 +52,103 @@ static bool is_state(struct download_client *dlc, enum download_client_state sta return ret; } -static void set_state(struct download_client *dlc, int state) +#if CONFIG_DOWNLOAD_CLIENT_LOG_LEVEL_DBG +char *state_to_str(int state) +{ + switch (state) { + case DOWNLOAD_CLIENT_IDLE: + return "IDLE"; + case DOWNLOAD_CLIENT_CONNECTING: + return "CONNECTING"; + case DOWNLOAD_CLIENT_CONNECTED: + return "CONNECTED"; + case DOWNLOAD_CLIENT_DOWNLOADING: + return "DOWNLOADING"; + case DOWNLOAD_CLIENT_DEINITIALIZING: + return "DEINITIALIZING"; + case DOWNLOAD_CLIENT_DEINITIALIZED: + return "DEINITIALIZED"; + } + + return "unknown"; +} +#else +char *state_to_str(int state) { + return ""; +} +#endif + +static void state_set(struct download_client *dlc, int state) { k_mutex_lock(&dlc->mutex, K_FOREVER); - /* Prevent moving back to CLOSING from IDLE */ - if ((state != DOWNLOAD_CLIENT_CLOSING) || (dlc->state != DOWNLOAD_CLIENT_IDLE)) { - dlc->state = state; - } + dlc->state = state; k_mutex_unlock(&dlc->mutex); - LOG_DBG("state = %d", state); + LOG_DBG("state = %d (%s)", state, state_to_str(state)); } static int client_connect(struct download_client *dlc) { int err; int ns_err; - int type; - uint16_t port; - err = url_parse_proto(dlc->host, &dlc->proto, &type); - if (err == -EINVAL) { - LOG_DBG("Protocol not specified, defaulting to HTTP(S)"); - type = SOCK_STREAM; - if (dlc->config.sec_tag_list && (dlc->config.sec_tag_count > 0)) { - dlc->proto = IPPROTO_TLS_1_2; - } else { - dlc->proto = IPPROTO_TCP; - } - } else if (err) { - goto cleanup; - } + err = -1; + ns_err = -1; - if ((dlc->proto == IPPROTO_UDP || dlc->proto == IPPROTO_DTLS_1_2) && - (!IS_ENABLED(CONFIG_COAP))) { - err = -EPROTONOSUPPORT; + err = client_socket_configure_and_connect(dlc); + if (err) { goto cleanup; } - if (dlc->proto == IPPROTO_TLS_1_2 || dlc->proto == IPPROTO_DTLS_1_2) { - if (dlc->config.sec_tag_list == NULL || dlc->config.sec_tag_count == 0) { - LOG_WRN("No security tag provided for TLS/DTLS"); - err = -EINVAL; - goto cleanup; - } - } - - if ((dlc->config.sec_tag_list == NULL || dlc->config.sec_tag_count == 0) && - dlc->config.set_tls_hostname) { - LOG_WRN("set_tls_hostname flag is set for non-TLS connection"); - err = -EINVAL; - goto cleanup; +#if CONFIG_COAP + /* Initialize CoAP */ + if (use_coap(dlc)) { + coap_block_init(dlc, dlc->progress); } +#endif - err = url_parse_port(dlc->host, &port); +cleanup: if (err) { - switch (dlc->proto) { - case IPPROTO_TLS_1_2: - port = 443; - break; - case IPPROTO_TCP: - port = 80; - break; - case IPPROTO_DTLS_1_2: - port = 5684; - break; - case IPPROTO_UDP: - port = 5683; - break; - } - LOG_DBG("Port not specified, using default: %d", port); + /* Unable to connect, close socket */ + client_close(dlc); + state_set(dlc, DOWNLOAD_CLIENT_IDLE); } - if (dlc->set_native_tls) { - LOG_DBG("Enabled native TLS"); - type |= SOCK_NATIVE_TLS; - } + return err; +} - err = -1; - ns_err = -1; +static int client_close(struct download_client *dlc) +{ + int err; + const struct download_client_evt evt = { + .id = DOWNLOAD_CLIENT_EVT_CLOSED, + }; - err = client_socket_configure_and_connect(dlc, type, port); + k_mutex_lock(&dlc->mutex, K_FOREVER); -cleanup: - if (err) { - error_evt_send(dlc, -err); + err = client_socket_close(dlc); - /* Unable to connect, close socket */ - handle_disconnect(dlc); - } + dlc->host_config.hostname = NULL; + dlc->file = NULL; + + dlc->config.callback(&evt); + + k_mutex_unlock(&dlc->mutex); return err; } static int request_send(struct download_client *dlc) { - if (dlc->fd < 0) { + if (dlc->sock.fd < 0) { return -1; } - switch (dlc->proto) { - case IPPROTO_TCP: - case IPPROTO_TLS_1_2: + if (use_http(dlc)) { return http_get_request_send(dlc); - case IPPROTO_UDP: - case IPPROTO_DTLS_1_2: - if (IS_ENABLED(CONFIG_COAP)) { - return coap_request_send(dlc); - } +#if CONFIG_COAP + } else if (use_coap(dlc)) { + return coap_request_send(dlc); +#endif } return 0; @@ -174,58 +169,66 @@ static int fragment_evt_send(struct download_client *dlc) dlc->buf_offset = 0; - return dlc->callback(&evt); + return dlc->config.callback(&evt); } static int error_evt_send(const struct download_client *dlc, int error) { /* Error will be sent as negative. */ - __ASSERT_NO_MSG(error > 0); + __ASSERT_NO_MSG(error < 0); const struct download_client_evt evt = { .id = DOWNLOAD_CLIENT_EVT_ERROR, - .error = -error + .error = error + }; + + return dlc->config.callback(&evt); +} + +static int deinit_evt_send(const struct download_client *dlc) +{ + const struct download_client_evt evt = { + .id = DOWNLOAD_CLIENT_EVT_DEINITIALIZED, }; - return dlc->callback(&evt); + return dlc->config.callback(&evt); } static int reconnect(struct download_client *dlc) { - int err; + int err = 0; LOG_INF("Reconnecting..."); - if (dlc->fd >= 0) { - err = close(dlc->fd); + + if (dlc->sock.fd >= 0) { + err = close(dlc->sock.fd); if (err) { LOG_DBG("disconnect failed, %d", err); } - dlc->fd = -1; + dlc->sock.fd = -1; } - err = client_connect(dlc); + + k_mutex_lock(&dlc->mutex, K_FOREVER); + state_set(dlc, DOWNLOAD_CLIENT_CONNECTING); + k_mutex_unlock(&dlc->mutex); + return err; } static int request_resend(struct download_client *dlc) { +#if CONFIG_COAP int rc; - switch (dlc->proto) { - case IPPROTO_UDP: - case IPPROTO_DTLS_1_2: - if (IS_ENABLED(CONFIG_COAP)) { - rc = coap_initiate_retransmission(dlc); + if (use_coap(dlc)) { + rc = coap_initiate_retransmission(dlc); if (rc) { - error_evt_send(dlc, ETIMEDOUT); + error_evt_send(dlc, -ETIMEDOUT); return -1; } - } - break; - case IPPROTO_TCP: - case IPPROTO_TLS_1_2: - break; } +#endif return 0; } @@ -240,7 +243,7 @@ static int client_revc_error_handle(struct download_client *dlc, ssize_t len) { int rc; - if (dlc->fd == -1) { + if (dlc->sock.fd == -1) { /* download was aborted */ return -1; } @@ -258,7 +261,7 @@ static int client_revc_error_handle(struct download_client *dlc, ssize_t len) } } - rc = ECONNRESET; + rc = -ECONNRESET; if (len == -1) { if ((errno == ETIMEDOUT) || @@ -269,10 +272,8 @@ static int client_revc_error_handle(struct download_client *dlc, ssize_t len) k_mutex_unlock(&dlc->mutex); if (rc == -1) { return -1; - } else if (rc == 1) { - return 0; } - rc = ETIMEDOUT; + rc = -ETIMEDOUT; } LOG_ERR("Error in recv(), errno %d", errno); } @@ -291,9 +292,7 @@ static int client_revc_error_handle(struct download_client *dlc, ssize_t len) return -1; } - k_mutex_lock(&dlc->mutex, K_FOREVER); rc = reconnect(dlc); - k_mutex_unlock(&dlc->mutex); if (rc) { return -1; } @@ -312,162 +311,111 @@ static int client_revc_handle(struct download_client *dlc, ssize_t len) LOG_DBG("Read %d bytes from socket", len); - if (dlc->proto == IPPROTO_TCP || dlc->proto == IPPROTO_TLS_1_2) { + if (use_http(dlc)) { rc = http_parse(dlc, len); - if (rc < 0) { - error_evt_send(dlc, -rc); - return -1; + if (rc) { + return rc; } if (dlc->http.header.has_end && dlc->buf_offset) { - if (fragment_evt_send(dlc)) { + rc = fragment_evt_send(dlc); + if (rc) { /* Restart and suspend */ - LOG_INF("Fragment refused, download stopped."); - rc = -1; + LOG_INF("Fragment refused (%d), download stopped.", rc); + return rc; } } - - if (!dlc->new_data_req) { - /* Wait for more data */ - return 0; - } - } else if (IS_ENABLED(CONFIG_COAP)) { +#if CONFIG_COAP + } else if (use_coap(dlc)) { rc = coap_parse(dlc, (size_t)len); - if (rc == 1) { - /* Duplicate packet received */ - return 1; + if (rc < 0) { + return rc; } +#endif } else { - /* Unknown protocol */ - rc = -EBADMSG; - } - - if (rc < 0) { - /* Something was wrong with the packet - * Restart and suspend - */ - error_evt_send(dlc, -rc); - return -1; - } - - if (dlc->file_size) { - LOG_INF("Downloaded %u/%u bytes (%d%%)", dlc->progress, dlc->file_size, - (dlc->progress * 100) / dlc->file_size); - } else { - LOG_INF("Downloaded %u bytes", dlc->progress); - } - - if (dlc->progress == dlc->file_size) { - LOG_INF("Download complete"); - const struct download_client_evt evt = { - .id = DOWNLOAD_CLIENT_EVT_DONE, - }; - dlc->callback(&evt); - /* Restart and suspend */ - rc = -1; - } else if ((dlc->proto == IPPROTO_TCP || dlc->proto == IPPROTO_TLS_1_2)) { - /* Request a next range */ - dlc->new_data_req = true; - rc = 0; + return -EBADMSG; } - return rc; + return 0; } -static int handle_disconnect(struct download_client *dlc) -{ - int err = 0; - - k_mutex_lock(&dlc->mutex, K_FOREVER); - - if (dlc->fd != -1) { - err = close(dlc->fd); - if (err) { - err = -errno; - LOG_ERR("Failed to close socket, errno %d", -err); - } +static void restart_and_suspend(struct download_client *dlc) { + if (dlc->host_config.close_when_done) { + client_close(dlc); + state_set(dlc, DOWNLOAD_CLIENT_IDLE); + return; } - dlc->fd = -1; - dlc->close_when_done = false; - dlc->host = NULL; - dlc->file = NULL; - set_state(dlc, DOWNLOAD_CLIENT_IDLE); - const struct download_client_evt evt = { - .id = DOWNLOAD_CLIENT_EVT_CLOSED, - }; - dlc->callback(&evt); - - k_mutex_unlock(&dlc->mutex); - - return err; + state_set(dlc, DOWNLOAD_CLIENT_CONNECTED); } + void download_thread(void *cli, void *a, void *b) { int rc; ssize_t len; struct download_client *const dlc = cli; - dlc->new_data_req = false; while (true) { rc = 0; - /* Wait for action */ - k_sem_take(&dlc->wait_for_download, K_FOREVER); + if (is_state(dlc, DOWNLOAD_CLIENT_IDLE)) { + /* Client idle, wait for action */ + k_sem_take(&dlc->wait_for_download, K_FOREVER); + } /* Connect to the target host */ if (is_state(dlc, DOWNLOAD_CLIENT_CONNECTING)) { + /* Client */ rc = client_connect(dlc); - if (rc) { + error_evt_send(dlc, rc); + /* Try connecting again */ continue; } - /* Initialize CoAP */ - if (use_coap(dlc)) { - coap_block_init(dlc, dlc->progress); + /* Connection successful */ + k_mutex_lock(&dlc->mutex, K_FOREVER); + if (dlc->file) { + dlc->new_data_req = true; + state_set(dlc, DOWNLOAD_CLIENT_DOWNLOADING); + k_mutex_unlock(&dlc->mutex); + continue; } - len = 0; - dlc->new_data_req = true; + state_set(dlc, DOWNLOAD_CLIENT_CONNECTED); + k_mutex_unlock(&dlc->mutex); + } - set_state(dlc, DOWNLOAD_CLIENT_DOWNLOADING); + if (is_state(dlc, DOWNLOAD_CLIENT_CONNECTED)) { + /* Client connected, wait for action */ + k_sem_take(&dlc->wait_for_download, K_FOREVER); } /* Request loop */ - while (is_state(dlc, DOWNLOAD_CLIENT_DOWNLOADING)) { + if (is_state(dlc, DOWNLOAD_CLIENT_DOWNLOADING)) { if (dlc->new_data_req) { /* Request next fragment */ dlc->buf_offset = 0; rc = request_send(dlc); - dlc->new_data_req = false; if (rc) { - rc = error_evt_send(dlc, ECONNRESET); + rc = error_evt_send(dlc, -ECONNRESET); if (rc) { - /* Restart and suspend */ - break; + restart_and_suspend(dlc); } rc = reconnect(dlc); if (rc) { - break; + restart_and_suspend(dlc); } - /* Send request again */ - dlc->new_data_req = true; continue; } + + dlc->new_data_req = false; } __ASSERT(dlc->buf_offset < dlc->config.buf_size, "Buffer overflow"); - if (dlc->config.buf_size - dlc->buf_offset == 0) { - LOG_ERR("Could not fit HTTP header from server (> %d)", - dlc->config.buf_size); - error_evt_send(dlc, E2BIG); - break; - } - LOG_DBG("Receiving up to %d bytes at %p...", (dlc->config.buf_size - dlc->buf_offset), (void *)(dlc->config.buf + dlc->buf_offset)); @@ -476,60 +424,65 @@ void download_thread(void *cli, void *a, void *b) if (len <= 0) { rc = client_revc_error_handle(dlc, len); if (rc < 0) { - break; + restart_and_suspend(dlc); + } + } else { + /* Accumulate buffer offset */ + dlc->buf_offset += len; + rc = client_revc_handle(dlc, len); + if (rc < 0) { + error_evt_send(dlc, rc); + restart_and_suspend(dlc); + } + + if (dlc->file_size) { + LOG_INF("Downloaded %u/%u bytes (%d%%)", + dlc->progress, dlc->file_size, + (dlc->progress * 100) / dlc->file_size); + } else { + LOG_INF("Downloaded %u bytes", dlc->progress); } - } - /* Accumulate buffer offset */ - dlc->buf_offset += len; - rc = client_revc_handle(dlc, len); - if (rc < 0) { - break; + if (dlc->progress == dlc->file_size) { + LOG_INF("Download complete"); + const struct download_client_evt evt = { + .id = DOWNLOAD_CLIENT_EVT_DONE, + }; + dlc->config.callback(&evt); + restart_and_suspend(dlc); + } } /* Attempt to reconnect if the connection was closed */ if (dlc->http.connection_close) { dlc->http.connection_close = false; - k_mutex_lock(&dlc->mutex, K_FOREVER); rc = reconnect(dlc); - k_mutex_unlock(&dlc->mutex); if (rc) { - error_evt_send(dlc, EHOSTDOWN); - break; + error_evt_send(dlc, -EHOSTDOWN); } - - /* Send request again */ - dlc->new_data_req = true; } } - if (is_state(dlc, DOWNLOAD_CLIENT_DOWNLOADING)) { - if (dlc->close_when_done) { - set_state(dlc, DOWNLOAD_CLIENT_CLOSING); - } else { - set_state(dlc, DOWNLOAD_CLIENT_FINISHED); - } + if (is_state(dlc, DOWNLOAD_CLIENT_DEINITIALIZING)) { + client_close(dlc); + state_set(dlc, DOWNLOAD_CLIENT_DEINITIALIZED); + deinit_evt_send(dlc); + return; } - - if (is_state(dlc, DOWNLOAD_CLIENT_CLOSING)) { - handle_disconnect(dlc); - LOG_DBG("Connection closed"); - } - - /* Do not let the thread return, since it can't be restarted */ } } int download_client_init(struct download_client *const dlc, - download_client_callback_t callback) + struct download_client_cfg *config) { - if (dlc == NULL || callback == NULL) { + if (dlc == NULL || config == NULL || config->callback == NULL || + config->buf == NULL || config->buf_size == 0) { return -EINVAL; } memset(dlc, 0, sizeof(*dlc)); - dlc->fd = -1; - dlc->callback = callback; + dlc->sock.fd = -1; + dlc->config = *config; k_sem_init(&dlc->wait_for_download, 0, 1); k_mutex_init(&dlc->mutex); @@ -551,89 +504,143 @@ int download_client_init(struct download_client *const dlc, return 0; } -int download_client_set_host(struct download_client *dlc, const char *host, - const struct download_client_cfg *config) +int download_client_deinit(struct download_client *const dlc) { - if (dlc == NULL || host == NULL || config == NULL) { + if (dlc == NULL) { return -EINVAL; } - if (config->range_override > config->buf_size) { //TODO how does this fit with header? - LOG_ERR("The configured fragment size is larger than buffer"); - return -E2BIG; - } - + state_set(dlc, DOWNLOAD_CLIENT_DEINITIALIZING); - k_mutex_lock(&dlc->mutex, K_FOREVER); - - if (!is_state(dlc, DOWNLOAD_CLIENT_IDLE)) { - k_mutex_unlock(&dlc->mutex); - return -EALREADY; - } - - dlc->config = *config; - dlc->host = host; - dlc->close_when_done = false; - k_mutex_unlock(&dlc->mutex); return 0; } -int download_client_disconnect(struct download_client *const dlc) +int download_client_start(struct download_client *dlc, + const struct download_client_host_cfg *host_config, + const char *file, size_t from) { - if (dlc == NULL || is_state(dlc, DOWNLOAD_CLIENT_IDLE)) { - return -EINVAL; - } - - set_state(dlc, DOWNLOAD_CLIENT_CLOSING); - k_sem_give(&dlc->wait_for_download); - - return 0; -} + int err; + bool host_connected; -int download_client_start(struct download_client *dlc, const char *file, - size_t from) -{ - if (dlc == NULL) { + if (dlc == NULL || host_config == NULL || host_config->hostname == NULL) { return -EINVAL; } k_mutex_lock(&dlc->mutex, K_FOREVER); - if (dlc->host == NULL) { + if (!is_state(dlc, DOWNLOAD_CLIENT_IDLE) && + !is_state(dlc, DOWNLOAD_CLIENT_CONNECTED)) { k_mutex_unlock(&dlc->mutex); - return -EINVAL; + return -EPERM; } - if (!is_state(dlc, DOWNLOAD_CLIENT_IDLE) && !is_state(dlc, DOWNLOAD_CLIENT_FINISHED)) { - k_mutex_unlock(&dlc->mutex); - return -EALREADY; - } + host_connected = is_state(dlc, DOWNLOAD_CLIENT_CONNECTED) && + dlc->host_config.hostname == host_config->hostname; + dlc->host_config = *host_config; dlc->file = file; dlc->file_size = 0; dlc->progress = from; dlc->buf_offset = 0; - dlc->http.header.has_end = false; - dlc->http.header.hdr_len = 0; - dlc->http.header.status_code = 0; - if (is_state(dlc, DOWNLOAD_CLIENT_IDLE)) { - set_state(dlc, DOWNLOAD_CLIENT_CONNECTING); + /* Socket configuration */ + err = url_parse_proto(dlc->host_config.hostname, &dlc->sock.proto, &dlc->sock.type); + if (err == -EINVAL) { + LOG_DBG("Protocol not specified, defaulting to HTTP(S)"); + dlc->sock.type = SOCK_STREAM; + if (dlc->host_config.sec_tag_list && (dlc->host_config.sec_tag_count > 0)) { + dlc->sock.proto = IPPROTO_TLS_1_2; + } else { + dlc->sock.proto = IPPROTO_TCP; + } + } else if (err) { + return err; + } + + if ((dlc->sock.proto == IPPROTO_UDP || dlc->sock.proto == IPPROTO_DTLS_1_2) && + (!IS_ENABLED(CONFIG_COAP))) { + return -EPROTONOSUPPORT; + } + + if (dlc->sock.proto == IPPROTO_TLS_1_2 || dlc->sock.proto == IPPROTO_DTLS_1_2) { + if (dlc->host_config.sec_tag_list == NULL || dlc->host_config.sec_tag_count == 0) { + LOG_WRN("No security tag provided for TLS/DTLS"); + return -EINVAL; + } + } + + if ((dlc->host_config.sec_tag_list == NULL || dlc->host_config.sec_tag_count == 0) && + dlc->host_config.set_tls_hostname) { + LOG_WRN("set_tls_hostname flag is set for non-TLS connection"); + return -EINVAL; + } + + err = url_parse_port(dlc->host_config.hostname, &dlc->sock.port); + if (err) { + switch (dlc->sock.proto) { + case IPPROTO_TLS_1_2: + dlc->sock.port = 443; + break; + case IPPROTO_TCP: + dlc->sock.port = 80; + break; + case IPPROTO_DTLS_1_2: + dlc->sock.port = 5684; + break; + case IPPROTO_UDP: + dlc->sock.port = 5683; + break; + } + LOG_DBG("Port not specified, using default: %d", dlc->sock.port); + } + + if (dlc->host_config.set_native_tls) { + LOG_DBG("Enabled native TLS"); + dlc->sock.type |= SOCK_NATIVE_TLS; + } + + if (use_http(dlc)) { + dlc->http.header.has_end = false; + dlc->http.header.hdr_len = 0; + dlc->http.header.status_code = 0; + } + + if (is_state(dlc, DOWNLOAD_CLIENT_CONNECTED)) { + if (host_connected) { + state_set(dlc, DOWNLOAD_CLIENT_DOWNLOADING); + } else { + /* We are connected to the wrong host */ + LOG_DBG("Closing connection to connect to different host"); + client_socket_close(dlc); + state_set(dlc, DOWNLOAD_CLIENT_CONNECTING); + } } else { - set_state(dlc, DOWNLOAD_CLIENT_DOWNLOADING); + /* IDLE */ + state_set(dlc, DOWNLOAD_CLIENT_CONNECTING); } k_mutex_unlock(&dlc->mutex); - LOG_INF("Downloading: %s [%u]", dlc->file, dlc->progress); - /* Let the thread run */ k_sem_give(&dlc->wait_for_download); return 0; } -int download_client_get(struct download_client *dlc, const char *host, - const struct download_client_cfg *config, const char *file, size_t from) +int download_client_stop(struct download_client *const dlc) +{ + if (dlc == NULL || is_state(dlc, DOWNLOAD_CLIENT_IDLE)) { + return -EINVAL; + } + + client_close(dlc); + state_set(dlc, DOWNLOAD_CLIENT_IDLE); + + return 0; +} + +int download_client_get(struct download_client *dlc, + const struct download_client_host_cfg *host_config, + const char *file, size_t from) { int rc; @@ -642,17 +649,13 @@ int download_client_get(struct download_client *dlc, const char *host, } if (file == NULL) { - file = host; + file = host_config->hostname; } k_mutex_lock(&dlc->mutex, K_FOREVER); - rc = download_client_set_host(dlc, host, config); - - if (rc == 0) { - dlc->close_when_done = true; - rc = download_client_start(dlc, file, from); - } + dlc->host_config.close_when_done = true; + rc = download_client_start(dlc, host_config, file, from); k_mutex_unlock(&dlc->mutex); diff --git a/subsys/net/lib/download_client/src/http.c b/subsys/net/lib/download_client/src/http.c index e638b08d50e7..ae8915d6fbfe 100644 --- a/subsys/net/lib/download_client/src/http.c +++ b/subsys/net/lib/download_client/src/http.c @@ -55,12 +55,12 @@ int http_get_request_send(struct download_client *dlc) char file[FILENAME_SIZE]; bool tls_force_range; - __ASSERT_NO_MSG(dlc->host); + __ASSERT_NO_MSG(dlc->host_config.hostname); __ASSERT_NO_MSG(dlc->file); dlc->http.header.has_end = false; - err = url_parse_host(dlc->host, host, sizeof(host)); + err = url_parse_host(dlc->host_config.hostname, host, sizeof(host)); if (err) { return err; } @@ -71,21 +71,21 @@ int http_get_request_send(struct download_client *dlc) } /* nRF91 series has a limitation of decoding ~2k of data at once when using TLS */ - tls_force_range = (dlc->proto == IPPROTO_TLS_1_2 && - !dlc->set_native_tls && + tls_force_range = (dlc->sock.proto == IPPROTO_TLS_1_2 && + !dlc->host_config.set_native_tls && IS_ENABLED(CONFIG_SOC_SERIES_NRF91X)); - if (dlc->config.range_override) { - if (tls_force_range && dlc->config.range_override > (TLS_RANGE_MAX - 1)) { + if (dlc->host_config.range_override) { + if (tls_force_range && dlc->host_config.range_override > (TLS_RANGE_MAX - 1)) { LOG_WRN("Range override > TLS max range, setting to TLS max range"); - dlc->config.range_override = (TLS_RANGE_MAX - 1); + dlc->host_config.range_override = (TLS_RANGE_MAX - 1); } } else if (tls_force_range) { - dlc->config.range_override = TLS_RANGE_MAX - 1; + dlc->host_config.range_override = TLS_RANGE_MAX - 1; } - if (dlc->config.range_override) { - off = dlc->progress + dlc->config.range_override; + if (dlc->host_config.range_override) { + off = dlc->progress + dlc->host_config.range_override; if (dlc->file_size && (off > dlc->file_size - 1)) { /* Don't request bytes past the end of file */ @@ -120,10 +120,6 @@ int http_get_request_send(struct download_client *dlc) LOG_HEXDUMP_DBG(dlc->config.buf, len, "HTTP request"); } - printk("File size: %d\n", dlc->file_size); - printk("Progress: %d\n", dlc->progress); - printk("REQ: %s\n", dlc->config.buf); - err = client_socket_send(dlc, len, 0); if (err) { LOG_ERR("Failed to send HTTP request, errno %d", errno); @@ -269,14 +265,12 @@ int http_parse(struct download_client *dlc, size_t len) { int parsed_len; - LOG_DBG("RES: %s", dlc->config.buf); - if (!dlc->http.header.has_end) { /* Parse what we can from the header */ parsed_len = http_header_parse(dlc, len); if (parsed_len < 0) { /* Something is wrong with the header */ - return parsed_len; + return -EBADMSG; } if (parsed_len == len) { @@ -290,6 +284,11 @@ int http_parse(struct download_client *dlc, size_t len) } if (!dlc->http.header.has_end) { + if (dlc->config.buf_size == dlc->buf_offset) { + LOG_ERR("Could not parse HTTP header lines from server (> %d)", + dlc->config.buf_size); + return -E2BIG; + } /* Wait for rest of header */ return 0; } @@ -302,8 +301,8 @@ int http_parse(struct download_client *dlc, size_t len) if (dlc->progress != dlc->file_size) { if (dlc->http.ranged) { dlc->http.ranged_progress += len; - if (dlc->http.ranged_progress < (dlc->config.range_override ? - dlc->config.range_override : + if (dlc->http.ranged_progress < (dlc->host_config.range_override ? + dlc->host_config.range_override : TLS_RANGE_MAX)) { /* Ranged query: read until a full fragment */ return 0; diff --git a/subsys/net/lib/download_client/src/shell.c b/subsys/net/lib/download_client/src/shell.c index 81ff9639bf4d..310603048867 100644 --- a/subsys/net/lib/download_client/src/shell.c +++ b/subsys/net/lib/download_client/src/shell.c @@ -18,7 +18,7 @@ static char file[CONFIG_DOWNLOAD_CLIENT_MAX_FILENAME_SIZE]; static int sec_tag_list[1]; static struct download_client downloader; -static struct download_client_cfg config = { +static struct download_client_host_cfg host_config = { .sec_tag_list = sec_tag_list, }; @@ -68,7 +68,7 @@ static int download_shell_init(void) static int cmd_dc_config(const struct shell *shell, size_t argc, char **argv) { - shell_warn(shell, "usage: dc config |\n"); + shell_warn(shell, "usage: dc host_config |\n"); return 0; } @@ -76,13 +76,13 @@ static int cmd_dc_config_pdn_id(const struct shell *shell, size_t argc, char **argv) { if (argc != 2) { - shell_warn(shell, "usage: dc config pdn \n"); + shell_warn(shell, "usage: dc host_config pdn \n"); return -EINVAL; } - config.pdn_id = atoi(argv[1]); + host_config.pdn_id = atoi(argv[1]); - shell_print(shell, "PDN ID set: %d\n", config.pdn_id); + shell_print(shell, "PDN ID set: %d\n", host_config.pdn_id); return 0; } @@ -90,14 +90,14 @@ static int cmd_dc_config_sec_tag(const struct shell *shell, size_t argc, char **argv) { if (argc != 2) { - shell_warn(shell, "usage: dc config sec_tag \n"); + shell_warn(shell, "usage: dc host_config sec_tag \n"); return -EINVAL; } sec_tag_list[0] = atoi(argv[1]); - config.sec_tag_count = 1; + host_config.sec_tag_count = 1; - shell_print(shell, "Security tag set: %d\n", config.sec_tag_list[0]); + shell_print(shell, "Security tag set: %d\n", host_config.sec_tag_list[0]); return 0; } @@ -114,9 +114,9 @@ static int cmd_dc_set_host(const struct shell *shell, size_t argc, char **argv) memcpy(host, argv[1], MIN(strlen(argv[1]) + 1, sizeof(host))); - err = download_client_set_host(&downloader, host, &config); + err = download_client_connect(&downloader, host, &host_config); if (err) { - shell_warn(shell, "download_client_set_host() failed, err %d", + shell_warn(shell, "download_client_connect() failed, err %d", err); return -ENOEXEC; } @@ -178,7 +178,7 @@ static int cmd_dc_get(const struct shell *shell, size_t argc, char **argv) } - err = download_client_get(&downloader, host, &config, f, from); + err = download_client_get(&downloader, host, &host_config, f, from); if (err) { shell_warn(shell, "download_client_get() failed, err %d", @@ -193,7 +193,7 @@ static int cmd_dc_get(const struct shell *shell, size_t argc, char **argv) static int cmd_dc_disconnect(const struct shell *shell, size_t argc, char **argv) { - return download_client_disconnect(&downloader); + return download_client_stop(&downloader); } SHELL_STATIC_SUBCMD_SET_CREATE(sub_config, @@ -203,7 +203,7 @@ SHELL_STATIC_SUBCMD_SET_CREATE(sub_config, ); SHELL_STATIC_SUBCMD_SET_CREATE(sub_dc, - SHELL_CMD(config, &sub_config, "Configure", cmd_dc_config), + SHELL_CMD(host_config, &sub_config, "Configure", cmd_dc_config), SHELL_CMD(set_host, NULL, "Set a target host", cmd_dc_set_host), SHELL_CMD(disconnect, NULL, "Disconnect from a host", cmd_dc_disconnect), diff --git a/subsys/net/lib/fota_download/src/fota_download.c b/subsys/net/lib/fota_download/src/fota_download.c index 78d0136ce640..86325d3c269d 100644 --- a/subsys/net/lib/fota_download/src/fota_download.c +++ b/subsys/net/lib/fota_download/src/fota_download.c @@ -155,7 +155,7 @@ static int disconnect(void) #if defined(CONFIG_FOTA_DOWNLOAD_EXTERNAL_DL) return 0; #endif - return download_client_disconnect(&dlc); + return download_client_stop(&dlc); } static int download_client_callback(const struct download_client_evt *event) @@ -587,7 +587,7 @@ int fota_download(const char *host, const char *file, int err; static int sec_tag_list_copy[CONFIG_FOTA_DOWNLOAD_SEC_TAG_LIST_SIZE_MAX]; - struct download_client_cfg config = { + struct download_client_host_cfg config = { .pdn_id = pdn_id, .range_override = fragment_size, }; diff --git a/subsys/net/lib/nrf_cloud/include/nrf_cloud_download.h b/subsys/net/lib/nrf_cloud/include/nrf_cloud_download.h index 5af78b40b184..909d366c8ebc 100644 --- a/subsys/net/lib/nrf_cloud/include/nrf_cloud_download.h +++ b/subsys/net/lib/nrf_cloud/include/nrf_cloud_download.h @@ -41,7 +41,7 @@ struct nrf_cloud_download_data { const char *path; /* Download client configuration */ - struct download_client_cfg dl_cfg; + struct download_client_host_cfg dl_cfg; union { /* FOTA type data */ diff --git a/subsys/net/lib/nrf_cloud/src/nrf_cloud_download.c b/subsys/net/lib/nrf_cloud/src/nrf_cloud_download.c index 0ad7f3151d95..991af8c672d1 100644 --- a/subsys/net/lib/nrf_cloud/src/nrf_cloud_download.c +++ b/subsys/net/lib/nrf_cloud/src/nrf_cloud_download.c @@ -394,7 +394,7 @@ static int dlc_disconnect(struct nrf_cloud_download_data *const dl) return coap_dl_disconnect(); #endif /* CONFIG_NRF_CLOUD_COAP_DOWNLOADS */ - return download_client_disconnect(dl->dlc); + return download_client_stop(dl->dlc); } static void active_dl_reset(void) diff --git a/subsys/net/lib/nrf_cloud/src/nrf_cloud_pgps_utils.c b/subsys/net/lib/nrf_cloud/src/nrf_cloud_pgps_utils.c index bea80135ca94..13d5ee1a40e1 100644 --- a/subsys/net/lib/nrf_cloud/src/nrf_cloud_pgps_utils.c +++ b/subsys/net/lib/nrf_cloud/src/nrf_cloud_pgps_utils.c @@ -570,7 +570,7 @@ static int download_client_callback(const struct download_client_evt *event) /* CoAP downloads do not need to disconnect since they don't directly use download_client */ #if !defined(CONFIG_NRF_CLOUD_COAP_DOWNLOADS) - int ret = download_client_disconnect(&dlc); + int ret = download_client_stop(&dlc); if (ret) { LOG_ERR("Error disconnecting from download client:%d", ret); diff --git a/tests/subsys/net/lib/download_client/src/main.c b/tests/subsys/net/lib/download_client/src/main.c index 4c7dabc45044..a87b34f4a4ce 100644 --- a/tests/subsys/net/lib/download_client/src/main.c +++ b/tests/subsys/net/lib/download_client/src/main.c @@ -79,7 +79,7 @@ static struct download_client_evt get_next_event(k_timeout_t timeout) return evt; } -static struct download_client_cfg config = { +static struct download_client_host_cfg config = { .pdn_id = 0, .range_override = 0, }; diff --git a/tests/subsys/net/lib/fota_download/src/test_fota_download.c b/tests/subsys/net/lib/fota_download/src/test_fota_download.c index 411523c03fc5..43178306e053 100644 --- a/tests/subsys/net/lib/fota_download/src/test_fota_download.c +++ b/tests/subsys/net/lib/fota_download/src/test_fota_download.c @@ -100,7 +100,7 @@ enum dfu_target_image_type dfu_target_smp_img_type_check(const void *const buf, } int download_client_get(struct download_client *client, const char *host, - const struct download_client_cfg *config, const char *file, size_t from) + const struct download_client_host_cfg *config, const char *file, size_t from) { if (fail_on_connect == true) { return -1; @@ -122,7 +122,7 @@ int download_client_get(struct download_client *client, const char *host, return 0; } -int download_client_disconnect(struct download_client *client) +int download_client_stop(struct download_client *client) { const struct download_client_evt evt = { .id = DOWNLOAD_CLIENT_EVT_CLOSED,