From 898ce25474b7446472debd4a630b8488f9d5c2b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eivind=20J=C3=B8lsgard?= Date: Wed, 13 Nov 2024 12:17:22 +0100 Subject: [PATCH] net: new downloader MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The new downloader is replacing the download_client library and is based on that. Internal restructuring: * Restructuring of socket functions and files. * Parse HTTP header line for line. This reduces the size requirement for the download client recv buffer. * Change TLS range override logic. * Use range requests for nRF91 TLS only, and when specified by app. API updates: * Let application provide client buffer. This allows for multiple download clients with different buffer sizes. * Add downloader_deinit() * Add downloader_stop() * Remove downloader_disconnect() * Changed signature of downloader_init(), downloader_start() and downloader_get() to take a URI. * Added downloader_get_with_host_and_path() for downloads where host and path are separate arguments to keep backwards compatibility. * The transports (http, coap) are now separated out of the download client with its own API. Future work: * Take uri as input param to fota_download library and use URI in other relevant libaries and structures. * Curent download client is deprecated and will be removed later. Signed-off-by: Eivind Jølsgard --- CODEOWNERS | 8 + .../asset_tracker_v2/boards/native_sim.conf | 3 +- .../doc/asset_tracker_v2_description.rst | 2 +- .../asset_tracker_v2/overlay-carrier.conf | 4 +- applications/asset_tracker_v2/prj.conf | 8 +- .../serial_lte_modem/doc/slm_description.rst | 2 +- .../serial_lte_modem/overlay-carrier.conf | 2 +- applications/serial_lte_modem/prj.conf | 6 +- .../serial_lte_modem/src/slm_at_fota.c | 4 +- .../bin/lwm2m_carrier/app_integration.rst | 2 +- .../bin/lwm2m_carrier/requirements.rst | 2 +- doc/nrf/libraries/networking/aws_fota.rst | 16 +- doc/nrf/libraries/networking/azure_fota.rst | 8 +- doc/nrf/libraries/networking/downloader.rst | 195 +++ .../libraries/networking/fota_download.rst | 4 +- doc/nrf/libraries/networking/nrf_cloud.rst | 2 +- .../libraries/networking/nrf_cloud_pgps.rst | 2 +- .../releases_and_maturity/known_issues.rst | 14 +- .../releases/release-notes-2.4.0.rst | 4 +- include/net/download_client.h | 9 +- include/net/downloader.h | 353 +++++ include/net/downloader_transport.h | 76 + include/net/fota_download.h | 15 +- include/net/nrf_cloud_pgps.h | 5 +- lib/bin/lwm2m_carrier/Kconfig | 15 +- lib/bin/lwm2m_carrier/include/lwm2m_os.h | 2 +- lib/bin/lwm2m_carrier/os/lwm2m_os.c | 38 +- .../application_update/overlay-carrier.conf | 2 +- .../http_update/application_update/prj.conf | 6 +- .../http_update/application_update/src/main.c | 1 - .../http_update/modem_delta_update/prj.conf | 7 +- .../http_update/modem_delta_update/src/main.c | 19 +- .../http_update/modem_full_update/prj.conf | 6 +- .../http_update/modem_full_update/src/main.c | 45 +- samples/cellular/location/overlay-pgps.conf | 2 +- samples/cellular/lwm2m_carrier/prj.conf | 4 +- samples/cellular/lwm2m_client/prj.conf | 5 +- .../lwm2m_client/sample_description.rst | 2 +- .../cellular/modem_shell/overlay-carrier.conf | 2 +- .../modem_shell/overlay-modem_fota_full.conf | 2 +- samples/cellular/modem_shell/prj.conf | 3 +- .../modem_shell/src/fota/fota_shell.c | 6 +- samples/cellular/modem_shell/src/gnss/gnss.c | 3 + .../cellular/nrf_cloud_multi_service/Kconfig | 2 +- .../cellular/nrf_cloud_multi_service/prj.conf | 8 +- samples/cellular/nrf_cloud_rest_fota/prj.conf | 2 +- .../boards/nrf7002dk_nrf5340_cpuapp_ns.conf | 6 +- .../aws_iot/boards/nrf9151dk_nrf9151_ns.conf | 4 +- .../aws_iot/boards/nrf9160dk_nrf9160_ns.conf | 4 +- .../aws_iot/boards/nrf9161dk_nrf9161_ns.conf | 4 +- .../aws_iot/boards/thingy91_nrf9160_ns.conf | 4 +- .../aws_iot/boards/thingy91x_nrf9151_ns.conf | 4 +- samples/net/azure_iot_hub/README.rst | 12 +- .../boards/nrf7002dk_nrf5340_cpuapp_ns.conf | 6 +- .../boards/nrf9151dk_nrf9151_ns.conf | 4 +- .../boards/nrf9160dk_nrf9160_ns.conf | 4 +- .../boards/nrf9161dk_nrf9161_ns.conf | 4 +- samples/net/download/README.rst | 4 +- samples/net/download/prj.conf | 4 +- samples/net/download/sample.yaml | 8 +- samples/net/download/src/main.c | 58 +- scripts/quarantine_integration.yaml | 4 +- subsys/dfu/dfu_target/Kconfig | 1 - subsys/net/lib/CMakeLists.txt | 5 +- subsys/net/lib/Kconfig | 1 + subsys/net/lib/aws_fota/src/aws_fota.c | 4 +- subsys/net/lib/azure_fota/azure_fota.c | 4 +- subsys/net/lib/downloader/CMakeLists.txt | 30 + subsys/net/lib/downloader/Kconfig | 111 ++ subsys/net/lib/downloader/dl_transports.ld | 5 + subsys/net/lib/downloader/include/dl_parse.h | 16 + subsys/net/lib/downloader/include/dl_socket.h | 22 + subsys/net/lib/downloader/src/dl_parse.c | 136 ++ subsys/net/lib/downloader/src/dl_socket.c | 406 +++++ subsys/net/lib/downloader/src/downloader.c | 641 ++++++++ subsys/net/lib/downloader/src/sanity.c | 20 + subsys/net/lib/downloader/src/shell.c | 274 ++++ .../net/lib/downloader/src/transports/coap.c | 549 +++++++ .../net/lib/downloader/src/transports/http.c | 569 +++++++ subsys/net/lib/fota_download/CMakeLists.txt | 4 +- subsys/net/lib/fota_download/Kconfig | 10 +- .../net/lib/fota_download/src/fota_download.c | 127 +- .../src/util/fota_download_delta_modem.c | 3 +- .../src/util/fota_download_full_modem.c | 6 +- .../src/util/fota_download_util.c | 26 +- subsys/net/lib/mcumgr_smp_client/Kconfig | 2 +- .../src/mcumgr_smp_client_shell.c | 2 +- .../net/lib/nrf_cloud/Kconfig.nrf_cloud_fota | 2 +- .../net/lib/nrf_cloud/Kconfig.nrf_cloud_pgps | 4 +- .../nrf_cloud/include/nrf_cloud_download.h | 10 +- .../nrf_cloud/include/nrf_cloud_pgps_utils.h | 1 - .../nrf_cloud/src/nrf_cloud_codec_internal.c | 3 +- .../lib/nrf_cloud/src/nrf_cloud_download.c | 39 +- subsys/net/lib/nrf_cloud/src/nrf_cloud_fota.c | 9 +- .../lib/nrf_cloud/src/nrf_cloud_fota_poll.c | 9 +- subsys/net/lib/nrf_cloud/src/nrf_cloud_pgps.c | 8 +- .../lib/nrf_cloud/src/nrf_cloud_pgps_utils.c | 49 +- .../lib/aws_fota/aws_fota_json/CMakeLists.txt | 4 +- .../subsys/net/lib/downloader/CMakeLists.txt | 50 + .../net/lib/downloader/boards/native_sim.conf | 5 + .../lib/downloader/boards/qemu_cortex_m3.conf | 5 + tests/subsys/net/lib/downloader/prj.conf | 7 + tests/subsys/net/lib/downloader/src/main.c | 1373 +++++++++++++++++ tests/subsys/net/lib/downloader/testcase.yaml | 7 + .../net/lib/fota_download/CMakeLists.txt | 10 +- .../fota_download/src/test_fota_download.c | 52 +- .../net/lib/lwm2m_client_utils/CMakeLists.txt | 6 +- .../net/lib/lwm2m_fota_utils/CMakeLists.txt | 6 +- .../net/lib/mcumgr_smp_client/CMakeLists.txt | 12 +- .../net/lib/nrf_cloud/cloud/CMakeLists.txt | 4 +- 110 files changed, 5306 insertions(+), 405 deletions(-) create mode 100644 doc/nrf/libraries/networking/downloader.rst create mode 100644 include/net/downloader.h create mode 100644 include/net/downloader_transport.h create mode 100644 subsys/net/lib/downloader/CMakeLists.txt create mode 100644 subsys/net/lib/downloader/Kconfig create mode 100644 subsys/net/lib/downloader/dl_transports.ld create mode 100644 subsys/net/lib/downloader/include/dl_parse.h create mode 100644 subsys/net/lib/downloader/include/dl_socket.h create mode 100644 subsys/net/lib/downloader/src/dl_parse.c create mode 100644 subsys/net/lib/downloader/src/dl_socket.c create mode 100644 subsys/net/lib/downloader/src/downloader.c create mode 100644 subsys/net/lib/downloader/src/sanity.c create mode 100644 subsys/net/lib/downloader/src/shell.c create mode 100644 subsys/net/lib/downloader/src/transports/coap.c create mode 100644 subsys/net/lib/downloader/src/transports/http.c create mode 100644 tests/subsys/net/lib/downloader/CMakeLists.txt create mode 100644 tests/subsys/net/lib/downloader/boards/native_sim.conf create mode 100644 tests/subsys/net/lib/downloader/boards/qemu_cortex_m3.conf create mode 100644 tests/subsys/net/lib/downloader/prj.conf create mode 100644 tests/subsys/net/lib/downloader/src/main.c create mode 100644 tests/subsys/net/lib/downloader/testcase.yaml diff --git a/CODEOWNERS b/CODEOWNERS index 35a62f07f935..44adb327f88c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -160,6 +160,7 @@ /doc/nrf/libraries/networking/azure_*.rst @nrfconnect/ncs-cia-doc /doc/nrf/libraries/networking/coap_utils.rst @nrfconnect/ncs-terahertz-doc /doc/nrf/libraries/networking/download_client.rst @nrfconnect/ncs-modem-doc +/doc/nrf/libraries/networking/downloader.rst @nrfconnect/ncs-modem-doc /doc/nrf/libraries/networking/fota_download.rst @nrfconnect/ncs-pluto-doc /doc/nrf/libraries/networking/ftp_client.rst @nrfconnect/ncs-iot-oulu-tampere-doc /doc/nrf/libraries/networking/icalendar_parser.rst @nrfconnect/ncs-doc-leads @@ -325,7 +326,10 @@ /include/mgmt/ @nrfconnect/ncs-pluto /include/modem/ @nrfconnect/ncs-modem /include/mpsl/ @nrfconnect/ncs-dragoon +/include/net/ @nrfconnect/ncs-co-networking /include/net/azure_* @nrfconnect/ncs-cia @coderbyheart +/include/net/download_client* @nrfconnect/ncs-modem +/include/net/downloader* @nrfconnect/ncs-modem /include/net/nrf_cloud_* @nrfconnect/ncs-nrf-cloud /include/net/wifi_credentials.h @nrfconnect/ncs-cia /include/nfc/ @nrfconnect/ncs-co-drivers @nrfconnect/ncs-si-muffin @@ -674,6 +678,8 @@ /subsys/net/lib/mqtt_helper/ @nrfconnect/ncs-cia /subsys/net/lib/azure_* @nrfconnect/ncs-cia @coderbyheart /subsys/net/lib/aws_* @nrfconnect/ncs-cia @coderbyheart +/subsys/net/lib/download_client* @nrfconnect/ncs-modem +/subsys/net/lib/downloader/ @nrfconnect/ncs-modem /subsys/net/lib/ftp_client/ @nrfconnect/ncs-iot-oulu /subsys/net/lib/icalendar_parser/ @lats1980 /subsys/net/lib/lwm2m_client_utils/ @nrfconnect/ncs-co-networking @nrfconnect/ncs-iot-oulu @@ -777,6 +783,8 @@ /tests/subsys/mpsl/ @nrfconnect/ncs-dragoon /tests/subsys/net/lib/aws_*/ @nrfconnect/ncs-cia /tests/subsys/net/lib/azure_iot_hub/ @nrfconnect/ncs-cia +/tests/subsys/net/lib/downloader/ @nrfconnect/ncs-modem +/tests/subsys/net/lib/download_client/ @nrfconnect/ncs-modem /tests/subsys/net/lib/fota_download/ @nrfconnect/ncs-pluto /tests/subsys/net/lib/lwm2m_*/ @nrfconnect/ncs-iot-oulu /tests/subsys/net/lib/mqtt_helper/ @nrfconnect/ncs-cia diff --git a/applications/asset_tracker_v2/boards/native_sim.conf b/applications/asset_tracker_v2/boards/native_sim.conf index 468ffc505eb7..e337465a8bc6 100644 --- a/applications/asset_tracker_v2/boards/native_sim.conf +++ b/applications/asset_tracker_v2/boards/native_sim.conf @@ -103,8 +103,7 @@ CONFIG_NET_CONFIG_MY_IPV4_GW="192.0.2.2" # FOTA CONFIG_FOTA_DOWNLOAD=n CONFIG_DFU_TARGET=n -CONFIG_DOWNLOAD_CLIENT=n -CONFIG_DOWNLOAD_CLIENT_HTTP_FRAG_SIZE_1024=n +CONFIG_DOWNLOADER=n # MCUBOOT CONFIG_BOOTLOADER_MCUBOOT=n diff --git a/applications/asset_tracker_v2/doc/asset_tracker_v2_description.rst b/applications/asset_tracker_v2/doc/asset_tracker_v2_description.rst index 6be8811c08ef..a2db86a4a8fe 100644 --- a/applications/asset_tracker_v2/doc/asset_tracker_v2_description.rst +++ b/applications/asset_tracker_v2/doc/asset_tracker_v2_description.rst @@ -235,7 +235,7 @@ This application uses the following |NCS| libraries and drivers: * :ref:`lib_date_time` * :ref:`lte_lc_readme` * :ref:`modem_info_readme` -* :ref:`lib_download_client` +* :ref:`lib_downloader` * :ref:`lib_fota_download` * :ref:`caf_leds` diff --git a/applications/asset_tracker_v2/overlay-carrier.conf b/applications/asset_tracker_v2/overlay-carrier.conf index f28a35093f29..6b446bcc7984 100644 --- a/applications/asset_tracker_v2/overlay-carrier.conf +++ b/applications/asset_tracker_v2/overlay-carrier.conf @@ -18,8 +18,8 @@ CONFIG_PDN=y # AT Monitor is used by PDN library CONFIG_AT_MONITOR=y -# Download client for DFU -CONFIG_DOWNLOAD_CLIENT_MAX_FILENAME_SIZE=230 +# Downloader for DFU +CONFIG_DOWNLOADER_MAX_FILENAME_SIZE=230 # Modem info CONFIG_MODEM_INFO_BUFFER_SIZE=512 diff --git a/applications/asset_tracker_v2/prj.conf b/applications/asset_tracker_v2/prj.conf index 7c6e966c1456..e1d8c07e2e7a 100644 --- a/applications/asset_tracker_v2/prj.conf +++ b/applications/asset_tracker_v2/prj.conf @@ -60,11 +60,9 @@ CONFIG_FCB=y # FOTA CONFIG_FOTA_DOWNLOAD=y CONFIG_DFU_TARGET=y -CONFIG_DOWNLOAD_CLIENT=y -CONFIG_DOWNLOAD_CLIENT_HTTP_FRAG_SIZE_1024=y -CONFIG_DOWNLOAD_CLIENT_STACK_SIZE=4096 -CONFIG_DOWNLOAD_CLIENT_BUF_SIZE=2300 -CONFIG_DOWNLOAD_CLIENT_MAX_HOSTNAME_SIZE=128 +CONFIG_DOWNLOADER=y +CONFIG_DOWNLOADER_STACK_SIZE=4096 +CONFIG_DOWNLOADER_MAX_HOSTNAME_SIZE=128 # Flash - Used in FOTA, settings and storage for P-GPS. CONFIG_FLASH=y diff --git a/applications/serial_lte_modem/doc/slm_description.rst b/applications/serial_lte_modem/doc/slm_description.rst index 70794bc0d6e9..26299f636898 100644 --- a/applications/serial_lte_modem/doc/slm_description.rst +++ b/applications/serial_lte_modem/doc/slm_description.rst @@ -677,7 +677,7 @@ This application uses the following |NCS| libraries: * :ref:`lib_ftp_client` * :ref:`sms_readme` * :ref:`lib_fota_download` -* :ref:`lib_download_client` +* :ref:`lib_downloader` * :ref:`lib_nrf_cloud` * :ref:`lib_nrf_cloud_agnss` * :ref:`lib_nrf_cloud_pgps` diff --git a/applications/serial_lte_modem/overlay-carrier.conf b/applications/serial_lte_modem/overlay-carrier.conf index c0f1e53d429d..aed314d75109 100644 --- a/applications/serial_lte_modem/overlay-carrier.conf +++ b/applications/serial_lte_modem/overlay-carrier.conf @@ -13,7 +13,7 @@ CONFIG_FP_HARDABI=y CONFIG_PDN=y # Download client for DFU -CONFIG_DOWNLOAD_CLIENT_MAX_FILENAME_SIZE=230 +CONFIG_DOWNLOADER_MAX_FILENAME_SIZE=230 # Modem info CONFIG_MODEM_INFO_BUFFER_SIZE=512 diff --git a/applications/serial_lte_modem/prj.conf b/applications/serial_lte_modem/prj.conf index 8130326d3728..222c1d3acd9e 100644 --- a/applications/serial_lte_modem/prj.conf +++ b/applications/serial_lte_modem/prj.conf @@ -62,9 +62,9 @@ CONFIG_HTTP_PARSER_URL=y CONFIG_FOTA_DOWNLOAD=y CONFIG_FOTA_DOWNLOAD_PROGRESS_EVT=y CONFIG_DFU_TARGET=y -CONFIG_DOWNLOAD_CLIENT=y -CONFIG_DOWNLOAD_CLIENT_STACK_SIZE=4096 -CONFIG_DOWNLOAD_CLIENT_MAX_FILENAME_SIZE=2048 +CONFIG_DOWNLOADER=y +CONFIG_DOWNLOADER_STACK_SIZE=4096 +CONFIG_DOWNLOADER_MAX_FILENAME_SIZE=2048 CONFIG_BOOTLOADER_MCUBOOT=y CONFIG_IMG_MANAGER=y diff --git a/applications/serial_lte_modem/src/slm_at_fota.c b/applications/serial_lte_modem/src/slm_at_fota.c index 4fffd1dad74d..962b9ee5f8b0 100644 --- a/applications/serial_lte_modem/src/slm_at_fota.c +++ b/applications/serial_lte_modem/src/slm_at_fota.c @@ -26,10 +26,10 @@ LOG_MODULE_REGISTER(slm_fota, CONFIG_SLM_LOG_LEVEL); /* file_uri: scheme://hostname[:port]path[?parameters] */ -#define FILE_URI_MAX CONFIG_DOWNLOAD_CLIENT_MAX_FILENAME_SIZE +#define FILE_URI_MAX CONFIG_DOWNLOADER_MAX_FILENAME_SIZE #define SCHEMA_HTTP "http" #define SCHEMA_HTTPS "https" -#define URI_HOST_MAX CONFIG_DOWNLOAD_CLIENT_MAX_HOSTNAME_SIZE +#define URI_HOST_MAX CONFIG_DOWNLOADER_MAX_HOSTNAME_SIZE #define URI_SCHEMA_MAX 8 #define ERASE_POLL_TIME 2 diff --git a/doc/nrf/libraries/bin/lwm2m_carrier/app_integration.rst b/doc/nrf/libraries/bin/lwm2m_carrier/app_integration.rst index c2f93fd1d9ab..ed82e52b6abf 100644 --- a/doc/nrf/libraries/bin/lwm2m_carrier/app_integration.rst +++ b/doc/nrf/libraries/bin/lwm2m_carrier/app_integration.rst @@ -31,7 +31,7 @@ It provides an abstraction of the following modules: .. lwm2m_osal_mod_list_start * :ref:`at_monitor_readme` - * :ref:`lib_download_client` + * :ref:`lib_downloader` * :ref:`sms_readme` * :ref:`pdn_readme` * :ref:`lib_dfu_target` diff --git a/doc/nrf/libraries/bin/lwm2m_carrier/requirements.rst b/doc/nrf/libraries/bin/lwm2m_carrier/requirements.rst index b6e137e41bd1..1dcffe165866 100644 --- a/doc/nrf/libraries/bin/lwm2m_carrier/requirements.rst +++ b/doc/nrf/libraries/bin/lwm2m_carrier/requirements.rst @@ -52,7 +52,7 @@ Following are some of the requirements and limitations of the application while * For example, setting :kconfig:option:`CONFIG_LWM2M_CARRIER_SERVER_SEC_TAG` to 42 uses the security tag range 43 to 46 instead of 25 to 28. * The CA certificates that are used for out-of-band FOTA must be provided by the application. - Out-of-band FOTA updates are done by the :ref:`lib_download_client`. + Out-of-band FOTA updates are done by the :ref:`lib_downloader`. Although the certificates are updated as part of the |NCS| releases, you must check the requirements from your carrier to know which certificates are applicable. * The LwM2M carrier library uses the following NVS record key range: ``0xCA00`` to ``0xCAFF``. diff --git a/doc/nrf/libraries/networking/aws_fota.rst b/doc/nrf/libraries/networking/aws_fota.rst index 9ed2a33e3b42..ee0b628a9cad 100644 --- a/doc/nrf/libraries/networking/aws_fota.rst +++ b/doc/nrf/libraries/networking/aws_fota.rst @@ -62,7 +62,7 @@ Creating a FOTA job #. Click the uploaded image file :file:`app_update.bin` and copy the *Object URL* without the *https://* prefix and folder path. #. Create a text file (job document) with content as in the snippet, replacing the following data: - * *protocol* with either `http` or `https`. + * *protocol* with either ``http`` or ``https``. * *host_url* with the *Object URL* copied in the previous step (for example, ``examplebucket.s3.eu-central-1.amazonaws.com``). * *file_path* with the path and file name (for example, ``app_update.bin``). @@ -105,10 +105,10 @@ Configure the following parameters when using this library: * :kconfig:option:`CONFIG_AWS_FOTA_PAYLOAD_SIZE` - Sets the maximum payload size for AWS IoT job messages. * :kconfig:option:`CONFIG_AWS_FOTA_DOWNLOAD_SECURITY_TAG` - Sets the security tag to be used in case of HTTPS downloads. -Additionally, configure the :ref:`lib_download_client` library: +Additionally, configure the :ref:`lib_downloader` library: -* :kconfig:option:`CONFIG_DOWNLOAD_CLIENT_MAX_HOSTNAME_SIZE` - Sets the maximum length of the host name for the download client. -* :kconfig:option:`CONFIG_DOWNLOAD_CLIENT_MAX_FILENAME_SIZE` - Sets the maximum length of the file name for the download client. +* :kconfig:option:`CONFIG_DOWNLOADER_MAX_HOSTNAME_SIZE` - Sets the maximum length of the host name for the download client. +* :kconfig:option:`CONFIG_DOWNLOADER_MAX_FILENAME_SIZE` - Sets the maximum length of the file name for the download client. .. _aws_fota_implementation: @@ -133,7 +133,7 @@ The following sequence diagram shows how a firmware over-the-air update is imple AWS IoT jobs ============ -The implementation uses a job document like the following (where *protocol* is either `http` or `https`, *bucket_name* is the name of your bucket and *file_name* is the name of your file) for passing information from `AWS IoT jobs`_ to the device: +The implementation uses a job document like the following (where *protocol* is either ``http`` or ``https``, *bucket_name* is the name of your bucket and *file_name* is the name of your file) for passing information from `AWS IoT jobs`_ to the device: .. parsed-literal:: :class: highlight @@ -191,7 +191,7 @@ Presigned URLs When using the presigned URLs, you might need to increase the value of the following Kconfig options to accommodate the long file name and payload size of the presigned URL and the secure download of the image: -* :kconfig:option:`CONFIG_DOWNLOAD_CLIENT_MAX_FILENAME_SIZE`. +* :kconfig:option:`CONFIG_DOWNLOADER_MAX_FILENAME_SIZE`. * :kconfig:option:`CONFIG_AWS_FOTA_PAYLOAD_SIZE`. * :kconfig:option:`CONFIG_MBEDTLS_HEAP_SIZE` - If running Mbed TLS on the application core (Wi-Fi® builds). @@ -199,9 +199,9 @@ Limitations *********** * If the :kconfig:option:`CONFIG_AWS_FOTA_DOWNLOAD_SECURITY_TAG` Kconfig option is not configured but HTTPS is selected as the protocol, the update job fails. - For further information about HTTPS support, refer to :ref:`the HTTPS section of the download client documentation `. + For further information about HTTPS support, refer to :ref:`the HTTPS section of the download client documentation `. * The library requires a Content-Range header to be present in the HTTP response from the server. - This limitation is inherited from the :ref:`lib_download_client` library. + This limitation is inherited from the :ref:`lib_downloader` library. API documentation ***************** diff --git a/doc/nrf/libraries/networking/azure_fota.rst b/doc/nrf/libraries/networking/azure_fota.rst index ba04327482ff..ec09919bdca0 100644 --- a/doc/nrf/libraries/networking/azure_fota.rst +++ b/doc/nrf/libraries/networking/azure_fota.rst @@ -58,16 +58,16 @@ Configure the following parameters when using this library: * :kconfig:option:`CONFIG_AZURE_FOTA_TLS` - Enables HTTPS for downloads. By default, TLS is enabled and currently, the transport protocol must be configured at compile time. * :kconfig:option:`CONFIG_AZURE_FOTA_SEC_TAG` - Sets the security tag for TLS credentials when using HTTPS as the transport layer. See :ref:`azure_iot_hub_flash_certs` for more details. -Additionally, configure the :ref:`lib_download_client` library: +Additionally, configure the :ref:`lib_downloader` library: -* :kconfig:option:`CONFIG_DOWNLOAD_CLIENT_MAX_HOSTNAME_SIZE` - Sets the maximum length of the host name for the download client. -* :kconfig:option:`CONFIG_DOWNLOAD_CLIENT_MAX_FILENAME_SIZE` - Sets the maximum length of the file name for the download client. +* :kconfig:option:`CONFIG_DOWNLOADER_MAX_HOSTNAME_SIZE` - Sets the maximum length of the host name for the download client. +* :kconfig:option:`CONFIG_DOWNLOADER_MAX_FILENAME_SIZE` - Sets the maximum length of the file name for the download client. Limitations *********** The library requires a ``Content-Range`` header to be present in the HTTP response from the server. -This limitation is inherited from the :ref:`lib_download_client` library. +This limitation is inherited from the :ref:`lib_downloader` library. API documentation ***************** diff --git a/doc/nrf/libraries/networking/downloader.rst b/doc/nrf/libraries/networking/downloader.rst new file mode 100644 index 000000000000..ef9ebe9f6311 --- /dev/null +++ b/doc/nrf/libraries/networking/downloader.rst @@ -0,0 +1,195 @@ +.. _lib_downloader: + +Downloader library +################## + +.. contents:: + :local: + :depth: 2 + +You can use the downloader client library to download files from a server. + +Overview +******** + +The download is carried out in a separate thread and the application receives events such as :c:enumerator:`DOWNLOADER_EVT_FRAGMENT` that contain the data fragments as the download progresses. +When the download completes, the library sends the :c:enumerator:`DOWNLOADER_EVT_DONE` event to the application. + +Protocols +========= + +The library supports HTTP, HTTPS (TLS 1.2), CoAP, and CoAPS (DTLS 1.2) over IPv4 and IPv6. +If other protocols are required they can be added by the application. +See :file:`downloader_transport.h` for details. +The protocol used for the download is specified in the beginning of the ``URI`` or ``host``. +Use ``http://`` for HTTP, ``https://`` for HTTPS, ``coap://`` for COAP and ``coaps://`` for COAPS. +If no protocol is specified, the downloader defaults to HTTP or HTTPS, depending on the server security configuration. + +.. _downloader_https: + +HTTP and HTTPS (TLS 1.2) +------------------------ + +When downloading using HTTP, the library sends only one HTTP request to the server and receives only one HTTP response. + +When downloading using HTTPS with the nRF91 Series, it is carried out through `Content-Range requests (IETF RFC 7233)`_ due to memory constraints that limit the maximum HTTPS message size to two kilobytes. +The library thus sends and receives as many requests and responses as the number of fragments that constitutes the download. +For example, to download a file of size 47 kilobytes file with a fragment size of 2 kilobytes, a total of 24 HTTP GET requests are sent. +The download can also be carried out through fragments by specifying the :c:member:`downloader_host_cfg.range_override` field of the host configuration. + +CoAP and CoAPS (DTLS 1.2) +------------------------- + +The CoAP feature is disabled by default. +You can enable it using the :kconfig:option:`CONFIG_DOWNLOADER_TRANSPORT_COAP` Kconfig option. +When downloading from a CoAP server, the library uses the CoAP block-wise transfer. + +Configuration +************* + +The configuration of the library depends on the protocol you are using. + +Configuring HTTP and HTTPS (TLS 1.2) +==================================== + +Make sure that the buffer provided to the downloader is large enough to accommodate the entire HTTP header of the request. +Ensure that the values of the :kconfig:option:`CONFIG_DOWNLOADER_MAX_HOSTNAME_SIZE` and :kconfig:option:`CONFIG_DOWNLOADER_MAX_FILENAME_SIZE` Kconfig options are large enough for your host and filenames, respectively. + +Moreover, the application must provision the TLS credentials and pass the security tag to the library when using HTTPS and calling the :c:func:`downloader_get` 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) +===================================== + +Make sure the buffer provided to the downloader is large enough to accommodate the entire CoAP header and the CoAP block. +The CoAP block size is provided by the :kconfig:option:`CONFIG_DOWNLOADER_COAP_BLOCK_SIZE` Kconfig option. +Ensure that the values of the :kconfig:option:`CONFIG_DOWNLOADER_MAX_HOSTNAME_SIZE` and :kconfig:option:`CONFIG_DOWNLOADER_MAX_FILENAME_SIZE` Kconfig options are large enough for your host and filenames, respectively. + +The application must provision the TLS credentials and pass the security tag to the library when using CoAPS and calling the :c:func:`downloader_get` function. + +When you have modem firmware v1.3.5 or newer, you can use the DTLS Connection Identifier feature in this library by setting the ``cid`` flag in the :c:struct:`downloader_host_cfg` structure. + +Limitations +*********** + +The library requires the host server to provide a Content-Range field in the HTTP GET header when using HTTPS with the nRF91 Series devices. +If this header field is missing, the library logs the following error:: + + downloader: Server did not send "Content-Range" in response + +It is not possible to use a CoAP block size of 1024 bytes, due to internal limitations. + +Usage +***** + +To initialize the library, call the :c:func:`downloader_init` function as follows: + +.. code-block:: c + + int err; + + static int dlc_callback(const struct downloader_evt *event); + + char dlc_buf[2048]; + struct downloader dlc; + struct downloader_cfg dlc_conf = { + .callback = dlc_callback, + .buf = dlc_buf, + .buf_size, + }; + + err = downloader_init(&dlc, &dls_config); + if (err) { + printk("downloader init failed, err %d\n", err); + } + +To deinitialize the library, call the :c:func:`downloader_deinit` function as follows: + +.. code-block:: c + + int err; + struct downloader dlc; + + /* downloader is initialized */ + + err = downloader_deinit(&dlc); + if (err) { + printk("downloader deinit failed, err %d\n", err); + } + +This will free up the resources used by the library. + +The following snippet shows how to download a file using HTTPS: + +.. code-block:: c + + + int err; + int dlc_res; + + static int dlc_callback(const struct downloader_evt *event) { + switch (event->id) { + case DOWNLOADER_EVT_FRAGMENT: + printk("Received fragment, dataptr: %p, len %d\n", + event->fragment.buf, event->fragment.len); + return 0; + case DOWNLOADER_EVT_ERROR: + printk("downloader error: %d\n", event->error); + dlc_res = event->error; + return 0; + case DOWNLOADER_EVT_DONE: + printk("downloader done\n"); + dlc_res = 0; + return 0; + case DOWNLOADER_EVT_STOPPED: + printk("downloader stopped\n"); + k_sem_give(&dlc_sem); + return 0; + case DOWNLOADER_EVT_DEINITIALIZED: + printk("downloader deinitialized\n"); + return 0; + } + } + + char dlc_buf[2048]; + struct downloader dlc; + struct downloader_cfg dlc_conf = { + .callback = dlc_callback, + .buf = dlc_buf, + .buf_size, + }; + + int sec_tags[] = {1, 2, 3}; + + struct downloader_host_cfg dlc_host_cfg = { + .sec_tag_list = sec_tags, + .sec_tag_count = ARRAY_SIZE(sec_tags), + /* This will disconnect the downloader from the server when the download is complete */ + .keep_connection = false, + }; + + err = downloader_init(&dlc, &dls_config); + if (err) { + printk("downloader init failed, err %d\n", err); + } + + err = downloader_get(&dlc, &dlc_host_cgf, "https://myserver.com/path/to/file.txt"); + if (err) { + printk("downloader start failed, err %d\n", err); + } + + /* Wait for download to complete */ + k_sem_take(&dlc_sem, K_FOREVER); + + err = downloader_deinit(&dlc); + if (err) { + printk("downloader deinit failed, err %d\n", err); + } + +API documentation +***************** + +| Header file: :file:`include/downloader.h` +| Source files: :file:`subsys/net/lib/downloader/src/` + +.. doxygengroup:: downloader diff --git a/doc/nrf/libraries/networking/fota_download.rst b/doc/nrf/libraries/networking/fota_download.rst index 15d81950cc03..751f1396b21c 100644 --- a/doc/nrf/libraries/networking/fota_download.rst +++ b/doc/nrf/libraries/networking/fota_download.rst @@ -19,7 +19,7 @@ To start a FOTA download, provide the URL for the file that should be downloaded * ``file`` - It indicates the path to the file. For example, ``path/to/resource/file.bin``. -The FOTA library downloads the image using the :ref:`lib_download_client` library. +The FOTA library downloads the image using the :ref:`lib_downloader` library. After downloading the first fragment, it uses the :ref:`lib_dfu_target` library to identify the type of image that is being downloaded. Examples of image types are *modem upgrades* and upgrades handled by a *second-stage bootloader*. @@ -38,7 +38,7 @@ HTTPS downloads The FOTA download library is used in the :ref:`http_application_update_sample` sample. By default, the FOTA download library uses HTTP for downloading the firmware file. -To use HTTPS, apply the changes described in :ref:`the HTTPS section of the download client documentation ` to the library. +To use HTTPS, apply the changes described in :ref:`the HTTPS section of the download client documentation ` to the library. Second-stage bootloader upgrades ******************************** diff --git a/doc/nrf/libraries/networking/nrf_cloud.rst b/doc/nrf/libraries/networking/nrf_cloud.rst index 4680189129dc..59cd39614d04 100644 --- a/doc/nrf/libraries/networking/nrf_cloud.rst +++ b/doc/nrf/libraries/networking/nrf_cloud.rst @@ -160,7 +160,7 @@ nRF Cloud FOTA enables the following additional features and libraries: * :kconfig:option:`CONFIG_FOTA_DOWNLOAD` enables :ref:`lib_fota_download` * :kconfig:option:`CONFIG_DFU_TARGET` enables :ref:`lib_dfu_target` -* :kconfig:option:`CONFIG_DOWNLOAD_CLIENT` enables :ref:`lib_download_client` +* :kconfig:option:`CONFIG_DOWNLOADER` enables :ref:`lib_downloader` * :kconfig:option:`CONFIG_FOTA_DOWNLOAD_PROGRESS_EVT` * :kconfig:option:`CONFIG_FOTA_PROGRESS_EVT_INCREMENT` * :kconfig:option:`CONFIG_REBOOT` diff --git a/doc/nrf/libraries/networking/nrf_cloud_pgps.rst b/doc/nrf/libraries/networking/nrf_cloud_pgps.rst index 0050157c2efa..b927a22dba6b 100644 --- a/doc/nrf/libraries/networking/nrf_cloud_pgps.rst +++ b/doc/nrf/libraries/networking/nrf_cloud_pgps.rst @@ -292,7 +292,7 @@ Dependencies This library uses the following |NCS| libraries: * :ref:`lib_date_time` -* :ref:`lib_download_client` +* :ref:`lib_downloader` * :ref:`modem_info_readme` * :ref:`lib_nrf_cloud` diff --git a/doc/nrf/releases_and_maturity/known_issues.rst b/doc/nrf/releases_and_maturity/known_issues.rst index ec103ce8e6c0..bf94a2b6ed3e 100644 --- a/doc/nrf/releases_and_maturity/known_issues.rst +++ b/doc/nrf/releases_and_maturity/known_issues.rst @@ -3387,7 +3387,7 @@ NCSDK-30161 Combination of :kconfig:option:`CONFIG_ASSERT`, :kconfig:option:`CON **Affected platforms:** nRF54H20 - **Workaround:** Set :kconfig:option:`CONFIG_ASSERT` to ``n`` or :kconfig:option:`CONFIG_SOC_NRF54H20_GPD`to ``n``. + **Workaround:** Set :kconfig:option:`CONFIG_ASSERT` to ``n`` or :kconfig:option:`CONFIG_SOC_NRF54H20_GPD` to ``n``. .. rst-class:: v2-8-0 @@ -3502,7 +3502,7 @@ Jobs not received after reset .. rst-class:: v2-6-2 v2-6-1 v2-6-0 v2-5-3 v2-5-2 v2-5-1 v2-5-0 -NCSDK-24305: fota_download library sends FOTA_DOWNLOAD_EVT_FINISHED when unable to connect +NCSDK-24305: fota_download and fota_download libraries sends FOTA_DOWNLOAD_EVT_FINISHED and FOTA_DOWNLOAD_EVT_FINISHED events, respectively, when unable to connect The :ref:`lib_download_client` library does not resume a download if the device cannot connect to a target server. This causes the :ref:`lib_fota_download` library to incorrectly assume that the download has completed. @@ -3511,9 +3511,9 @@ NCSDK-24305: fota_download library sends FOTA_DOWNLOAD_EVT_FINISHED when unable .. rst-class:: v1-1-0 Stalled download - :ref:`lib_fota_download` does not resume a download if the device loses the connection. + :ref:`lib_fota_download` and :ref:`lib_fota_download` does not resume a download if the device loses the connection. - **Workaround:** Call :cpp:func:`fota_download_start` again with the same arguments when the connection is re-established to resume the download. + **Workaround:** Call :cpp:func:`fota_download_start` or :cpp:func:`fota_download_start` again with the same arguments when the connection is re-established to resume the download. .. rst-class:: v1-1-0 @@ -3526,12 +3526,12 @@ Download stopped on socket connection timeout In the nRF9160: AWS FOTA and :ref:`http_application_update_sample` samples, the download is stopped if the socket connection times out before the modem can delete the modem firmware. A fix for this issue is available in commit `38625ba7 `_. - **Workaround:** Call :cpp:func:`fota_download_start` again with the same arguments. + **Workaround:** Call :cpp:func:`fota_download_start` or :cpp:func:`fota_download_start` again with the same arguments. .. rst-class:: v1-1-0 Update event triggered by an error event - If the last fragment of a :ref:`lib_fota_download` is received but is corrupted, or if the last write is unsuccessful, the library emits an error event as expected. + If the last fragment of a :ref:`lib_fota_download` or :ref:`lib_fota_download` is received but is corrupted, or if the last write is unsuccessful, the library emits an error event as expected. However, it also emits an apply/request update event, even though the downloaded data is invalid. .. rst-class:: v1-0-0 v0-4-0 @@ -5062,7 +5062,7 @@ NCSDK-19536: TF-M does not compile when the board is missing a ``uart1`` node an .. rst-class:: v2-1-4 v2-1-3 v2-1-2 v2-1-1 v2-1-0 v2-0-2 v2-0-1 v2-0-0 NCSDK-15909: TF-M failing to build with Zephyr SDK 0.14.2 - TF-M may fail to build due to flash overflow with Zephyr SDK 0.14.2 when :kconfig:option`CONFIG_TFM_PROFILE_TYPE_NOT_SET` is set to ``y``. + TF-M may fail to build due to flash overflow with Zephyr SDK 0.14.2 when :kconfig:option:`CONFIG_TFM_PROFILE_TYPE_NOT_SET` is set to ``y``. **Workaround:** Use one of the following workarounds: 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 6f5724bd6647..3bdce784a08e 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 ``download_client_set_host``, :c:func:`download_client_start`, and ``download_client_disconnect``. * 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 ``download_client_set_host`` 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 84e5a7953099..82d057702193 100644 --- a/include/net/download_client.h +++ b/include/net/download_client.h @@ -143,8 +143,7 @@ struct download_client_cfg { * * @return Zero to continue the download, non-zero otherwise. */ -typedef int (*download_client_callback_t)( - const struct download_client_evt *event); +typedef int (*download_client_callback_t)(const struct download_client_evt *event); /** * @brief Download client instance. @@ -210,8 +209,7 @@ struct download_client { struct k_sem wait_for_download; /* Internal thread stack. */ - K_THREAD_STACK_MEMBER(thread_stack, - CONFIG_DOWNLOAD_CLIENT_STACK_SIZE); + K_THREAD_STACK_MEMBER(thread_stack, CONFIG_DOWNLOAD_CLIENT_STACK_SIZE); /** Event handler. */ download_client_callback_t callback; @@ -275,8 +273,7 @@ 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 char *file, size_t from); /** * @brief Retrieve the size of the file being downloaded, in bytes. diff --git a/include/net/downloader.h b/include/net/downloader.h new file mode 100644 index 000000000000..4bdfdd81d353 --- /dev/null +++ b/include/net/downloader.h @@ -0,0 +1,353 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/**@file downloader.h + * + * @defgroup downloader Downloader + * @{ + * @brief Client for downloading an object. + * + * @details The downloader provides APIs for: + * - downloading an object from the server, + * - receiving asynchronous event notifications on the download status. + */ + +#ifndef __DOWNLOADER_H__ +#define __DOWNLOADER_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define K_THREAD_STACK_MEMBER K_KERNEL_STACK_MEMBER + +/** + * @brief Downloader event IDs. + */ +enum downloader_evt_id { + /** + * Event contains a fragment of data received from the server. + * When using range requests the amound of data per fragment may be less than the + * range requested. + * The application may return any non-zero value to stop the download. + */ + DOWNLOADER_EVT_FRAGMENT, + /** + * An error has occurred during download and + * the connection to the server has been lost. + * + * Error reason may be one of the following: + * - ECONNRESET: socket error, peer closed connection + * - ECONNREFUSED: socket error, connection refused by server + * - ENETDOWN: socket error, network down + * - ETIMEDOUT: socket error, connection timed out + * - EHOSTDOWN: host went down during download + * - EBADMSG: HTTP response header not as expected + * - ERANGE: HTTP response does not support range requests + * - E2BIG: HTTP response header could not fit in buffer + * - EPROTONOSUPPORT: Protocol is not supported + * - EINVAL: Invalid configuration + * - EAFNOSUPPORT: Unsupported address family (IPv4/IPv6) + * - EHOSTUNREACH: Failed to resolve the target address + * + * In case of @c ECONNRESET errors, returning zero from the callback will let the + * library attempt to reconnect to the server and download the last fragment again. + * Otherwise, the application may return any non-zero value to stop the download. + * On any other error code than @c ECONNRESET, the downloader will not attempt to reconnect + * and ignores the return value. + * + * In case the download is stopped or completed, and the + * @c downloader_host_cfg.keep_connection flag in + * @c downloader_host_cfg.downloader_host_cfg is set, the downloader will stay + * connected to the server. If a new download is initiated from a different server, the + * current connection is closed and the downloader will connect to the new server. The + * connection can be closed by deinitializing the downloader, which will also free the + * downloaders resources. + * + * If the @c downloader_host_cfg.keep_connection flag is not set, the downloader + * automatically closes the connection. The application should wait for the + * @c DOWNLOADER_EVT_STOPPED event before attempting another download. + */ + DOWNLOADER_EVT_ERROR, + /** Download complete. */ + DOWNLOADER_EVT_DONE, + /** Download has been stopped. Downloader is ready for new download. */ + DOWNLOADER_EVT_STOPPED, + /** Downloader deinitialized. Memory can be freed. */ + DOWNLOADER_EVT_DEINITIALIZED, +}; + +struct download_fragment { + const void *buf; + size_t len; +}; + +/** + * @brief Downloader event. + */ +struct downloader_evt { + /** Event ID. */ + enum downloader_evt_id id; + + union { + /** Error cause. */ + int error; + /** Fragment data. */ + struct download_fragment fragment; + }; +}; + +/** + * @brief Downloader asynchronous event handler. + * + * Through this callback, the application receives events, such as + * download of a fragment, download completion, or errors. + * + * On a @c DOWNLOADER_EVT_ERROR event with error @c ECONNRESET, + * returning zero from the callback will let the library attempt + * to reconnect to the server and continue the download. + * Otherwise, the callback may return any non-zero value + * to stop the download. On any other error code than @c ECONNRESET, the downloader + * will not attempt to reconnect and ignores the return value. + * To resume the download, use @ref downloader_get(). + * + * @param[in] event The event. + * + * @return Zero to continue the download, non-zero otherwise. + */ +typedef int (*downloader_callback_t)(const struct downloader_evt *event); + +/** + * @brief Downloader configuration options. + */ +struct downloader_cfg { + /** Event handler. */ + downloader_callback_t callback; + /** Downloader buffer. */ + char *buf; + /** Downloader buffer size. */ + size_t buf_size; +}; + +/** + * @brief Downloader host configuration options. + */ +struct downloader_host_cfg { + /** + * TLS security tag list. + * Pass NULL to disable TLS. + * The list must be kept in scope while download is going on. + */ + const int *sec_tag_list; + /** + * Number of TLS security tags in list. + * Set to 0 to disable TLS. + */ + uint8_t sec_tag_count; + /** + * PDN ID to be used for the download. + * Zero is the default PDN. + */ + uint8_t pdn_id; + /** + * Range override. + * Request a number of bytes from the server at a time. + * 0 disables the range override, and the downloader will ask for the whole file. + * The nRF91 series has a limitation of decoding ~2k of data at once when using TLS, hence + * range override will be used in this case regardless of the value here. + */ + size_t range_override; + /** Use native TLS */ + bool set_native_tls; + /** + * Keep connection to server when done. + * Server is disconnected if a file is requested from another server, and when the + * downloader is deinitialized. + */ + bool keep_connection; + /** + * Enable DTLS connection identifier (CID) feature. + * This option require modem firmware version >= 1.3.5. + */ + bool cid; + /** + * Address family to be used for the download, AF_INET6 or AF_INET. + * Set to AF_UNSPEC (0) to fallback to AF_INET if AF_INET6 does not work. + */ + int family; +}; + +/** + * @brief Downloader internal state. + */ +enum downloader_state { + DOWNLOADER_DEINITIALIZED, + DOWNLOADER_IDLE, + DOWNLOADER_CONNECTING, + DOWNLOADER_CONNECTED, + DOWNLOADER_DOWNLOADING, + DOWNLOADER_STOPPING, + DOWNLOADER_DEINITIALIZING, +}; + +/** + * @brief Download Downloader instance. + * + * Members are set internally by the downloader. + */ +struct downloader { + /** Downloader configuration options. */ + struct downloader_cfg config; + /** Host configuration options. */ + struct downloader_host_cfg host_config; + /** Host name, null-terminated. */ + char hostname[CONFIG_DOWNLOADER_MAX_HOSTNAME_SIZE]; + /** File name, null-terminated. */ + char file[CONFIG_DOWNLOADER_MAX_FILENAME_SIZE]; + /** 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; + + /** Downloader transport, http, CoAP, MQTT, ... + * Store a pointer to the selected transport per DLC instance to avoid looking it up each + * call. + */ + struct dl_transport *transport; + /** Transport parameters. */ + uint8_t transport_internal[CONFIG_DOWNLOADER_TRANSPORT_PARAMS_SIZE]; + + /** Ensure that thread is ready for download */ + struct k_sem event_sem; + /** Protect shared variables. */ + struct k_mutex mutex; + /** Downloader state. */ + enum downloader_state state; + /** Internal download thread. */ + struct k_thread thread; + /** Internal thread ID. */ + k_tid_t tid; + + /* Internal thread stack. */ + K_KERNEL_STACK_MEMBER(thread_stack, CONFIG_DOWNLOADER_STACK_SIZE); +}; + +/** + * @brief Initialize the downloader. + * + * @param[in] dl Downloader instance. + * @param[in] config Downloader configuration options. + * + * @retval int Zero on success, otherwise a negative error code. + */ +int downloader_init(struct downloader *dl, struct downloader_cfg *config); + +/** + * @brief Deinitialize the downloader. + * + * @param[in] dl Downloader instance. + * + * @retval int Zero on success. + */ +int downloader_deinit(struct downloader *dl); + +/** + * @brief Download a file asynchronously. + * + * This initiates an asynchronous connect-download-disconnect sequence to the target + * host. + * + * Downloads are handled one at a time. If previous download is not finished + * this returns -EALREADY. + * + * @param[in] dl Downloader instance. + * @param[in] host_config Host configuration options. + * @param[in] url URI of the host to connect to. + * Can include scheme, port number and full file path, defaults to + * HTTP or HTTPS if no scheme is provided. + * @param[in] from Offset from where to resume the download, + * or zero to download from the beginning. + * + * @retval int Zero on success, a negative error code otherwise. + */ +int downloader_get(struct downloader *dl, + const struct downloader_host_cfg *host_config, const char *url, + size_t from); + +/** + * @brief Download a file asynchronously with host and file as separate parameters. + * + * This initiates an asynchronous connect-download-disconnect sequence to the target + * host. + * + * Downloads are handled one at a time. If previous download is not finished, + * this returns -EALREADY. + * + * @param[in] dl Downloader instance. + * @param[in] host_config Host configuration options. + * @param[in] host URI of the host to connect to. + * Can include scheme and port number, defaults to + * HTTP or HTTPS if no scheme is provided. + * @param[in] file URL to download. + * @param[in] from Offset from where to resume the download, + * or zero to download from the beginning. + * + * @retval int Zero on success, a negative error code otherwise. + */ +int downloader_get_with_host_and_path(struct downloader *dl, + const struct downloader_host_cfg *host_config, + const char *host, const char *file, size_t from); + +/** + * @brief Cancel file download. + * + * Request downloader to stop the download. This does not block. + * If the @c downloader_host_cfg.keep_connection flag is set the downloader remains connected to + * the server. Else the downloader is disconnected and the @c DOWNLOADER_EVT_STOPPED event is sent. + * + * @param[in] dl Downloader instance. + * + * @return Zero on success, a negative error code otherwise. + */ +int downloader_cancel(struct downloader *dl); + +/** + * @brief Retrieve the size of the file being downloaded, in bytes. + * + * The file size is only available after the download has begun. + * + * @param[in] dl Downloader instance. + * @param[out] size File size. + * + * @retval int Zero on success, a negative error code otherwise. + */ +int downloader_file_size_get(struct downloader *dl, size_t *size); + +/** + * @brief Retrieve the number of bytes downloaded so far. + * + * The progress is only available after the download has begun. + * + * @param[in] dl Downloader instance. + * @param[out] size Number of bytes downloaded so far. + * + * @retval int Zero on success, a negative error code otherwise. + */ +int downloader_downloaded_size_get(struct downloader *dl, size_t *size); + +#ifdef __cplusplus +} +#endif + +#endif /* DOWNLOADER_H__ */ + +/**@} */ diff --git a/include/net/downloader_transport.h b/include/net/downloader_transport.h new file mode 100644 index 000000000000..5de5dd2243fa --- /dev/null +++ b/include/net/downloader_transport.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef DOWNLOADER_TRANSPORT_H +#define DOWNLOADER_TRANSPORT_H + +#include + +int dl_transport_evt_data(struct downloader *dl, void *data, size_t len); +int dl_transport_evt_download_complete(struct downloader *dl); + +struct dl_transport { + /** + * Parse protocol + * + */ + bool (*proto_supported)(struct downloader *dl, const char *uri); + /** + * Initialize DLC transport + * + * @param ... + * @returns 0 on success, negative error on failure. + */ + int (*init)(struct downloader *dl, struct downloader_host_cfg *host_cgf, + const char *uri); + /** + * Deinitialize DLC transport + * + * @param ... + * @returns 0 on success, negative error on failure. + */ + int (*deinit)(struct downloader *dl); + /** + * Connect DLC transport. + * + * Connection result is given by callback to @c dl_transport_event_connected. + * + * @param ... + * @returns 0 on success, negative error on failure. + */ + int (*connect)(struct downloader *dl); + /** + * Close DLC transport + * + * @param ... + * @returns 0 on success, negative error on failure. + */ + int (*close)(struct downloader *dl); + /** + * Download data with DLC transport + * + * @param ... + * @returns 0 on success, negative error on failure. + */ + int (*download)(struct downloader *dl); +}; + +struct dl_transport_entry { + struct dl_transport *transport; +}; + +/** + * @brief Define a DLC transport. + * + * @param entry The entry name. + * @param _transport The transport. + */ +#define DLC_TRANSPORT(entry, _transport) \ + static STRUCT_SECTION_ITERABLE(dl_transport_entry, entry) = { \ + .transport = _transport, \ + } + +#endif /* DOWNLOADER_TRANSPORT_H */ diff --git a/include/net/fota_download.h b/include/net/fota_download.h index 505ae1fbb11d..7c45d95c8fc1 100644 --- a/include/net/fota_download.h +++ b/include/net/fota_download.h @@ -20,7 +20,7 @@ #include #include -#include +#include #include #ifdef __cplusplus @@ -139,8 +139,7 @@ int fota_download_init(fota_download_callback_t client_callback); * @param sec_tag_list Security tags that you want to use with HTTPS. Pass NULL to disable TLS. * @param sec_tag_count Number of TLS security tags in list. Pass 0 to disable TLS. * @param pdn_id Packet Data Network ID to use for the download, or 0 to use the default. - * @param fragment_size Fragment size to be used for the download. - * If 0, @kconfig{CONFIG_DOWNLOAD_CLIENT_HTTP_FRAG_SIZE} is used. + * @param fragment_size Fragment size to be used for the download. If 0, no fragment is used. * @param expected_type Type of firmware file to be downloaded and installed. * * @retval 0 If download has started successfully. @@ -172,14 +171,14 @@ int fota_download(const char *host, const char *file, const int *sec_tag_list, * download, both paths will be treated as upgradable bootloader slot 0 * and slot 1 binaries respectively, and only the binary corresponding to * the currently inactive bootloader slot will be selected and downloaded. -* See + * See * Secure Bootloader Chain Docs for details regarding the upgradable * bootloader slots. * @param sec_tag_list Security tags that you want to use with HTTPS. Pass NULL to disable TLS. * @param sec_tag_count Number of TLS security tags in list. Pass 0 to disable TLS. * @param pdn_id Packet Data Network ID to use for the download, or 0 to use the default. * @param fragment_size Fragment size to be used for the download. - * If 0, @kconfig{CONFIG_DOWNLOAD_CLIENT_HTTP_FRAG_SIZE} is used. + * If 0, no fragment is used. * * @retval 0 If download has started successfully. * @retval -EALREADY If download is already ongoing. @@ -205,7 +204,7 @@ int fota_download_any(const char *host, const char *file, const int *sec_tag_lis * @param sec_tag Security tag you want to use with HTTPS. Pass -1 to disable TLS. * @param pdn_id Packet Data Network ID to use for the download, or 0 to use the default. * @param fragment_size Fragment size to be used for the download. - * If 0, @kconfig{CONFIG_DOWNLOAD_CLIENT_HTTP_FRAG_SIZE} is used. + * If 0, no fragmentation is used. * * @retval 0 If download has started successfully. * @retval -EALREADY If download is already ongoing. @@ -230,7 +229,7 @@ int fota_download_start(const char *host, const char *file, int sec_tag, * @param sec_tag Security tag you want to use with HTTPS. Pass -1 to disable TLS. * @param pdn_id Packet Data Network ID to use for the download, or 0 to use the default. * @param fragment_size Fragment size to be used for the download. - * If 0, @kconfig{CONFIG_DOWNLOAD_CLIENT_HTTP_FRAG_SIZE} is used. + * If 0, no fragmentation is used. * @param expected_type Type of firmware file to be downloaded and installed. * * @retval 0 If download has started successfully. @@ -305,7 +304,7 @@ int fota_download_external_start(const char *host, const char *file, * @retval 0 If successful. * Otherwise, a (negative) error code is returned. */ -int fota_download_external_evt_handle(struct download_client_evt const *const evt); +int fota_download_external_evt_handle(struct downloader_evt const *const evt); #ifdef __cplusplus } diff --git a/include/net/nrf_cloud_pgps.h b/include/net/nrf_cloud_pgps.h index 86ad2f3ece30..d99bfab7af13 100644 --- a/include/net/nrf_cloud_pgps.h +++ b/include/net/nrf_cloud_pgps.h @@ -100,12 +100,11 @@ struct gps_pgps_request { * to nrf_cloud_pgps_process(). */ struct nrf_cloud_pgps_result { - /** User-provided buffer to hold download host name */ + /** User-provided buffer to hold download host */ char *host; /** Size of user-provided host buffer */ size_t host_sz; - - /** User-provided buffer to hold download path/file name */ + /** User-provided buffer to hold download path */ char *path; /** Size of user-provided path buffer */ size_t path_sz; diff --git a/lib/bin/lwm2m_carrier/Kconfig b/lib/bin/lwm2m_carrier/Kconfig index 67f4ea1a751e..ee2ad9903586 100644 --- a/lib/bin/lwm2m_carrier/Kconfig +++ b/lib/bin/lwm2m_carrier/Kconfig @@ -26,9 +26,9 @@ menuconfig LWM2M_CARRIER depends on NET_SOCKETS depends on NET_SOCKETS_OFFLOAD # Networking NCS - depends on DOWNLOAD_CLIENT - depends on (DOWNLOAD_CLIENT_MAX_HOSTNAME_SIZE >= 64) - depends on (DOWNLOAD_CLIENT_MAX_FILENAME_SIZE >= 192) + depends on DOWNLOADER + depends on (DOWNLOADER_MAX_HOSTNAME_SIZE >= 64) + depends on (DOWNLOADER_MAX_FILENAME_SIZE >= 192) # AT libraries depends on AT_MONITOR depends on (AT_MONITOR_HEAP_SIZE >= 320) @@ -308,6 +308,15 @@ config LWM2M_CARRIER_FIRMWARE_DOWNLOAD_TIMEOUT PUSH delivery method of firmware images. Setting this to 0 will disable the use of this timer. +config LWM2M_CARRIER_FIRMWARE_DOWNLOAD_BUF_SIZE + int "Firmware download buffer size" + range 128 65535 + default 2048 + help + Size of the buffer used for the download client to download a new firmware image. + Must be large enough to hold the message used to request data from the server, e.g. a + HTTP header. + config LWM2M_CARRIER_AUTO_REGISTER bool "Auto registration on LTE Attach" default y diff --git a/lib/bin/lwm2m_carrier/include/lwm2m_os.h b/lib/bin/lwm2m_carrier/include/lwm2m_os.h index 942a8c5eeabd..17c4927d1f8b 100644 --- a/lib/bin/lwm2m_carrier/include/lwm2m_os.h +++ b/lib/bin/lwm2m_carrier/include/lwm2m_os.h @@ -430,7 +430,7 @@ void lwm2m_os_sms_client_deregister(int handle); * * @retval 0 If success. */ -int lwm2m_os_download_get(const char *host, const struct lwm2m_os_download_cfg *cfg, size_t from); +int lwm2m_os_download_get(const char *uri, const struct lwm2m_os_download_cfg *cfg, size_t from); /** * @brief Disconnect from the server. diff --git a/lib/bin/lwm2m_carrier/os/lwm2m_os.c b/lib/bin/lwm2m_carrier/os/lwm2m_os.c index 57a2145f4254..36605b69011a 100644 --- a/lib/bin/lwm2m_carrier/os/lwm2m_os.c +++ b/lib/bin/lwm2m_carrier/os/lwm2m_os.c @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include #include #include @@ -412,13 +412,21 @@ void lwm2m_os_sms_client_deregister(int handle) } /* Download client module abstractions. */ +static char dlc_buf[CONFIG_LWM2M_CARRIER_FIRMWARE_DOWNLOAD_BUF_SIZE]; +static int callback(const struct downloader_evt *event); -static struct download_client http_downloader; +static struct downloader_cfg dlc_config = { + .callback = callback, + .buf = dlc_buf, + .buf_size = sizeof(dlc_buf), +}; + +static struct downloader http_downloader; 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) +int lwm2m_os_download_get(const char *uri, const struct lwm2m_os_download_cfg *cfg, size_t from) { - struct download_client_cfg config = { + struct downloader_host_cfg config = { .sec_tag_list = cfg->sec_tag_list, .sec_tag_count = cfg->sec_tag_count, .pdn_id = cfg->pdn_id, @@ -430,31 +438,31 @@ int lwm2m_os_download_get(const char *host, const struct lwm2m_os_download_cfg * config.family = AF_INET; } - return download_client_get(&http_downloader, host, &config, NULL, from); + return downloader_get(&http_downloader, &config, uri, from); } int lwm2m_os_download_disconnect(void) { - return download_client_disconnect(&http_downloader); + return downloader_cancel(&http_downloader); } -static void download_client_evt_translate(const struct download_client_evt *event, +static void downloader_evt_translate(const struct downloader_evt *event, struct lwm2m_os_download_evt *lwm2m_os_event) { switch (event->id) { - case DOWNLOAD_CLIENT_EVT_FRAGMENT: + case DOWNLOADER_EVT_FRAGMENT: lwm2m_os_event->id = LWM2M_OS_DOWNLOAD_EVT_FRAGMENT; lwm2m_os_event->fragment.buf = event->fragment.buf; lwm2m_os_event->fragment.len = event->fragment.len; break; - case DOWNLOAD_CLIENT_EVT_DONE: + case DOWNLOADER_EVT_DONE: lwm2m_os_event->id = LWM2M_OS_DOWNLOAD_EVT_DONE; break; - case DOWNLOAD_CLIENT_EVT_ERROR: + case DOWNLOADER_EVT_ERROR: lwm2m_os_event->id = LWM2M_OS_DOWNLOAD_EVT_ERROR; lwm2m_os_event->error = event->error; break; - case DOWNLOAD_CLIENT_EVT_CLOSED: + case DOWNLOADER_EVT_STOPPED: lwm2m_os_event->id = LWM2M_OS_DOWNLOAD_EVT_CLOSED; break; default: @@ -462,11 +470,11 @@ static void download_client_evt_translate(const struct download_client_evt *even } } -static int callback(const struct download_client_evt *event) +static int callback(const struct downloader_evt *event) { struct lwm2m_os_download_evt lwm2m_os_event; - download_client_evt_translate(event, &lwm2m_os_event); + downloader_evt_translate(event, &lwm2m_os_event); return lwm2m_os_lib_callback(&lwm2m_os_event); } @@ -475,12 +483,12 @@ int lwm2m_os_download_init(lwm2m_os_download_callback_t lib_callback) { lwm2m_os_lib_callback = lib_callback; - return download_client_init(&http_downloader, callback); + return downloader_init(&http_downloader, &dlc_config); } int lwm2m_os_download_file_size_get(size_t *size) { - return download_client_file_size_get(&http_downloader, size); + return downloader_file_size_get(&http_downloader, size); } bool lwm2m_os_uicc_bootstrap_is_enabled(void) diff --git a/samples/cellular/http_update/application_update/overlay-carrier.conf b/samples/cellular/http_update/application_update/overlay-carrier.conf index 36f0fd1dabd1..52f47ec53285 100644 --- a/samples/cellular/http_update/application_update/overlay-carrier.conf +++ b/samples/cellular/http_update/application_update/overlay-carrier.conf @@ -23,7 +23,7 @@ CONFIG_AT_MONITOR_HEAP_SIZE=320 CONFIG_HEAP_MEM_POOL_SIZE=4096 # Download client for DFU -CONFIG_DOWNLOAD_CLIENT_MAX_FILENAME_SIZE=230 +CONFIG_DOWNLOADER_MAX_FILENAME_SIZE=230 # Non-volatile Storage CONFIG_NVS=y diff --git a/samples/cellular/http_update/application_update/prj.conf b/samples/cellular/http_update/application_update/prj.conf index 82a74fd6a786..6c021fc1a8ec 100644 --- a/samples/cellular/http_update/application_update/prj.conf +++ b/samples/cellular/http_update/application_update/prj.conf @@ -12,6 +12,7 @@ CONFIG_NEWLIB_LIBC=y CONFIG_NETWORKING=y CONFIG_NET_SOCKETS=y CONFIG_NET_NATIVE=n +CONFIG_NET_IPV4=y # LTE link control CONFIG_LTE_LINK_CONTROL=y @@ -30,6 +31,7 @@ CONFIG_DK_LIBRARY=y # Heap and stacks CONFIG_HEAP_MEM_POOL_SIZE=2048 CONFIG_MAIN_STACK_SIZE=8192 +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048 # Image manager CONFIG_IMG_MANAGER=y @@ -45,8 +47,8 @@ CONFIG_GPIO=y CONFIG_FOTA_DOWNLOAD=y # Download client -CONFIG_DOWNLOAD_CLIENT=y -CONFIG_DOWNLOAD_CLIENT_STACK_SIZE=4096 +CONFIG_DOWNLOADER=y +CONFIG_DOWNLOADER_STACK_SIZE=4096 # DFU Target CONFIG_DFU_TARGET=y diff --git a/samples/cellular/http_update/application_update/src/main.c b/samples/cellular/http_update/application_update/src/main.c index cbcb5b36d143..164bb54855b4 100644 --- a/samples/cellular/http_update/application_update/src/main.c +++ b/samples/cellular/http_update/application_update/src/main.c @@ -367,7 +367,6 @@ static int update_download(void) file = CONFIG_DOWNLOAD_FILE_V2; #endif - /* Functions for getting the host and file */ err = fota_download_start(CONFIG_DOWNLOAD_HOST, file, SEC_TAG, 0, 0); if (err) { app_dfu_btn_irq_enable(); diff --git a/samples/cellular/http_update/modem_delta_update/prj.conf b/samples/cellular/http_update/modem_delta_update/prj.conf index 692e4f7450bb..42af07bc4404 100644 --- a/samples/cellular/http_update/modem_delta_update/prj.conf +++ b/samples/cellular/http_update/modem_delta_update/prj.conf @@ -8,6 +8,8 @@ CONFIG_NCS_SAMPLES_DEFAULTS=y CONFIG_REBOOT=y CONFIG_NEWLIB_LIBC=y +CONFIG_NET_IPV4=y + # Network CONFIG_NETWORKING=y CONFIG_NET_SOCKETS=y @@ -30,6 +32,7 @@ CONFIG_SHELL_CMD_BUFF_SIZE=128 # Heap and stacks CONFIG_HEAP_MEM_POOL_SIZE=2048 CONFIG_MAIN_STACK_SIZE=8192 +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048 # GPIO CONFIG_GPIO=y @@ -38,8 +41,8 @@ CONFIG_GPIO=y CONFIG_FOTA_DOWNLOAD=y # Download client -CONFIG_DOWNLOAD_CLIENT=y -CONFIG_DOWNLOAD_CLIENT_STACK_SIZE=4096 +CONFIG_DOWNLOADER=y +CONFIG_DOWNLOADER_STACK_SIZE=4096 # DFU Target CONFIG_DFU_TARGET=y diff --git a/samples/cellular/http_update/modem_delta_update/src/main.c b/samples/cellular/http_update/modem_delta_update/src/main.c index 5a5b27274e44..bb3457c3500c 100644 --- a/samples/cellular/http_update/modem_delta_update/src/main.c +++ b/samples/cellular/http_update/modem_delta_update/src/main.c @@ -296,23 +296,22 @@ static int update_download(void) return false; } - if (is_test_firmware()) { - file = CONFIG_DOWNLOAD_FILE_FOTA_TEST_TO_BASE; - } else { - file = CONFIG_DOWNLOAD_FILE_BASE_TO_FOTA_TEST; - } - err = fota_download_init(fota_dl_handler); if (err) { printk("fota_download_init() failed, err %d\n", err); return err; } - /* Functions for getting the host and file */ + file = CONFIG_DOWNLOAD_FILE_BASE_TO_FOTA_TEST; + + if (is_test_firmware()) { + file = CONFIG_DOWNLOAD_FILE_FOTA_TEST_TO_BASE; + } + err = fota_download(CONFIG_DOWNLOAD_HOST, file, &sec_tag, sec_tag_count, 0, 0, - DFU_TARGET_IMAGE_TYPE_MODEM_DELTA); + DFU_TARGET_IMAGE_TYPE_MODEM_DELTA); if (err) { - printk("fota_download_any() failed, err %d\n", err); + printk("fota_download() failed, err %d\n", err); return err; } @@ -402,8 +401,10 @@ static void fota_work_cb(struct k_work *work) break; case UPDATE_APPLY: printk("Applying firmware update. This can take a while.\n"); + lte_lc_power_off(); /* Re-initialize the modem to apply the update. */ + err = nrf_modem_lib_shutdown(); if (err) { printk("Failed to shutdown modem, err %d\n", err); diff --git a/samples/cellular/http_update/modem_full_update/prj.conf b/samples/cellular/http_update/modem_full_update/prj.conf index 78b182f54532..c5f814c52ac6 100644 --- a/samples/cellular/http_update/modem_full_update/prj.conf +++ b/samples/cellular/http_update/modem_full_update/prj.conf @@ -8,6 +8,8 @@ CONFIG_NCS_SAMPLES_DEFAULTS=y CONFIG_REBOOT=y CONFIG_NEWLIB_LIBC=y +CONFIG_NET_IPV4=y + # Network CONFIG_NETWORKING=y CONFIG_NET_SOCKETS=y @@ -44,8 +46,8 @@ CONFIG_GPIO=y CONFIG_FOTA_DOWNLOAD=y # Download client -CONFIG_DOWNLOAD_CLIENT=y -CONFIG_DOWNLOAD_CLIENT_STACK_SIZE=4096 +CONFIG_DOWNLOADER=y +CONFIG_DOWNLOADER_STACK_SIZE=4096 # DFU Target CONFIG_DFU_TARGET=y diff --git a/samples/cellular/http_update/modem_full_update/src/main.c b/samples/cellular/http_update/modem_full_update/src/main.c index f026181879b5..b54a350f1036 100644 --- a/samples/cellular/http_update/modem_full_update/src/main.c +++ b/samples/cellular/http_update/modem_full_update/src/main.c @@ -277,38 +277,47 @@ static int apply_state(enum fota_state new_state) return 0; } -static int apply_fmfu_from_ext_flash(bool valid_init) +static int apply_fmfu_from_ext_flash(void) { int err; printk("Applying full modem firmware update from external flash\n"); - if (valid_init) { - err = nrf_modem_lib_shutdown(); - if (err != 0) { - printk("nrf_modem_lib_shutdown() failed: %d\n", err); - return err; - } + err = lte_lc_offline(); + if (err) { + printk("Failed to disconnect LTE."); + return err; + } + + err = nrf_modem_lib_shutdown(); + if (err != 0) { + printk("nrf_modem_lib_shutdown() failed: %d\n", err); + return err; } + err = nrf_modem_lib_bootloader_init(); if (err != 0) { printk("nrf_modem_lib_bootloader_init() failed: %d\n", err); - return err; + goto reinit; } err = fmfu_fdev_load(fmfu_buf, sizeof(fmfu_buf), flash_dev, 0); if (err != 0) { printk("fmfu_fdev_load failed: %d\n", err); - return err; + nrf_modem_lib_shutdown(); + goto reinit; } err = nrf_modem_lib_shutdown(); if (err != 0) { printk("nrf_modem_lib_shutdown() failed: %d\n", err); - return err; + goto reinit; } + printk("Modem firmware update completed, reinitialiing in normal mode.\n"); + +reinit: err = nrf_modem_lib_init(); if (err) { printk("Modem library initialization failed, err %d\n", err); @@ -323,11 +332,9 @@ static int apply_fmfu_from_ext_flash(bool valid_init) } } - printk("Modem firmware update completed.\n"); - current_version_display(); - return 0; + return err; } #if defined(CONFIG_USE_HTTPS) @@ -406,7 +413,8 @@ void fota_dl_handler(const struct fota_download_evt *evt) switch (evt->id) { case FOTA_DOWNLOAD_EVT_ERROR: printk("Received error from fota_download\n"); - /* Fallthrough */ + apply_state(CONNECTED); + break; case FOTA_DOWNLOAD_EVT_FINISHED: apply_state(UPDATE_PENDING); break; @@ -451,15 +459,14 @@ static int update_download(void) return err; } + file = CONFIG_DOWNLOAD_MODEM_0_FILE; + if (current_version_is_0()) { file = CONFIG_DOWNLOAD_MODEM_1_FILE; - } else { - file = CONFIG_DOWNLOAD_MODEM_0_FILE; } - /* Functions for getting the host and file */ err = fota_download(CONFIG_DOWNLOAD_HOST, file, &sec_tag, sec_tag_count, 0, 0, - DFU_TARGET_IMAGE_TYPE_FULL_MODEM); + DFU_TARGET_IMAGE_TYPE_FULL_MODEM); if (err != 0) { printk("fota_download() failed, err %d\n", err); return err; @@ -519,7 +526,7 @@ static void fota_work_cb(struct k_work *work) } break; case UPDATE_APPLY: - err = apply_fmfu_from_ext_flash(true); + err = apply_fmfu_from_ext_flash(); if (err) { printk("FMFU failed, err %d\n", err); } diff --git a/samples/cellular/location/overlay-pgps.conf b/samples/cellular/location/overlay-pgps.conf index cc116f12d89c..a6defeca321d 100644 --- a/samples/cellular/location/overlay-pgps.conf +++ b/samples/cellular/location/overlay-pgps.conf @@ -24,4 +24,4 @@ CONFIG_MPU_ALLOW_FLASH_WRITE=y CONFIG_HEAP_MEM_POOL_SIZE=8192 # Download client library stack size needs to be increased with P-GPS -CONFIG_DOWNLOAD_CLIENT_STACK_SIZE=1280 +CONFIG_DOWNLOADER_STACK_SIZE=1280 diff --git a/samples/cellular/lwm2m_carrier/prj.conf b/samples/cellular/lwm2m_carrier/prj.conf index 82e5c8fcb00d..2658091eb756 100644 --- a/samples/cellular/lwm2m_carrier/prj.conf +++ b/samples/cellular/lwm2m_carrier/prj.conf @@ -23,8 +23,8 @@ CONFIG_PDN=y CONFIG_SMS=y # Download client for DFU -CONFIG_DOWNLOAD_CLIENT=y -CONFIG_DOWNLOAD_CLIENT_MAX_FILENAME_SIZE=230 +CONFIG_DOWNLOADER=y +CONFIG_DOWNLOADER_MAX_FILENAME_SIZE=230 # AT Monitor CONFIG_AT_MONITOR=y diff --git a/samples/cellular/lwm2m_client/prj.conf b/samples/cellular/lwm2m_client/prj.conf index e6a3a9831fb2..3dee8af0e080 100644 --- a/samples/cellular/lwm2m_client/prj.conf +++ b/samples/cellular/lwm2m_client/prj.conf @@ -79,9 +79,8 @@ CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048 CONFIG_AT_MONITOR_HEAP_SIZE=512 # Allow FOTA downloads using download-client -CONFIG_DOWNLOAD_CLIENT=y -CONFIG_DOWNLOAD_CLIENT_STACK_SIZE=4096 -CONFIG_DOWNLOAD_CLIENT_HTTP_FRAG_SIZE_1024=y +CONFIG_DOWNLOADER=y +CONFIG_DOWNLOADER_STACK_SIZE=4096 CONFIG_FOTA_DOWNLOAD=y # Application version diff --git a/samples/cellular/lwm2m_client/sample_description.rst b/samples/cellular/lwm2m_client/sample_description.rst index 1f2b4d7844be..2610ce2df6ba 100644 --- a/samples/cellular/lwm2m_client/sample_description.rst +++ b/samples/cellular/lwm2m_client/sample_description.rst @@ -834,7 +834,7 @@ This sample application uses the following |NCS| libraries and drivers: * :ref:`lib_dfu_target` * :ref:`lib_fmfu_fdev` * :ref:`lib_fota_download` -* :ref:`lib_download_client` +* :ref:`lib_downloader` It uses the following `sdk-nrfxlib`_ library: diff --git a/samples/cellular/modem_shell/overlay-carrier.conf b/samples/cellular/modem_shell/overlay-carrier.conf index b7aa9e7df79d..bde253bb4dc4 100644 --- a/samples/cellular/modem_shell/overlay-carrier.conf +++ b/samples/cellular/modem_shell/overlay-carrier.conf @@ -16,7 +16,7 @@ CONFIG_MPU_ALLOW_FLASH_WRITE=y CONFIG_NVS=y CONFIG_NVS_LOG_LEVEL_OFF=y -CONFIG_DOWNLOAD_CLIENT=y +CONFIG_DOWNLOADER=y CONFIG_MODEM_KEY_MGMT=y diff --git a/samples/cellular/modem_shell/overlay-modem_fota_full.conf b/samples/cellular/modem_shell/overlay-modem_fota_full.conf index 59b537a33ce8..45a101218713 100644 --- a/samples/cellular/modem_shell/overlay-modem_fota_full.conf +++ b/samples/cellular/modem_shell/overlay-modem_fota_full.conf @@ -15,5 +15,5 @@ CONFIG_ZCBOR=y CONFIG_STREAM_FLASH_ERASE=y CONFIG_STREAM_FLASH=y CONFIG_FMFU_FDEV=y -CONFIG_DOWNLOAD_CLIENT_STACK_SIZE=2560 +CONFIG_DOWNLOADER_STACK_SIZE=2560 CONFIG_MBEDTLS_LEGACY_CRYPTO_C=y diff --git a/samples/cellular/modem_shell/prj.conf b/samples/cellular/modem_shell/prj.conf index ace599ba5ce8..33f2c05f4e63 100644 --- a/samples/cellular/modem_shell/prj.conf +++ b/samples/cellular/modem_shell/prj.conf @@ -145,7 +145,8 @@ CONFIG_FLASH=y CONFIG_REBOOT=y CONFIG_DFU_TARGET=y CONFIG_FOTA_DOWNLOAD=y -CONFIG_DOWNLOAD_CLIENT=y +CONFIG_DOWNLOADER=y +CONFIG_DOWNLOADER_SHELL=y # BOOTLOADER_MCUBOOT reduces usable flash size by half so it's disabled by default # This means application FOTA is disabled. Modem FOTA works without these. CONFIG_BOOTLOADER_MCUBOOT=n diff --git a/samples/cellular/modem_shell/src/fota/fota_shell.c b/samples/cellular/modem_shell/src/fota/fota_shell.c index 8dcf968e875f..0e24179a565e 100644 --- a/samples/cellular/modem_shell/src/fota/fota_shell.c +++ b/samples/cellular/modem_shell/src/fota/fota_shell.c @@ -33,8 +33,7 @@ static int cmd_fota_download(const struct shell *shell, size_t argc, } else if (strcmp(argv[1], "au") == 0) { fota_server = fota_server_au; } else { - mosh_error("FOTA: Unknown server: %s", argv[1]); - return -EINVAL; + fota_server = argv[1]; } mosh_print("FOTA: Starting download..."); @@ -56,7 +55,8 @@ SHELL_STATIC_SUBCMD_SET_CREATE( sub_fota, SHELL_CMD_ARG( download, NULL, - " \nDownload and install a FOTA update. Available servers are \"eu\", \"us\", \"jpn\" and \"au\".", + " \nDownload and install a FOTA update. " + "Available servers are \"eu\", \"us\", \"jpn\" and \"au\"", cmd_fota_download, 3, 0), SHELL_SUBCMD_SET_END); diff --git a/samples/cellular/modem_shell/src/gnss/gnss.c b/samples/cellular/modem_shell/src/gnss/gnss.c index 467410c0d6db..96800569ae75 100644 --- a/samples/cellular/modem_shell/src/gnss/gnss.c +++ b/samples/cellular/modem_shell/src/gnss/gnss.c @@ -941,17 +941,20 @@ static void get_pgps_data_work_fn(struct k_work *work) err = nrf_cloud_rest_pgps_data_get(&rest_ctx, &request); #elif defined(CONFIG_NRF_CLOUD_COAP) struct nrf_cloud_pgps_result file_location = {0}; + static char host[64]; static char path[128]; memset(host, 0, sizeof(host)); memset(path, 0, sizeof(path)); + file_location.host = host; file_location.host_sz = sizeof(host); file_location.path = path; file_location.path_sz = sizeof(path); err = nrf_cloud_coap_pgps_url_get(&request, &file_location); + #endif if (err) { mosh_error("GNSS: Failed to get P-GPS data, error: %d", err); diff --git a/samples/cellular/nrf_cloud_multi_service/Kconfig b/samples/cellular/nrf_cloud_multi_service/Kconfig index e22f5e414eb4..acb98381f0c1 100644 --- a/samples/cellular/nrf_cloud_multi_service/Kconfig +++ b/samples/cellular/nrf_cloud_multi_service/Kconfig @@ -260,7 +260,7 @@ menuconfig COAP_FOTA select FOTA_DOWNLOAD_PROGRESS_EVT select IMG_ERASE_PROGRESSIVELY select DFU_TARGET - select DOWNLOAD_CLIENT + select DOWNLOADER select REBOOT select CJSON_LIB select SETTINGS diff --git a/samples/cellular/nrf_cloud_multi_service/prj.conf b/samples/cellular/nrf_cloud_multi_service/prj.conf index 67116b6f66aa..72eba8112941 100644 --- a/samples/cellular/nrf_cloud_multi_service/prj.conf +++ b/samples/cellular/nrf_cloud_multi_service/prj.conf @@ -115,11 +115,9 @@ CONFIG_SETTINGS_FCB=y CONFIG_FCB=y # Download Client - used by FOTA and PGPS -CONFIG_DOWNLOAD_CLIENT=y -CONFIG_DOWNLOAD_CLIENT_HTTP_FRAG_SIZE_1024=y -CONFIG_DOWNLOAD_CLIENT_STACK_SIZE=4096 -CONFIG_DOWNLOAD_CLIENT_BUF_SIZE=2300 -CONFIG_DOWNLOAD_CLIENT_MAX_HOSTNAME_SIZE=128 +CONFIG_DOWNLOADER=y +CONFIG_DOWNLOADER_STACK_SIZE=4096 +CONFIG_DOWNLOADER_MAX_HOSTNAME_SIZE=128 # Flash - Used by FOTA and PGPS CONFIG_FLASH=y diff --git a/samples/cellular/nrf_cloud_rest_fota/prj.conf b/samples/cellular/nrf_cloud_rest_fota/prj.conf index c0a47f95d996..81559cff9d2c 100644 --- a/samples/cellular/nrf_cloud_rest_fota/prj.conf +++ b/samples/cellular/nrf_cloud_rest_fota/prj.conf @@ -19,7 +19,7 @@ CONFIG_NRF_CLOUD_FOTA_POLL=y CONFIG_FOTA_DOWNLOAD=y CONFIG_FOTA_DOWNLOAD_PROGRESS_EVT=y CONFIG_DFU_TARGET=y -CONFIG_DOWNLOAD_CLIENT=y +CONFIG_DOWNLOADER=y # MCUBOOT CONFIG_BOOTLOADER_MCUBOOT=y diff --git a/samples/net/aws_iot/boards/nrf7002dk_nrf5340_cpuapp_ns.conf b/samples/net/aws_iot/boards/nrf7002dk_nrf5340_cpuapp_ns.conf index 3251b360651a..619550810638 100644 --- a/samples/net/aws_iot/boards/nrf7002dk_nrf5340_cpuapp_ns.conf +++ b/samples/net/aws_iot/boards/nrf7002dk_nrf5340_cpuapp_ns.conf @@ -97,10 +97,8 @@ CONFIG_FOTA_DOWNLOAD=y CONFIG_DFU_TARGET=y # Download client (needed by AWS FOTA) -CONFIG_DOWNLOAD_CLIENT=y -CONFIG_DOWNLOAD_CLIENT_STACK_SIZE=4096 -CONFIG_DOWNLOAD_CLIENT_BUF_SIZE=4096 -CONFIG_DOWNLOAD_CLIENT_HTTP_FRAG_SIZE_4096=y +CONFIG_DOWNLOADER=y +CONFIG_DOWNLOADER_STACK_SIZE=4096 # TLS credentials CONFIG_TLS_CREDENTIALS_BACKEND_PROTECTED_STORAGE=y diff --git a/samples/net/aws_iot/boards/nrf9151dk_nrf9151_ns.conf b/samples/net/aws_iot/boards/nrf9151dk_nrf9151_ns.conf index a4996fe39ef7..7edce8fef430 100644 --- a/samples/net/aws_iot/boards/nrf9151dk_nrf9151_ns.conf +++ b/samples/net/aws_iot/boards/nrf9151dk_nrf9151_ns.conf @@ -53,5 +53,5 @@ CONFIG_FOTA_DOWNLOAD=y CONFIG_DFU_TARGET=y # Download client (needed by AWS FOTA) -CONFIG_DOWNLOAD_CLIENT=y -CONFIG_DOWNLOAD_CLIENT_STACK_SIZE=4096 +CONFIG_DOWNLOADER=y +CONFIG_DOWNLOADER_STACK_SIZE=4096 diff --git a/samples/net/aws_iot/boards/nrf9160dk_nrf9160_ns.conf b/samples/net/aws_iot/boards/nrf9160dk_nrf9160_ns.conf index 131eed58a1de..739444cb4422 100644 --- a/samples/net/aws_iot/boards/nrf9160dk_nrf9160_ns.conf +++ b/samples/net/aws_iot/boards/nrf9160dk_nrf9160_ns.conf @@ -53,5 +53,5 @@ CONFIG_FOTA_DOWNLOAD=y CONFIG_DFU_TARGET=y # Download client (needed by AWS FOTA) -CONFIG_DOWNLOAD_CLIENT=y -CONFIG_DOWNLOAD_CLIENT_STACK_SIZE=4096 +CONFIG_DOWNLOADER=y +CONFIG_DOWNLOADER_STACK_SIZE=4096 diff --git a/samples/net/aws_iot/boards/nrf9161dk_nrf9161_ns.conf b/samples/net/aws_iot/boards/nrf9161dk_nrf9161_ns.conf index 7772d1e11098..93c2c3b655f4 100644 --- a/samples/net/aws_iot/boards/nrf9161dk_nrf9161_ns.conf +++ b/samples/net/aws_iot/boards/nrf9161dk_nrf9161_ns.conf @@ -53,5 +53,5 @@ CONFIG_FOTA_DOWNLOAD=y CONFIG_DFU_TARGET=y # Download client (needed by AWS FOTA) -CONFIG_DOWNLOAD_CLIENT=y -CONFIG_DOWNLOAD_CLIENT_STACK_SIZE=4096 +CONFIG_DOWNLOADER=y +CONFIG_DOWNLOADER_STACK_SIZE=4096 diff --git a/samples/net/aws_iot/boards/thingy91_nrf9160_ns.conf b/samples/net/aws_iot/boards/thingy91_nrf9160_ns.conf index fd574ed0e498..a8906323e49b 100644 --- a/samples/net/aws_iot/boards/thingy91_nrf9160_ns.conf +++ b/samples/net/aws_iot/boards/thingy91_nrf9160_ns.conf @@ -53,5 +53,5 @@ CONFIG_FOTA_DOWNLOAD=y CONFIG_DFU_TARGET=y # Download client (needed by AWS FOTA) -CONFIG_DOWNLOAD_CLIENT=y -CONFIG_DOWNLOAD_CLIENT_STACK_SIZE=4096 +CONFIG_DOWNLOADER=y +CONFIG_DOWNLOADER_STACK_SIZE=4096 diff --git a/samples/net/aws_iot/boards/thingy91x_nrf9151_ns.conf b/samples/net/aws_iot/boards/thingy91x_nrf9151_ns.conf index 96fa4982bc78..5ee1206c6079 100644 --- a/samples/net/aws_iot/boards/thingy91x_nrf9151_ns.conf +++ b/samples/net/aws_iot/boards/thingy91x_nrf9151_ns.conf @@ -53,5 +53,5 @@ CONFIG_FOTA_DOWNLOAD=y CONFIG_DFU_TARGET=y # Download client (needed by AWS FOTA) -CONFIG_DOWNLOAD_CLIENT=y -CONFIG_DOWNLOAD_CLIENT_STACK_SIZE=4096 +CONFIG_DOWNLOADER=y +CONFIG_DOWNLOADER_STACK_SIZE=4096 diff --git a/samples/net/azure_iot_hub/README.rst b/samples/net/azure_iot_hub/README.rst index 0b547a26d7fc..63a6370d51e1 100644 --- a/samples/net/azure_iot_hub/README.rst +++ b/samples/net/azure_iot_hub/README.rst @@ -224,17 +224,17 @@ If a new FOTA update is initiated, the console output is like this: azure_iot_hub_sample: AZURE_IOT_HUB_EVT_TWIN_RESULT_SUCCESS, ID: 140 azure_fota: Attempting to download firmware (version 'v0.0.2-dev') from example.com/firmware/app_update.bin - download_client: Downloading: firmware/app_update.bin [0] + downloader: Downloading: firmware/app_update.bin [0] azure_iot_hub_sample: AZURE_IOT_HUB_EVT_FOTA_START azure_iot_hub_sample: AZURE_IOT_HUB_EVT_TWIN_DESIRED_RECEIVED - download_client: Setting up TLS credentials, sec tag count 1 - download_client: Connecting to example.com + downloader: Setting up TLS credentials, sec tag count 1 + downloader: Connecting to example.com azure_iot_hub_sample: AZURE_IOT_HUB_EVT_TWIN_RESULT_SUCCESS, ID: 190 azure_iot_hub_sample: AZURE_IOT_HUB_EVT_TWIN_RESULT_SUCCESS, ID: 190 - download_client: Downloaded 1800/674416 bytes (0%) + downloader: Downloaded 1800/674416 bytes (0%) ... - download_client: Downloaded 674416/674416 bytes (100%) - download_client: Download complete + downloader: Downloaded 674416/674416 bytes (100%) + downloader: Download complete dfu_target_mcuboot: MCUBoot image-0 upgrade scheduled. Reset device to apply azure_iot_hub_sample: AZURE_IOT_HUB_EVT_FOTA_DONE azure_iot_hub_sample: The device will reboot in 5 seconds to apply update diff --git a/samples/net/azure_iot_hub/boards/nrf7002dk_nrf5340_cpuapp_ns.conf b/samples/net/azure_iot_hub/boards/nrf7002dk_nrf5340_cpuapp_ns.conf index 68dc74ed2614..6c86a1fb3f29 100644 --- a/samples/net/azure_iot_hub/boards/nrf7002dk_nrf5340_cpuapp_ns.conf +++ b/samples/net/azure_iot_hub/boards/nrf7002dk_nrf5340_cpuapp_ns.conf @@ -87,10 +87,8 @@ CONFIG_MCUBOOT_IMG_MANAGER=y CONFIG_IMG_MANAGER=y CONFIG_STREAM_FLASH=y CONFIG_IMG_ERASE_PROGRESSIVELY=y -CONFIG_DOWNLOAD_CLIENT=y -CONFIG_DOWNLOAD_CLIENT_STACK_SIZE=4096 -CONFIG_DOWNLOAD_CLIENT_BUF_SIZE=4096 -CONFIG_DOWNLOAD_CLIENT_HTTP_FRAG_SIZE_4096=y +CONFIG_DOWNLOADER=y +CONFIG_DOWNLOADER_STACK_SIZE=4096 # Enable external flash to host MCUBoot secondary partition CONFIG_SPI=y diff --git a/samples/net/azure_iot_hub/boards/nrf9151dk_nrf9151_ns.conf b/samples/net/azure_iot_hub/boards/nrf9151dk_nrf9151_ns.conf index b9c8237eecd3..5e3b80998bf5 100644 --- a/samples/net/azure_iot_hub/boards/nrf9151dk_nrf9151_ns.conf +++ b/samples/net/azure_iot_hub/boards/nrf9151dk_nrf9151_ns.conf @@ -39,5 +39,5 @@ CONFIG_STREAM_FLASH=y CONFIG_FLASH_MAP=y CONFIG_FLASH=y CONFIG_IMG_ERASE_PROGRESSIVELY=y -CONFIG_DOWNLOAD_CLIENT=y -CONFIG_DOWNLOAD_CLIENT_STACK_SIZE=4096 +CONFIG_DOWNLOADER=y +CONFIG_DOWNLOADER_STACK_SIZE=4096 diff --git a/samples/net/azure_iot_hub/boards/nrf9160dk_nrf9160_ns.conf b/samples/net/azure_iot_hub/boards/nrf9160dk_nrf9160_ns.conf index 8c90510ece39..8847cbfade9c 100644 --- a/samples/net/azure_iot_hub/boards/nrf9160dk_nrf9160_ns.conf +++ b/samples/net/azure_iot_hub/boards/nrf9160dk_nrf9160_ns.conf @@ -39,5 +39,5 @@ CONFIG_STREAM_FLASH=y CONFIG_FLASH_MAP=y CONFIG_FLASH=y CONFIG_IMG_ERASE_PROGRESSIVELY=y -CONFIG_DOWNLOAD_CLIENT=y -CONFIG_DOWNLOAD_CLIENT_STACK_SIZE=4096 +CONFIG_DOWNLOADER=y +CONFIG_DOWNLOADER_STACK_SIZE=4096 diff --git a/samples/net/azure_iot_hub/boards/nrf9161dk_nrf9161_ns.conf b/samples/net/azure_iot_hub/boards/nrf9161dk_nrf9161_ns.conf index 8c90510ece39..8847cbfade9c 100644 --- a/samples/net/azure_iot_hub/boards/nrf9161dk_nrf9161_ns.conf +++ b/samples/net/azure_iot_hub/boards/nrf9161dk_nrf9161_ns.conf @@ -39,5 +39,5 @@ CONFIG_STREAM_FLASH=y CONFIG_FLASH_MAP=y CONFIG_FLASH=y CONFIG_IMG_ERASE_PROGRESSIVELY=y -CONFIG_DOWNLOAD_CLIENT=y -CONFIG_DOWNLOAD_CLIENT_STACK_SIZE=4096 +CONFIG_DOWNLOADER=y +CONFIG_DOWNLOADER_STACK_SIZE=4096 diff --git a/samples/net/download/README.rst b/samples/net/download/README.rst index a9ccb6688de1..47e6dcd6d7df 100644 --- a/samples/net/download/README.rst +++ b/samples/net/download/README.rst @@ -8,7 +8,7 @@ Download client :depth: 2 The Download client sample demonstrates how to download a file from an HTTP or a CoAP server, with optional TLS or DTLS. -It uses the :ref:`lib_download_client` library. +It uses the :ref:`lib_downloader` library. .. |wifi| replace:: Wi-Fi® @@ -35,7 +35,7 @@ The sample then performs the following actions: 1. Establishes a connection to the network #. Optionally sets up the secure socket options -#. Uses the :ref:`lib_download_client` library to download a file from an HTTP server. +#. Uses the :ref:`lib_downloader` library to download a file from an HTTP server. Downloading from a CoAP server diff --git a/samples/net/download/prj.conf b/samples/net/download/prj.conf index 330d0f00479c..fe1e131b9cf2 100644 --- a/samples/net/download/prj.conf +++ b/samples/net/download/prj.conf @@ -4,8 +4,8 @@ # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # -CONFIG_DOWNLOAD_CLIENT=y -CONFIG_DOWNLOAD_CLIENT_STACK_SIZE=4096 +CONFIG_DOWNLOADER=y +CONFIG_DOWNLOADER_STACK_SIZE=4096 # Networking CONFIG_NETWORKING=y diff --git a/samples/net/download/sample.yaml b/samples/net/download/sample.yaml index 3967f89e0fa4..d28def5fb33e 100644 --- a/samples/net/download/sample.yaml +++ b/samples/net/download/sample.yaml @@ -1,7 +1,7 @@ sample: name: Download sample tests: - sample.net.download_client: + sample.net.downloader: sysbuild: true build_only: true integration_platforms: @@ -13,13 +13,13 @@ tests: - nrf7002dk/nrf5340/cpuapp/ns - native_sim tags: ci_build sysbuild ci_samples_net - sample.net.download_client.ci: + sample.net.downloader.ci: sysbuild: true build_only: true extra_configs: - CONFIG_SHELL=y - CONFIG_COAP=y - - CONFIG_DOWNLOAD_CLIENT_SHELL=y + - CONFIG_DOWNLOADER_SHELL=y - CONFIG_SAMPLE_COMPUTE_HASH=y integration_platforms: - nrf9160dk/nrf9160/ns @@ -32,7 +32,7 @@ tests: - nrf9151dk/nrf9151/ns - nrf7002dk/nrf5340/cpuapp/ns tags: ci_build sysbuild ci_samples_net - sample.net.download_client.nrf54l15.wifi: + sample.net.downloader.nrf54l15.wifi: sysbuild: true tags: ci_build sysbuild ci_samples_net build_only: true diff --git a/samples/net/download/src/main.c b/samples/net/download/src/main.c index 0088d796607c..0fb7a42bcf93 100644 --- a/samples/net/download/src/main.c +++ b/samples/net/download/src/main.c @@ -11,7 +11,11 @@ #include #include #include -#include +#include + +#include +LOG_MODULE_REGISTER(download, LOG_LEVEL_INF); + #if CONFIG_MODEM_KEY_MGMT #include @@ -49,13 +53,22 @@ static int sec_tag_list[] = { SEC_TAG }; BUILD_ASSERT(sizeof(cert) < KB(4), "Certificate too large"); #endif -static struct download_client downloader; -static struct download_client_cfg config = { +static char dlc_buf[2048]; + +static int callback(const struct downloader_evt *event); + +static struct downloader downloader; +static struct downloader_cfg config = { + .callback = callback, + .buf = dlc_buf, + .buf_size = sizeof(dlc_buf), +}; +static struct downloader_host_cfg host_config = { #if CONFIG_SAMPLE_SECURE_SOCKET .sec_tag_list = sec_tag_list, .sec_tag_count = ARRAY_SIZE(sec_tag_list), - .set_tls_hostname = true, #endif + .range_override = 0, }; #if CONFIG_SAMPLE_COMPUTE_HASH @@ -162,21 +175,19 @@ static void connectivity_event_handler(struct net_mgmt_event_callback *cb, static void progress_print(size_t downloaded, size_t file_size) { + static int prev_percent; const int percent = (downloaded * 100) / file_size; - size_t lpad = (percent * PROGRESS_WIDTH) / 100; - size_t rpad = PROGRESS_WIDTH - lpad; - printk("\r[ %3d%% ] |", percent); - for (size_t i = 0; i < lpad; i++) { - printk("="); - } - for (size_t i = 0; i < rpad; i++) { - printk(" "); + if (percent == prev_percent) { + return; } - printk("| (%d/%d bytes)", downloaded, file_size); + + prev_percent = percent; + + printk("[ %3d%% ] (%d/%d bytes)\r", percent, downloaded, file_size); } -static int callback(const struct download_client_evt *event) +static int callback(const struct downloader_evt *event) { static size_t downloaded; static size_t file_size; @@ -184,17 +195,17 @@ static int callback(const struct download_client_evt *event) int64_t ms_elapsed; if (downloaded == 0) { - download_client_file_size_get(&downloader, &file_size); + downloader_file_size_get(&downloader, &file_size); downloaded += STARTING_OFFSET; } switch (event->id) { - case DOWNLOAD_CLIENT_EVT_FRAGMENT: + case DOWNLOADER_EVT_FRAGMENT: downloaded += event->fragment.len; if (file_size) { progress_print(downloaded, file_size); } else { - printk("\r[ %d bytes ] ", downloaded); + printk("\r[ %d bytes ]\n", downloaded); } #if CONFIG_SAMPLE_COMPUTE_HASH @@ -203,7 +214,7 @@ static int callback(const struct download_client_evt *event) #endif return 0; - case DOWNLOAD_CLIENT_EVT_DONE: + case DOWNLOADER_EVT_DONE: ms_elapsed = k_uptime_delta(&ref_time); speed = ((float)file_size / ms_elapsed) * MSEC_PER_SEC; printk("\nDownload completed in %lld ms @ %d bytes per sec, total %d bytes\n", @@ -233,7 +244,7 @@ static int callback(const struct download_client_evt *event) printk("Bye\n"); return 0; - case DOWNLOAD_CLIENT_EVT_ERROR: + case DOWNLOADER_EVT_ERROR: printk("Error %d during download\n", event->error); if (event->error == -ECONNRESET) { /* With ECONNRESET, allow library to attempt a reconnect by returning 0 */ @@ -244,9 +255,12 @@ static int callback(const struct download_client_evt *event) return -1; } break; - case DOWNLOAD_CLIENT_EVT_CLOSED: + case DOWNLOADER_EVT_STOPPED: printk("Socket closed\n"); break; + case DOWNLOADER_EVT_DEINITIALIZED: + printk("Client deinitialized\n"); + break; } return 0; @@ -302,7 +316,7 @@ int main(void) printk("Network connected\n"); - err = download_client_init(&downloader, callback); + err = downloader_init(&downloader, &config); if (err) { printk("Failed to initialize the client, err %d", err); return 0; @@ -315,7 +329,7 @@ int main(void) ref_time = k_uptime_get(); - err = download_client_get(&downloader, URL, &config, URL, STARTING_OFFSET); + err = downloader_get(&downloader, &host_config, URL, STARTING_OFFSET); if (err) { printk("Failed to start the downloader, err %d", err); return 0; diff --git a/scripts/quarantine_integration.yaml b/scripts/quarantine_integration.yaml index 9bab59dfce79..d0223fdd20c0 100644 --- a/scripts/quarantine_integration.yaml +++ b/scripts/quarantine_integration.yaml @@ -1100,8 +1100,8 @@ - scenarios: - sample.net.https_client - sample.net.https_client.lte.tfm-mbedtls - - sample.net.download_client - - sample.net.download_client.ci + - sample.net.downloader + - sample.net.downloader.ci platforms: - nrf9161dk/nrf9161/ns comment: "Configurations excluded to limit resources usage in integration builds" diff --git a/subsys/dfu/dfu_target/Kconfig b/subsys/dfu/dfu_target/Kconfig index cbdee019e5a5..b01f047151f8 100644 --- a/subsys/dfu/dfu_target/Kconfig +++ b/subsys/dfu/dfu_target/Kconfig @@ -68,7 +68,6 @@ config DFU_TARGET_STREAM_SAVE_PROGRESS config DFU_TARGET_MODEM_DELTA bool "Modem delta update support" - imply DOWNLOAD_CLIENT_RANGE_REQUESTS default y depends on SOC_SERIES_NRF91X help diff --git a/subsys/net/lib/CMakeLists.txt b/subsys/net/lib/CMakeLists.txt index c58764ef3835..3f141cd53f4f 100644 --- a/subsys/net/lib/CMakeLists.txt +++ b/subsys/net/lib/CMakeLists.txt @@ -16,13 +16,14 @@ if (DEFINED CONFIG_NRF_CLOUD_MQTT OR endif() add_subdirectory_ifdef(CONFIG_REST_CLIENT rest_client) -add_subdirectory_ifdef(CONFIG_DOWNLOAD_CLIENT download_client) -add_subdirectory_ifdef(CONFIG_FOTA_DOWNLOAD fota_download) add_subdirectory_ifdef(CONFIG_AWS_JOBS aws_jobs) add_subdirectory_ifdef(CONFIG_AWS_FOTA aws_fota) add_subdirectory_ifdef(CONFIG_AWS_IOT aws_iot) add_subdirectory_ifdef(CONFIG_AZURE_FOTA azure_fota) add_subdirectory_ifdef(CONFIG_AZURE_IOT_HUB azure_iot_hub) +add_subdirectory_ifdef(CONFIG_DOWNLOAD_CLIENT download_client) +add_subdirectory_ifdef(CONFIG_DOWNLOADER downloader) +add_subdirectory_ifdef(CONFIG_FOTA_DOWNLOAD fota_download) add_subdirectory_ifdef(CONFIG_ZZHC zzhc) add_subdirectory_ifdef(CONFIG_ICAL_PARSER icalendar_parser) add_subdirectory_ifdef(CONFIG_FTP_CLIENT ftp_client) diff --git a/subsys/net/lib/Kconfig b/subsys/net/lib/Kconfig index 6a653344a719..0d040778ad90 100644 --- a/subsys/net/lib/Kconfig +++ b/subsys/net/lib/Kconfig @@ -29,6 +29,7 @@ endchoice rsource "nrf_cloud/Kconfig" rsource "rest_client/Kconfig" rsource "download_client/Kconfig" +rsource "downloader/Kconfig" rsource "fota_download/Kconfig" rsource "aws_iot/Kconfig" rsource "aws_jobs/Kconfig" diff --git a/subsys/net/lib/aws_fota/src/aws_fota.c b/subsys/net/lib/aws_fota/src/aws_fota.c index 8610724bdb78..c4229fcd229d 100644 --- a/subsys/net/lib/aws_fota/src/aws_fota.c +++ b/subsys/net/lib/aws_fota/src/aws_fota.c @@ -61,8 +61,8 @@ static uint8_t get_topic[AWS_JOBS_TOPIC_MAX_LEN]; /* Allocated buffers for keeping hostname, json payload and file_path. */ static uint8_t payload_buf[CONFIG_AWS_FOTA_PAYLOAD_SIZE]; static uint8_t protocol[sizeof("https://")]; -static uint8_t hostname[CONFIG_DOWNLOAD_CLIENT_MAX_HOSTNAME_SIZE]; -static uint8_t file_path[CONFIG_DOWNLOAD_CLIENT_MAX_FILENAME_SIZE]; +static uint8_t hostname[CONFIG_DOWNLOADER_MAX_HOSTNAME_SIZE]; +static uint8_t file_path[CONFIG_DOWNLOADER_MAX_FILENAME_SIZE]; /* Allocated buffer used to keep track the job ID currently being handled by the library. */ static uint8_t job_id_handling[AWS_JOBS_JOB_ID_MAX_LEN] = AWS_JOB_ID_DEFAULT; diff --git a/subsys/net/lib/azure_fota/azure_fota.c b/subsys/net/lib/azure_fota/azure_fota.c index fb31921e3881..7afc9af501d3 100644 --- a/subsys/net/lib/azure_fota/azure_fota.c +++ b/subsys/net/lib/azure_fota/azure_fota.c @@ -72,8 +72,8 @@ static enum fota_status { } current_status = REP_STATUS_CURRENT; static struct fota_object { - char host[CONFIG_DOWNLOAD_CLIENT_MAX_HOSTNAME_SIZE]; - char path[CONFIG_DOWNLOAD_CLIENT_MAX_FILENAME_SIZE]; + char host[CONFIG_DOWNLOADER_MAX_HOSTNAME_SIZE]; + char path[CONFIG_DOWNLOADER_MAX_FILENAME_SIZE]; char version[CONFIG_AZURE_FOTA_VERSION_MAX_LEN]; char job_id[CONFIG_AZURE_FOTA_JOB_ID_MAX_LEN]; size_t fragment_size; diff --git a/subsys/net/lib/downloader/CMakeLists.txt b/subsys/net/lib/downloader/CMakeLists.txt new file mode 100644 index 000000000000..9c9335b0a432 --- /dev/null +++ b/subsys/net/lib/downloader/CMakeLists.txt @@ -0,0 +1,30 @@ +# +# Copyright (c) 2019 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +zephyr_library() +zephyr_library_sources( + src/dl_parse.c + src/dl_socket.c + src/downloader.c + src/sanity.c +) + +zephyr_library_sources_ifdef( + CONFIG_DOWNLOADER_TRANSPORT_HTTP + src/transports/http.c +) + +zephyr_library_sources_ifdef( + CONFIG_DOWNLOADER_TRANSPORT_COAP + src/transports/coap.c +) + +zephyr_library_sources_ifdef( + CONFIG_DOWNLOADER_SHELL + src/shell.c +) + +zephyr_include_directories(./include) +zephyr_linker_sources(RODATA dl_transports.ld) diff --git a/subsys/net/lib/downloader/Kconfig b/subsys/net/lib/downloader/Kconfig new file mode 100644 index 000000000000..4717e47ac2b6 --- /dev/null +++ b/subsys/net/lib/downloader/Kconfig @@ -0,0 +1,111 @@ +# +# Copyright (c) 2018 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +menuconfig DOWNLOADER + bool "Download client" + +if DOWNLOADER + +comment "Thread and stack buffers" + +config DOWNLOADER_STACK_SIZE + int "Thread stack size" + range 768 4096 + default 1280 + +config DOWNLOADER_MAX_HOSTNAME_SIZE + int "Maximum hostname length (stack)" + range 8 256 + default 256 + +config DOWNLOADER_MAX_FILENAME_SIZE + int "Maximum filename length (stack)" + range 8 2048 + default 255 + +config DOWNLOADER_SHELL + bool "Download client shell" + depends on SHELL + +config DOWNLOADER_TRANSPORT_PARAMS_SIZE + int "Maximum transport parameter size" + default 128 if DOWNLOADER_TRANSPORT_COAP + default 64 if DOWNLOADER_TRANSPORT_HTTP + +config DOWNLOADER_TRANSPORT_HTTP + bool "HTTP transport" + depends on NET_IPV4 || NET_IPV6 + default y + +if DOWNLOADER_TRANSPORT_HTTP + +config DOWNLOADER_HTTP_TIMEO_MS + int "Receive timeout on TCP sockets, in milliseconds" + default 30000 + range -1 600000 + help + Socket timeout for recv() calls, in milliseconds. + When using HTTP or HTTPS, set a timeout to be able to detect + when the server is not responding and client should give up. + Set to -1 disable. + +endif #DOWNLOADER_TRANSPORT_HTTP + +config DOWNLOADER_TRANSPORT_COAP + bool "CoAP transport" + depends on COAP + depends on NET_IPV4 ||NET_IPV6 + +if DOWNLOADER_TRANSPORT_COAP + +config DOWNLOADER_COAP_MAX_RETRANSMIT_REQUEST_COUNT + int "Number of CoAP request retransmissions" + default 4 + range 1 10 + help + As part of CoAP exponential backoff mechanism this is the number + of retransmissions of a request. If the retransmissions exceeds, + the download will be stopped. + +config DOWNLOADER_COAP_BLOCK_SIZE + int + default 3 if DOWNLOADER_COAP_BLOCK_SIZE_128 + default 4 if DOWNLOADER_COAP_BLOCK_SIZE_256 + default 5 if DOWNLOADER_COAP_BLOCK_SIZE_512 + +choice DOWNLOADER_COAP_BLOCK_SIZE_CHOICE + prompt "CoAP block size" + depends on COAP + default DOWNLOADER_COAP_BLOCK_SIZE_512 + help + CoAP blockwise transfer block size. + +config DOWNLOADER_COAP_BLOCK_SIZE_512 + bool "512" + +config DOWNLOADER_COAP_BLOCK_SIZE_256 + bool "256" + +config DOWNLOADER_COAP_BLOCK_SIZE_128 + bool "128" + +endchoice + +endif #DOWNLOADER_TRANSPORT_COAP + +module=DOWNLOADER +module-dep=LOG +module-str=Download client +source "${ZEPHYR_BASE}/subsys/logging/Kconfig.template.log_config" + +if DOWNLOADER_LOG_LEVEL_DBG + +config DOWNLOADER_LOG_HEADERS + bool "Log protocol headers to console [Debug]" + +endif #DOWNLOADER_LOG_LEVEL_DBG + +endif #DOWNLOADER diff --git a/subsys/net/lib/downloader/dl_transports.ld b/subsys/net/lib/downloader/dl_transports.ld new file mode 100644 index 000000000000..ed41ad57b359 --- /dev/null +++ b/subsys/net/lib/downloader/dl_transports.ld @@ -0,0 +1,5 @@ +/* DL transports */ +. = ALIGN(4); +_dl_transport_entry_list_start = .; +KEEP(*(SORT_BY_NAME("._dl_transport_entry.*"))); +_dl_transport_entry_list_end = .; diff --git a/subsys/net/lib/downloader/include/dl_parse.h b/subsys/net/lib/downloader/include/dl_parse.h new file mode 100644 index 000000000000..83bb092e1b1b --- /dev/null +++ b/subsys/net/lib/downloader/include/dl_parse.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef DL_PARSE_H +#define DL_PARSE_H + +#include + +int dl_parse_url_port(const char *url, uint16_t *port); +int dl_parse_url_host(const char *url, char *host, size_t len); +int dl_parse_url_file(const char *url, char *file, size_t len); + +#endif /* DL_PARSE_H */ diff --git a/subsys/net/lib/downloader/include/dl_socket.h b/subsys/net/lib/downloader/include/dl_socket.h new file mode 100644 index 000000000000..c46fbffe289f --- /dev/null +++ b/subsys/net/lib/downloader/include/dl_socket.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef DL_SOCKET_H +#define DL_SOCKET_H + +#include +#include + +int dl_socket_configure_and_connect( + int *fd, int proto, int type, uint16_t port, struct sockaddr *remote_addr, + const char *hostname, struct downloader_host_cfg *host_cfg); +int dl_socket_close(int *fd); +int dl_socket_send(int fd, void *buf, size_t len); +ssize_t dl_socket_recv(int fd, void *buf, size_t len); +int dl_socket_recv_timeout_set(int fd, int timeout_ms); +int dl_socket_send_timeout_set(int fd, int timeout_ms); + +#endif /* DL_SOCKET_H */ diff --git a/subsys/net/lib/downloader/src/dl_parse.c b/subsys/net/lib/downloader/src/dl_parse.c new file mode 100644 index 000000000000..351b09c961a5 --- /dev/null +++ b/subsys/net/lib/downloader/src/dl_parse.c @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include + +#include "dl_parse.h" + +static int swallow(const char **str, const char *swallow) +{ + const char *p; + + p = strstr(*str, swallow); + if (!p) { + return 1; + } + + *str = p + strlen(swallow); + return 0; +} + +int dl_parse_url_host(const char *url, char *host, size_t len) +{ + const char *cur; + const char *end; + + cur = url; + + (void)swallow(&cur, "://"); + + if (cur[0] == '[') { + /* literal IPv6 address */ + end = strchr(cur, ']'); + + if (!end) { + return -EINVAL; + } + ++end; + } else { + end = strchr(cur, ':'); + if (!end) { + end = strchr(cur, '/'); + if (!end) { + end = url + strlen(url) + 1; + } + } + } + + if (end - cur + 1 > len) { + return -E2BIG; + } + + len = end - cur; + + memcpy(host, cur, len); + host[len] = '\0'; + + return 0; +} + +int dl_parse_url_port(const char *url, uint16_t *port) +{ + int err; + const char *cur; + const char *end; + char aport[8]; + size_t len; + + cur = url; + + (void)swallow(&cur, "://"); + + if (cur[0] == '[') { + /* literal IPv6 address */ + swallow(&cur, "]"); + } + + err = swallow(&cur, ":"); + if (err) { + return -EINVAL; + } + + end = strchr(cur, '/'); + if (!end) { + len = strlen(cur); + } else { + len = end - cur; + } + + len = MIN(len, sizeof(aport) - 1); + + memcpy(aport, cur, len); + aport[len] = '\0'; + + *port = atoi(aport); + + return 0; +} + +int dl_parse_url_file(const char *url, char *file, size_t len) +{ + int err; + const char *cur; + + cur = url; + + if (strstr(url, "//")) { + err = swallow(&cur, "://"); + if (err) { + return -EINVAL; + } + + } + + err = swallow(&cur, "/"); + if (err) { + return -EINVAL; + } + + if (strlen(cur) + 1 > len) { + return -E2BIG; + } + + len = strlen(cur); + + memcpy(file, cur, len); + file[len] = '\0'; + + return 0; +} diff --git a/subsys/net/lib/downloader/src/dl_socket.c b/subsys/net/lib/downloader/src/dl_socket.c new file mode 100644 index 000000000000..646b5f891cd5 --- /dev/null +++ b/subsys/net/lib/downloader/src/dl_socket.c @@ -0,0 +1,406 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#if defined(CONFIG_POSIX_API) +#include +#else +#include +#endif +#include +#include +#include +#include + +#include "dl_socket.h" + +#include +LOG_MODULE_DECLARE(downloader, CONFIG_DOWNLOADER_LOG_LEVEL); + +#define SIN6(A) ((struct sockaddr_in6 *)(A)) +#define SIN(A) ((struct sockaddr_in *)(A)) + +#define HOSTNAME_SIZE CONFIG_DOWNLOADER_MAX_HOSTNAME_SIZE + +static const char *str_family(int family) +{ + switch (family) { + case AF_UNSPEC: + return "Unspec"; + case AF_INET: + return "IPv4"; + case AF_INET6: + return "IPv6"; + default: + __ASSERT(false, "Unsupported family"); + return "Unknown"; + } +} + +int dl_socket_send_timeout_set(int fd, int timeout_ms) +{ + int err; + + if (timeout_ms <= 0) { + return 0; + } + + struct timeval timeo = { + .tv_sec = (timeout_ms / 1000), + .tv_usec = (timeout_ms % 1000) * 1000, + }; + + err = setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeo, sizeof(timeo)); + if (err) { + LOG_WRN("Failed to set socket timeout, errno %d", errno); + return -errno; + } + + return 0; +} + +static int socket_sectag_set(int fd, const int * const sec_tag_list, uint8_t sec_tag_count) +{ + int err; + int verify; + + enum { + NONE = 0, + OPTIONAL = 1, + REQUIRED = 2, + }; + + verify = REQUIRED; + + err = setsockopt(fd, SOL_TLS, TLS_PEER_VERIFY, &verify, sizeof(verify)); + if (err) { + LOG_ERR("Failed to setup peer verification, errno %d", errno); + return -errno; + } + + LOG_INF("Setting up TLS credentials, sec tag count %u", sec_tag_count); + err = setsockopt(fd, SOL_TLS, TLS_SEC_TAG_LIST, sec_tag_list, + sizeof(sec_tag_t) * sec_tag_count); + if (err) { + LOG_ERR("Failed to setup socket security tag list, errno %d", errno); + return -errno; + } + + return 0; +} + +static int socket_tls_hostname_set(int fd, const char * const host) +{ + __ASSERT_NO_MSG(host); + + int err; + + err = setsockopt(fd, SOL_TLS, TLS_HOSTNAME, host, + strlen(host)); + if (err) { + LOG_ERR("Failed to setup TLS hostname (%s), errno %d", + host, errno); + return -errno; + } + + return 0; +} + +static int socket_pdn_id_set(int fd, int pdn_id) +{ + int err; + + LOG_INF("Binding to PDN ID: %d", pdn_id); + err = setsockopt(fd, SOL_SOCKET, SO_BINDTOPDN, &pdn_id, sizeof(pdn_id)); + if (err) { + LOG_ERR("Failed to bind socket to PDN ID %d, err %d", + pdn_id, errno); + return -ENETDOWN; + } + + return 0; +} + +static bool is_ip_address(const char *hostname) +{ + struct sockaddr sa; + + if (zsock_inet_pton(AF_INET, hostname, sa.data) == 1) { + return true; + } else if (zsock_inet_pton(AF_INET6, hostname, sa.data) == 1) { + return true; + } + + return false; +} + +static int dl_socket_host_lookup( + const char * const hostname, uint8_t pdn_id, struct sockaddr *sa, int family) +{ + int err; + char pdnserv[4]; + char *servname = NULL; + struct addrinfo *ai; + struct addrinfo hints = { + .ai_family = family, + }; + +#if !defined(CONFIG_NET_IPV6) + if (family == AF_INET6) { + return -EINVAL; + } +#endif + +#if !defined(CONFIG_NET_IPV4) + if (family == AF_INET) { + return -EINVAL; + } +#endif + + LOG_DBG("host lookup %s, pdn id %d, family %d\n", hostname, pdn_id, family); + + if (pdn_id) { + hints.ai_flags = AI_PDNSERV; + (void)snprintf(pdnserv, sizeof(pdnserv), "%d", pdn_id); + servname = pdnserv; + } + + err = getaddrinfo(hostname, servname, &hints, &ai); + if (err) { + /* We expect this to fail on IPv6 sometimes */ + LOG_DBG("Failed to resolve hostname %s on %s, err %d\n", + hostname, str_family(hints.ai_family), err); + return -EHOSTUNREACH; + } + + memcpy(sa, ai->ai_addr, ai->ai_addrlen); + freeaddrinfo(ai); + + return 0; +} + +static int dl_socket_create_and_connect( + int *fd, int proto, int type, uint16_t port, struct sockaddr *remote_addr, + const char *hostname, struct downloader_host_cfg *host_cfg) +{ + int err; + socklen_t addrlen; + + switch (remote_addr->sa_family) { + case AF_INET6: + SIN6(remote_addr)->sin6_port = htons(port); + addrlen = sizeof(struct sockaddr_in6); + break; + case AF_INET: + SIN(remote_addr)->sin_port = htons(port); + addrlen = sizeof(struct sockaddr_in); + break; + default: + err = -EAFNOSUPPORT; + goto cleanup; + } + + LOG_DBG("family: %d, type: %d, proto: %d\n", + remote_addr->sa_family, type, proto); + + *fd = socket(remote_addr->sa_family, type, proto); + if (*fd < 0) { + err = -errno; + LOG_ERR("Failed to create socket, errno %d", -err); + goto cleanup; + } + + LOG_DBG("Socket opened, fd %d", *fd); + + if (host_cfg->pdn_id) { + err = socket_pdn_id_set(*fd, host_cfg->pdn_id); + if (err) { + goto cleanup; + } + } + + if ((proto == IPPROTO_TLS_1_2 || proto == IPPROTO_DTLS_1_2) && + (host_cfg->sec_tag_list != NULL) && (host_cfg->sec_tag_count > 0)) { + err = socket_sectag_set(*fd, host_cfg->sec_tag_list, + host_cfg->sec_tag_count); + if (err) { + goto cleanup; + } + + if (proto == IPPROTO_TLS_1_2 && + !is_ip_address(hostname)) { + err = socket_tls_hostname_set(*fd, hostname); + if (err) { + err = -errno; + goto cleanup; + } + } + + if (proto == IPPROTO_DTLS_1_2 && host_cfg->cid) { + /* Enable connection ID */ + uint32_t dtls_cid = TLS_DTLS_CID_ENABLED; + + err = setsockopt(*fd, SOL_TLS, TLS_DTLS_CID, &dtls_cid, + sizeof(dtls_cid)); + if (err) { + err = -errno; + LOG_ERR("Failed to enable TLS_DTLS_CID: %d", err); + /* Not fatal, so continue */ + } + } + } + + if (IS_ENABLED(CONFIG_LOG)) { + char ip_addr_str[NET_IPV6_ADDR_LEN]; + void *sin_addr; + + if (remote_addr->sa_family == AF_INET6) { + sin_addr = &((struct sockaddr_in6 *)remote_addr)->sin6_addr; + } else { + sin_addr = &((struct sockaddr_in *)remote_addr)->sin_addr; + } + inet_ntop(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", + *fd, addrlen, str_family(remote_addr->sa_family), port); + + err = connect(*fd, remote_addr, addrlen); + if (err) { + err = -errno; + LOG_ERR("Unable to connect, errno %d", -err); + /* Make sure that ECONNRESET is not returned as it has a special meaning + * in the download client API + */ + if (err == -ECONNRESET) { + err = -ECONNREFUSED; + } + } + +cleanup: + if (err) { + dl_socket_close(fd); + } + + return err; +} + +int dl_socket_configure_and_connect( + int *fd, int proto, int type, uint16_t port, struct sockaddr *remote_addr, + const char *hostname, struct downloader_host_cfg *host_cfg) +{ + int err = -1; + int fam; + + if (remote_addr->sa_family) { + goto connect; + } + + fam = host_cfg->family ? host_cfg->family : AF_INET6; + + err = dl_socket_host_lookup(hostname, host_cfg->pdn_id, remote_addr, fam); + if (!err) { + goto connect; + } else if (host_cfg->family) { + LOG_ERR("Host lookup failed for hostname %s, err %d", hostname, err); + return err; + } + +fallback_ipv4: + err = dl_socket_host_lookup(hostname, host_cfg->pdn_id, remote_addr, AF_INET); + if (err) { + LOG_ERR("Host lookup failed for hostname %s, err %d", hostname, err); + return err; + } + +connect: + err = dl_socket_create_and_connect(fd, proto, type, port, remote_addr, hostname, host_cfg); + if (err) { + if (remote_addr->sa_family == AF_INET6) { + LOG_INF("Could not connect on IPv6, attempting IPv4"); + goto fallback_ipv4; + } + } + + return 0; +} + +int dl_socket_close(int *fd) +{ + int err = 0; + + if (*fd != -1) { + err = close(*fd); + if (err && errno != EBADF) { + err = -errno; + LOG_ERR("Failed to close socket, errno %d", -err); + } + + LOG_DBG("Socket closed, fd %d", *fd); + *fd = -1; + } + + return err; +} + +int dl_socket_send(int fd, void *buf, size_t len) +{ + int sent; + size_t off = 0; + + while (len) { + sent = send(fd, (uint8_t *)buf + off, len, 0); + if (sent < 0) { + return -errno; + } + + off += sent; + len -= sent; + } + + return 0; +} + +int dl_socket_recv_timeout_set(int fd, int timeout_ms) +{ + int err; + struct timeval timeo; + + if (timeout_ms <= 0) { + return 0; + } + + if (fd == -1) { + return -EINVAL; + } + + timeo.tv_sec = (timeout_ms / 1000); + timeo.tv_usec = (timeout_ms % 1000) * 1000; + + err = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo)); + if (err) { + LOG_WRN("Failed to set socket timeout, errno %d", errno); + return -errno; + } + + return 0; +} + +ssize_t dl_socket_recv(int fd, void *buf, size_t len) +{ + int err = 0; + + if (fd == -1) { + return -EINVAL; + } + + err = recv(fd, buf, len, 0); + if (err < 0) { + return -errno; + } + + return err; +} diff --git a/subsys/net/lib/downloader/src/downloader.c b/subsys/net/lib/downloader/src/downloader.c new file mode 100644 index 000000000000..770c09c4258d --- /dev/null +++ b/subsys/net/lib/downloader/src/downloader.c @@ -0,0 +1,641 @@ +/* + * Copyright (c) 2019-2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "dl_parse.h" +#include "dl_socket.h" + +#include +LOG_MODULE_REGISTER(downloader, CONFIG_DOWNLOADER_LOG_LEVEL); + +static int stopped_evt_send(struct downloader *dl); + +#define FALLBACK_HTTP "http://" +#define FALLBACK_HTTPS "https://" + +#define STATE_ALLOW_ANY 0xa1151a7e + +#if defined(CONFIG_DOWNLOADER_LOG_LEVEL_WRN) +char *state_to_str(int state) +{ + switch (state) { + case DOWNLOADER_IDLE: + return "IDLE"; + case DOWNLOADER_CONNECTING: + return "CONNECTING"; + case DOWNLOADER_CONNECTED: + return "CONNECTED"; + case DOWNLOADER_DOWNLOADING: + return "DOWNLOADING"; + case DOWNLOADER_STOPPING: + return "STOPPING"; + case DOWNLOADER_DEINITIALIZING: + return "DEINITIALIZING"; + case DOWNLOADER_DEINITIALIZED: + return "DEINITIALIZED"; + } + + return "unknown"; +} +#else +char *state_to_str(int state) +{ + return ""; +} +#endif + +static void state_set( + struct downloader *dl, unsigned int before_state, unsigned int new_state) +{ + k_mutex_lock(&dl->mutex, K_FOREVER); + if (dl->state != before_state && before_state != STATE_ALLOW_ANY) { + LOG_WRN("Unexpected state transition attempted, aborted: %d->%d (%s->%s)", + before_state, new_state, + state_to_str(before_state), state_to_str(new_state)); + return; + } + + dl->state = new_state; + k_mutex_unlock(&dl->mutex); + LOG_DBG("state = %d (%s)", new_state, state_to_str(new_state)); +} + +static bool is_state(struct downloader *dl, enum downloader_state state) +{ + bool ret; + + k_mutex_lock(&dl->mutex, K_FOREVER); + ret = dl->state == state; + k_mutex_unlock(&dl->mutex); + return ret; +} + +static int transport_init(struct downloader *dl, + struct downloader_host_cfg *host_config, const char *uri) +{ + int err; + + if (!dl || !dl->transport) { + return -EINVAL; + } + + err = dl->transport->init(dl, host_config, uri); + + return err; +} + +static int transport_deinit(struct downloader *dl) +{ + int err; + + if (!dl || !dl->transport) { + return -EINVAL; + } + + err = dl->transport->deinit(dl); + + return err; +} + +static int transport_connect(struct downloader *dl) +{ + int err; + + if (!dl || !dl->transport) { + return -EINVAL; + } + + err = dl->transport->connect(dl); + + return err; +} + +static int transport_close(struct downloader *dl) +{ + int err; + + if (!dl || !dl->transport) { + return -EINVAL; + } + + err = dl->transport->close(dl); + + return err; +} + +static int transport_download(struct downloader *dl) +{ + int err; + + if (!dl || !dl->transport) { + return -EINVAL; + } + + err = dl->transport->download(dl); + + return err; +} + + +static int reconnect(struct downloader *dl) +{ + int err = 0; + + LOG_INF("Reconnecting..."); + + err = transport_close(dl); + if (err) { + LOG_DBG("disconnect failed, %d", err); + } + + err = transport_connect(dl); + + return err; +} + +static void restart_and_suspend(struct downloader *dl) +{ + if (!is_state(dl, DOWNLOADER_DOWNLOADING)) { + return; + } + + if (!dl->host_config.keep_connection) { + transport_close(dl); + stopped_evt_send(dl); + state_set(dl, DOWNLOADER_DOWNLOADING, DOWNLOADER_IDLE); + return; + } + + stopped_evt_send(dl); + state_set(dl, DOWNLOADER_DOWNLOADING, DOWNLOADER_CONNECTED); +} + +static int data_evt_send(const struct downloader *dl, void *data, size_t len) +{ + const struct downloader_evt evt = { + .id = DOWNLOADER_EVT_FRAGMENT, + .fragment = { + .buf = data, + .len = len, + } + }; + + return dl->config.callback(&evt); +} + +static int download_complete_evt_send(const struct downloader *dl) +{ + const struct downloader_evt evt = { + .id = DOWNLOADER_EVT_DONE, + }; + + return dl->config.callback(&evt); +} + +static int stopped_evt_send(struct downloader *dl) +{ + const struct downloader_evt evt = { + .id = DOWNLOADER_EVT_STOPPED, + }; + + return dl->config.callback(&evt); +} + +static int error_evt_send(const struct downloader *dl, int error) +{ + /* Error will be sent as negative. */ + __ASSERT_NO_MSG(error < 0); + + const struct downloader_evt evt = { + .id = DOWNLOADER_EVT_ERROR, + .error = error + }; + + return dl->config.callback(&evt); +} + +static int deinit_evt_send(const struct downloader *dl) +{ + const struct downloader_evt evt = { + .id = DOWNLOADER_EVT_DEINITIALIZED, + }; + + return dl->config.callback(&evt); +} + +/* Events from the transport */ +int dl_transport_evt_data(struct downloader *dl, void *data, size_t len) +{ + int err; + + LOG_DBG("Read %d bytes from transport", len); + + if (dl->file_size) { + LOG_INF("Downloaded %u/%u bytes (%d%%)", + dl->progress, dl->file_size, + (dl->progress * 100) / dl->file_size); + } else { + LOG_INF("Downloaded %u bytes", dl->progress); + } + + err = data_evt_send(dl, data, len); + if (err) { + /* Application refused data, suspend */ + restart_and_suspend(dl); + } + + return 0; +} + +int dl_transport_evt_download_complete(struct downloader *dl) +{ + LOG_INF("Download complete"); + download_complete_evt_send(dl); + restart_and_suspend(dl); + + return 0; +} + +void download_thread(void *cli, void *a, void *b) +{ + int rc, rc2; + struct downloader *const dl = cli; + + while (true) { + rc = 0; + + if (is_state(dl, DOWNLOADER_IDLE)) { + /* Client idle, wait for action */ + k_sem_take(&dl->event_sem, K_FOREVER); + } + + /* Connect to the target host */ + if (is_state(dl, DOWNLOADER_CONNECTING)) { + /* Client */ + rc = transport_connect(dl); + if (rc) { + state_set(dl, DOWNLOADER_CONNECTING, DOWNLOADER_IDLE); + rc = error_evt_send(dl, rc); + if (rc) { + stopped_evt_send(dl); + state_set(dl, DOWNLOADER_CONNECTING, DOWNLOADER_IDLE); + continue; + } + continue; + } + + /* Connection successful */ + state_set(dl, DOWNLOADER_CONNECTING, DOWNLOADER_DOWNLOADING); + } + + if (is_state(dl, DOWNLOADER_CONNECTED)) { + /* Client connected, wait for action */ + k_sem_take(&dl->event_sem, K_FOREVER); + } + + if (is_state(dl, DOWNLOADER_DOWNLOADING)) { + /* Download until transport returns an error or the download is complete + * (separate event). + */ + rc = transport_download(dl); + if (rc) { + if (rc != -ECONNRESET) { + /* On ECONNRESET we want to try to reconnect. + * If successful, we continue as normal. + */ + goto reconnect; + } + + rc = error_evt_send(dl, rc); + if (rc) { + restart_and_suspend(dl); + continue; + } + +reconnect: + rc2 = reconnect(dl); + if (rc2 == 0) { + continue; + } + + LOG_ERR("Failed to reconnect, err %d", rc2); + if (rc == -ECONNRESET) { + /* We haven't sent the error before in this case, + * so we do it now. + */ + rc2 = error_evt_send(dl, rc); + if (rc2 == 0) { + goto reconnect; + } + } + + transport_close(dl); + stopped_evt_send(dl); + state_set(dl, DOWNLOADER_DOWNLOADING, DOWNLOADER_IDLE); + continue; + } + } + + if (is_state(dl, DOWNLOADER_STOPPING)) { + if (!dl->host_config.keep_connection) { + transport_close(dl); + state_set(dl, STATE_ALLOW_ANY, DOWNLOADER_IDLE); + } else { + state_set(dl, STATE_ALLOW_ANY, DOWNLOADER_CONNECTED); + } + + stopped_evt_send(dl); + } + + if (is_state(dl, DOWNLOADER_DEINITIALIZING)) { + transport_close(dl); + transport_deinit(dl); + state_set(dl, DOWNLOADER_DEINITIALIZING, + DOWNLOADER_DEINITIALIZED); + deinit_evt_send(dl); + return; + } + } +} + +int downloader_init(struct downloader *const dl, + struct downloader_cfg *config) +{ + if (dl == NULL || config == NULL || config->callback == NULL || + config->buf == NULL || config->buf_size == 0) { + return -EINVAL; + } + + if (dl->state != DOWNLOADER_DEINITIALIZED) { + return -EALREADY; + } + + memset(dl, 0, sizeof(*dl)); + dl->config = *config; + k_sem_init(&dl->event_sem, 0, 1); + k_mutex_init(&dl->mutex); + + k_mutex_lock(&dl->mutex, K_FOREVER); + + /* The thread is spawned now, but it will suspend itself; + * it is resumed when the download is started via the API. + */ + dl->tid = + k_thread_create(&dl->thread, dl->thread_stack, + K_THREAD_STACK_SIZEOF(dl->thread_stack), + download_thread, dl, NULL, NULL, + K_LOWEST_APPLICATION_THREAD_PRIO, 0, K_NO_WAIT); + + k_thread_name_set(dl->tid, "downloader"); + + state_set(dl, DOWNLOADER_DEINITIALIZED, DOWNLOADER_IDLE); + + k_mutex_unlock(&dl->mutex); + + return 0; +} + +int downloader_deinit(struct downloader *const dl) +{ + if (!dl) { + return -EINVAL; + } + + if (is_state(dl, DOWNLOADER_CONNECTING) || + is_state(dl, DOWNLOADER_DOWNLOADING)) { + error_evt_send(dl, -ECANCELED); + stopped_evt_send(dl); + } + + state_set(dl, STATE_ALLOW_ANY, DOWNLOADER_DEINITIALIZING); + k_sem_give(&dl->event_sem); + + k_thread_join(&dl->thread, K_FOREVER); + + return 0; +} + +static int downloader_start(struct downloader *dl, + const struct downloader_host_cfg *host_config, + const char *uri, size_t from) +{ + int err; + struct dl_transport *transport_connected = NULL; + + if (dl == NULL || host_config == NULL || uri == NULL) { + return -EINVAL; + } + + LOG_DBG("URI: %s", uri); + + k_mutex_lock(&dl->mutex, K_FOREVER); + + if (!is_state(dl, DOWNLOADER_IDLE) && + !is_state(dl, DOWNLOADER_CONNECTED)) { + LOG_ERR("Invalid start state: %d", dl->state); + k_mutex_unlock(&dl->mutex); + return -EPERM; + } + + /* Check if we are already connected to the correct host */ + if (is_state(dl, DOWNLOADER_CONNECTED)) { + char hostname[CONFIG_DOWNLOADER_MAX_HOSTNAME_SIZE]; + + err = dl_parse_url_host(uri, hostname, sizeof(hostname)); + if (err) { + LOG_ERR("Failed to parse hostname"); + k_mutex_unlock(&dl->mutex); + return -EINVAL; + } + if (strncmp(hostname, dl->hostname, sizeof(hostname)) == 0) { + transport_connected = dl->transport; + } + } + + /* Extract the hostname, without protocol or port */ + err = dl_parse_url_host(uri, dl->hostname, sizeof(dl->hostname)); + if (err) { + LOG_ERR("Failed to parse hostname, err %d", err); + k_mutex_unlock(&dl->mutex); + return -EINVAL; + } + + /* Extract the filename, without protocol or port */ + err = dl_parse_url_file(uri, dl->file, sizeof(dl->file)); + if (err) { + LOG_ERR("Failed to parse filename, err %d, uri %s", err, uri); + k_mutex_unlock(&dl->mutex); + return -EINVAL; + } + + dl->host_config = *host_config; + dl->file_size = 0; + dl->progress = from; + dl->buf_offset = 0; + + dl->transport = NULL; + STRUCT_SECTION_FOREACH(dl_transport_entry, entry) { + if (entry->transport->proto_supported(dl, uri)) { + dl->transport = entry->transport; + break; + } + } + + if (!dl->transport) { + if (strstr(uri, "://") == NULL) { + char *fallback = FALLBACK_HTTP; + + if (host_config->sec_tag_list && host_config->sec_tag_count) { + fallback = FALLBACK_HTTPS; + } + + LOG_WRN("Protocol not specified for %s, attempting %s", uri, fallback); + STRUCT_SECTION_FOREACH(dl_transport_entry, entry) { + if (entry->transport->proto_supported(dl, fallback)) { + dl->transport = entry->transport; + break; + } + } + } + + if (!dl->transport) { + LOG_ERR("Protocol not found for %s", uri); + k_mutex_unlock(&dl->mutex); + return -EPROTONOSUPPORT; + } + }; + + if (is_state(dl, DOWNLOADER_CONNECTED)) { + if (dl->transport == transport_connected) { + state_set(dl, DOWNLOADER_CONNECTED, DOWNLOADER_DOWNLOADING); + goto out; + } else { + /* We are connected to the wrong host */ + LOG_DBG("Closing connection to connect different host or protocol"); + transport_connected->close(dl); + transport_connected->deinit(dl); + state_set(dl, DOWNLOADER_CONNECTED, DOWNLOADER_CONNECTING); + } + } else { + /* IDLE */ + state_set(dl, DOWNLOADER_IDLE, DOWNLOADER_CONNECTING); + } + + memset(dl->transport_internal, 0, sizeof(dl->transport_internal)); + err = transport_init(dl, &dl->host_config, uri); + if (err) { + state_set(dl, STATE_ALLOW_ANY, DOWNLOADER_IDLE); + k_mutex_unlock(&dl->mutex); + LOG_ERR("Failed to initialize transport, err %d", err); + return err; + } + +out: + k_mutex_unlock(&dl->mutex); + + /* Let the thread run */ + k_sem_give(&dl->event_sem); + return 0; +} + +int downloader_cancel(struct downloader *const dl) +{ + if (dl == NULL || + is_state(dl, DOWNLOADER_IDLE) || + is_state(dl, DOWNLOADER_CONNECTED) || + is_state(dl, DOWNLOADER_DEINITIALIZED)) { + return -EPERM; + } + + state_set(dl, STATE_ALLOW_ANY, DOWNLOADER_STOPPING); + return 0; +} + +int downloader_get(struct downloader *dl, + const struct downloader_host_cfg *host_config, + const char *uri, size_t from) +{ + int rc; + + if (dl == NULL || uri == NULL) { + return -EINVAL; + } + + k_mutex_lock(&dl->mutex, K_FOREVER); + + rc = downloader_start(dl, host_config, uri, from); + + k_mutex_unlock(&dl->mutex); + + return rc; +} + +int downloader_get_with_host_and_path(struct downloader *dl, + const struct downloader_host_cfg *host_config, + const char *host, const char *file, size_t from) +{ + int rc; + + if (dl == NULL || host == NULL || file == NULL) { + return -EINVAL; + } + + k_mutex_lock(&dl->mutex, K_FOREVER); + + if (!is_state(dl, DOWNLOADER_IDLE) && + !is_state(dl, DOWNLOADER_CONNECTED)) { + LOG_ERR("Invalid start state: %d", dl->state); + k_mutex_unlock(&dl->mutex); + return -EPERM; + } + + /* We use the download client buffer to parse the uri */ + if (strlen(host) + strlen(file) + 2 > dl->config.buf_size) { + LOG_ERR("Download client buffer is not large enough to parse uri"); + return -EINVAL; + } + + sprintf(dl->config.buf, "%s/%s", host, file); + + rc = downloader_start(dl, host_config, dl->config.buf, from); + + k_mutex_unlock(&dl->mutex); + + return rc; +} + +int downloader_file_size_get(struct downloader *dl, size_t *size) +{ + if (!dl || !size) { + return -EINVAL; + } + + k_mutex_lock(&dl->mutex, K_FOREVER); + *size = dl->file_size; + k_mutex_unlock(&dl->mutex); + + return 0; +} + +int downloader_downloaded_size_get(struct downloader *dl, size_t *size) +{ + if (!dl || !size) { + return -EINVAL; + } + + k_mutex_lock(&dl->mutex, K_FOREVER); + *size = dl->progress; + k_mutex_unlock(&dl->mutex); + + return 0; +} diff --git a/subsys/net/lib/downloader/src/sanity.c b/subsys/net/lib/downloader/src/sanity.c new file mode 100644 index 000000000000..741a280aedbb --- /dev/null +++ b/subsys/net/lib/downloader/src/sanity.c @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include + +#define HOSTNAME_SIZE CONFIG_DOWNLOADER_MAX_HOSTNAME_SIZE +#define FILENAME_SIZE CONFIG_DOWNLOADER_MAX_FILENAME_SIZE +#define STACK_SIZE CONFIG_DOWNLOADER_STACK_SIZE + +/* Ensure that the stack size is large enough + * to accommodate for host and file names + */ + +BUILD_ASSERT( + STACK_SIZE - (HOSTNAME_SIZE + FILENAME_SIZE) >= 512, + "Your stack size is too small" +); diff --git a/subsys/net/lib/downloader/src/shell.c b/subsys/net/lib/downloader/src/shell.c new file mode 100644 index 000000000000..74f376298464 --- /dev/null +++ b/subsys/net/lib/downloader/src/shell.c @@ -0,0 +1,274 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include + +LOG_MODULE_DECLARE(downloader); + +static int dl_callback(const struct downloader_evt *event); + +static char dl_buf[2048]; +static struct downloader downloader; +static struct downloader_cfg config = { + .callback = dl_callback, + .buf = dl_buf, + .buf_size = sizeof(dl_buf), +}; + +static int sec_tag_list[1]; +static struct downloader_host_cfg host_config = { + .sec_tag_list = sec_tag_list, +}; + +static char uri[CONFIG_DOWNLOADER_MAX_HOSTNAME_SIZE + + CONFIG_DOWNLOADER_MAX_FILENAME_SIZE + 1]; +static bool in_progress; + +static const struct shell *shell_instance; + +static int dl_callback(const struct downloader_evt *event) +{ + static size_t downloaded; + static size_t file_size; + + if (downloaded == 0) { + downloader_file_size_get(&downloader, &file_size); + } + + switch (event->id) { + case DOWNLOADER_EVT_FRAGMENT: + downloaded += event->fragment.len; + if (file_size) { + shell_fprintf(shell_instance, SHELL_NORMAL, + "\r[ %d%% ] ", + (downloaded * 100) / file_size); + } else { + shell_fprintf(shell_instance, SHELL_NORMAL, + "\r[ %d bytes ] ", downloaded); + } + break; + case DOWNLOADER_EVT_DONE: + shell_print(shell_instance, "done (%d bytes)", downloaded); + downloaded = 0; + break; + case DOWNLOADER_EVT_ERROR: + shell_error(shell_instance, "error %d during download", + event->error); + downloaded = 0; + in_progress = false; + break; + case DOWNLOADER_EVT_STOPPED: + shell_print(shell_instance, "download client closed"); + in_progress = false; + case DOWNLOADER_EVT_DEINITIALIZED: + shell_print(shell_instance, "client deinitialized"); + in_progress = false; + break; + } + + return 0; +} + +static int cmd_dc_init(const struct shell *shell, size_t argc, char **argv) +{ + int err; + + shell_instance = shell; + + err = downloader_init(&downloader, &config); + if (err) { + shell_warn(shell, "Download client init failed %d\n", err); + } + + shell_print(shell, "Download client initialized\n"); + + return err; +} + +static int cmd_dc_deinit(const struct shell *shell, size_t argc, char **argv) +{ + int err; + + err = downloader_deinit(&downloader); + if (err) { + shell_warn(shell, "Download client deinit failed %d\n", err); + } + + return err; +} + +static int cmd_host_config_pdn_id(const struct shell *shell, size_t argc, + char **argv) +{ + if (argc != 2) { + shell_warn(shell, "usage: dc host_config pdn \n"); + return -EINVAL; + } + + host_config.pdn_id = atoi(argv[1]); + + shell_print(shell, "PDN ID set: %d\n", host_config.pdn_id); + return 0; +} + +static int cmd_host_config_sec_tag(const struct shell *shell, size_t argc, + char **argv) +{ + if (argc != 2) { + shell_warn(shell, "usage: dc host_config sec_tag \n"); + return -EINVAL; + } + + sec_tag_list[0] = atoi(argv[1]); + host_config.sec_tag_count = 1; + + shell_print(shell, "Security tag set: %d\n", host_config.sec_tag_list[0]); + return 0; +} + +static int cmd_host_config_native_tls(const struct shell *shell, size_t argc, + char **argv) +{ + if (argc != 2) { + shell_warn(shell, "usage: dc host_config native_tls <0/1>\n"); + return -EINVAL; + } + + host_config.set_native_tls = atoi(argv[1]); + + shell_print(shell, "Native tls %s\n", host_config.set_native_tls ? "enabled" : "disabled"); + + return 0; +} + +static int cmd_host_config_keep_connection(const struct shell *shell, size_t argc, + char **argv) +{ + if (argc != 2) { + shell_warn(shell, "usage: dc host_config keep_connection <0/1>\n"); + return -EINVAL; + } + + host_config.keep_connection = atoi(argv[1]); + + shell_print(shell, "Keep connection %s\n", + host_config.keep_connection ? "enabled" : "disabled"); + + return 0; +} + +static int cmd_download_get(const struct shell *shell, size_t argc, char **argv) +{ + int err; + size_t from = 0; + + shell_instance = shell; + + if (argc < 2 || argc > 3) { + shell_warn(shell, "usage: dc get [offset]"); + return -EINVAL; + } + + if (argc == 3) { + from = atoi(argv[2]); + } + + if (in_progress) { + return -EALREADY; + } + + strncpy(uri, argv[1], sizeof(uri)); + uri[sizeof(uri) - 1] = '\0'; + + err = downloader_get(&downloader, &host_config, uri, from); + + if (err) { + shell_warn(shell, "downloader_get() failed, err %d", + err); + return -ENOEXEC; + } + + in_progress = true; + shell_print(shell, "Downloading"); + + return 0; +} + +static int cmd_download_cancel(const struct shell *shell, size_t argc, char **argv) +{ + int err; + + err = downloader_cancel(&downloader); + if (err) { + shell_warn(shell, "downloader_cancel() failed, err %d", + err); + } else { + shell_print(shell, "Download cancelled"); + } + return 0; +} + +static int cmd_download_file_size_get(const struct shell *shell, size_t argc, char **argv) +{ + int err; + size_t fs; + + err = downloader_file_size_get(&downloader, &fs); + if (err) { + shell_warn(shell, "downloader_file_size_get() failed, err %d", + err); + } else { + shell_print(shell, "File size: %d", fs); + } + return 0; +} + +static int cmd_download_progress_get(const struct shell *shell, size_t argc, char **argv) +{ + int err; + size_t fs; + + err = downloader_downloaded_size_get(&downloader, &fs); + if (err) { + shell_warn(shell, "downloader_downloaded_size_get() failed, err %d", + err); + } else { + shell_print(shell, "Downloaded: %d", fs); + } + return 0; +} + +SHELL_STATIC_SUBCMD_SET_CREATE(host_config_options, + SHELL_CMD(sec_tag, NULL, "Set security tag", cmd_host_config_sec_tag), + SHELL_CMD(pdn_id, NULL, "Set PDN ID", cmd_host_config_pdn_id), + SHELL_CMD(sec_tag, NULL, "Set security tag", cmd_host_config_sec_tag), + SHELL_CMD(native_tls, NULL, "Enable native TLS", cmd_host_config_native_tls), + SHELL_CMD(keep_connection, NULL, "Keep host connection", cmd_host_config_keep_connection), + SHELL_SUBCMD_SET_END +); + +SHELL_STATIC_SUBCMD_SET_CREATE(download_options, + SHELL_CMD(get, NULL, "Get file", cmd_download_get), + SHELL_CMD(cancel, NULL, "Cancel download", cmd_download_cancel), + SHELL_CMD(file_size, NULL, "Get file size", cmd_download_file_size_get), + SHELL_CMD(progress, NULL, "Get bytes downloaded", cmd_download_progress_get), + SHELL_SUBCMD_SET_END +); + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_dl, + SHELL_CMD(init, NULL, "Initialize download client", cmd_dc_init), + SHELL_CMD(deinit, NULL, "Deinitialize download client", cmd_dc_deinit), + SHELL_CMD(host_config, &host_config_options, "Set configuration option", NULL), + SHELL_CMD(download, &download_options, "Download options", NULL), + SHELL_SUBCMD_SET_END +); + +SHELL_CMD_REGISTER(dl, &sub_dl, "Download client", NULL); diff --git a/subsys/net/lib/downloader/src/transports/coap.c b/subsys/net/lib/downloader/src/transports/coap.c new file mode 100644 index 000000000000..b4bab23cf923 --- /dev/null +++ b/subsys/net/lib/downloader/src/transports/coap.c @@ -0,0 +1,549 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "dl_socket.h" +#include "dl_parse.h" + + +LOG_MODULE_DECLARE(downloader, CONFIG_DOWNLOADER_LOG_LEVEL); + +#define COAP_VER 1 +#define FILENAME_SIZE CONFIG_DOWNLOADER_MAX_FILENAME_SIZE +#define COAP_PATH_ELEM_DELIM "/" + +struct transport_params_coap { + /** Initialization status */ + bool initialized; + /** CoAP block context. */ + struct coap_block_context block_ctx; + /** CoAP pending object. */ + struct coap_pending pending; + + struct { + /** 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; + + /** Request new data */ + bool new_data_req; + /** Request retransmission */ + bool retransmission_req; +}; + +BUILD_ASSERT(CONFIG_DOWNLOADER_TRANSPORT_PARAMS_SIZE >= sizeof(struct transport_params_coap)); + +/* declaration of strtok_r appears to be missing in some cases, + * even though it's defined in the minimal libc, so we forward declare it + */ +extern char *strtok_r(char *str, const char *sep, char **state); + +int dl_parse_url_file(const char *url, char *file, size_t len); + +static int coap_get_current_from_response_pkt(const struct coap_packet *cpkt) +{ + int block = 0; + + block = coap_get_option_int(cpkt, COAP_OPTION_BLOCK2); + if (block < 0) { + return block; + } + + return GET_BLOCK_NUM(block) << (GET_BLOCK_SIZE(block) + 4); +} + +static bool has_pending(struct downloader *dl) +{ + struct transport_params_coap *coap; + + coap = (struct transport_params_coap *)dl->transport_internal; + return coap->pending.timeout > 0; +} + +int coap_block_init(struct downloader *dl, size_t from) +{ + struct transport_params_coap *coap; + + coap = (struct transport_params_coap *)dl->transport_internal; + + if (coap->initialized) { + return 0; + } + + coap_block_transfer_init(&coap->block_ctx, CONFIG_COAP_BLOCK_SIZE, 0); + coap->block_ctx.current = from; + coap_pending_clear(&coap->pending); + + coap->initialized = true; + return 0; +} + +int coap_get_recv_timeout(struct downloader *dl) +{ + int timeout; + struct transport_params_coap *coap; + + coap = (struct transport_params_coap *)dl->transport_internal; + + __ASSERT(has_pending(dl), "Must have coap pending"); + + /* Retransmission is cycled in case recv() times out. In case sending request + * blocks, the time that is used for sending request must be substracted next time + * recv() is called. + */ + timeout = coap->pending.t0 + coap->pending.timeout - k_uptime_get_32(); + if (timeout < 0) { + /* All time is spent when sending request and time this + * method is called, there is no time left for receiving; + * skip over recv() and initiate retransmission on next + * cycle + */ + return 0; + } + + return timeout; +} + +int coap_initiate_retransmission(struct downloader *dl) +{ + struct transport_params_coap *coap; + + coap = (struct transport_params_coap *)dl->transport_internal; + + if (coap->pending.timeout == 0) { + return -EINVAL; + } + + if (!coap_pending_cycle(&coap->pending)) { + LOG_ERR("CoAP max-retransmissions exceeded"); + return -1; + } + + return 0; +} + +static int coap_block_update(struct downloader *dl, struct coap_packet *pkt, + size_t *blk_off, bool *more) +{ + struct transport_params_coap *coap; + + coap = (struct transport_params_coap *)dl->transport_internal; + + int err, new_current; + + *blk_off = coap->block_ctx.current % + coap_block_size_to_bytes(coap->block_ctx.block_size); + if (*blk_off) { + LOG_DBG("%d bytes of current block already downloaded", + *blk_off); + } + + new_current = coap_get_current_from_response_pkt(pkt); + if (new_current < 0) { + LOG_ERR("Failed to get current from CoAP packet, err %d", new_current); + return new_current; + } + + if (new_current < coap->block_ctx.current) { + LOG_WRN("Block out of order %d, expected %d", new_current, + coap->block_ctx.current); + return -1; + } else if (new_current > coap->block_ctx.current) { + LOG_WRN("Block out of order %d, expected %d", new_current, + coap->block_ctx.current); + return -1; + } + + err = coap_update_from_block(pkt, &coap->block_ctx); + if (err) { + return err; + } + + if (dl->file_size == 0 && coap->block_ctx.total_size > 0) { + LOG_DBG("Total size: %d", coap->block_ctx.total_size); + dl->file_size = coap->block_ctx.total_size; + } + + *more = coap_next_block(pkt, &coap->block_ctx); + if (!*more) { + LOG_DBG("Last block received"); + } + + return 0; +} + +static int coap_parse(struct downloader *dl, size_t len) +{ + int err; + size_t blk_off; + uint8_t response_code; + uint16_t payload_len; + const uint8_t *payload; + struct coap_packet response; + bool more; + struct transport_params_coap *coap; + + coap = (struct transport_params_coap *)dl->transport_internal; + + /* TODO: currently we stop download on every error, but this is mostly not necessary + * and we can just request the same block again using retry mechanism + */ + + err = coap_packet_parse(&response, dl->config.buf, len, NULL, 0); + if (err) { + LOG_ERR("Failed to parse CoAP packet, err %d", err); + return -EBADMSG; + } + + if (coap_header_get_id(&response) != coap->pending.id) { + LOG_ERR("Response is not pending"); + return -EBADMSG; + } + + coap_pending_clear(&coap->pending); + + if (coap_header_get_type(&response) != COAP_TYPE_ACK) { + LOG_ERR("Response must be of coap type ACK"); + return -EBADMSG; + } + + response_code = coap_header_get_code(&response); + if (response_code != COAP_RESPONSE_CODE_OK && + response_code != COAP_RESPONSE_CODE_CONTENT) { + LOG_ERR("Server responded with code 0x%x", response_code); + return -EBADMSG; + } + + err = coap_block_update(dl, &response, &blk_off, &more); + if (err) { + return -EBADMSG; + } + + payload = coap_packet_get_payload(&response, &payload_len); + if (!payload) { + LOG_WRN("No CoAP payload!"); + return -EBADMSG; + } + + /* Accumulate buffer offset */ + dl->progress += payload_len; + dl->buf_offset = 0; + + dl_transport_evt_data(dl, (void *)payload, payload_len); + + if (!more) { + /* Mark the end, in case we did not know the total size */ + dl->file_size = dl->progress; + } + + coap->new_data_req = true; + return 0; +} + +static int coap_request_send(struct downloader *dl) +{ + int err; + uint16_t id; + char file[FILENAME_SIZE]; + char *path_elem; + char *path_elem_saveptr; + struct coap_packet request; + struct transport_params_coap *coap; + + coap = (struct transport_params_coap *)dl->transport_internal; + + if (has_pending(dl)) { + id = coap->pending.id; + } else { + id = coap_next_id(); + } + + err = coap_packet_init(&request, dl->config.buf, dl->config.buf_size, COAP_VER, + COAP_TYPE_CON, 8, coap_next_token(), COAP_METHOD_GET, id); + if (err) { + LOG_ERR("Failed to init CoAP message, err %d", err); + return err; + } + + err = dl_parse_url_file(dl->file, file, sizeof(file)); + if (err) { + LOG_ERR("Unable to parse url"); + return err; + } + + path_elem = strtok_r(file, COAP_PATH_ELEM_DELIM, &path_elem_saveptr); + do { + err = coap_packet_append_option(&request, COAP_OPTION_URI_PATH, + path_elem, strlen(path_elem)); + if (err) { + LOG_ERR("Unable add option to request"); + return err; + } + } while ((path_elem = strtok_r(NULL, COAP_PATH_ELEM_DELIM, &path_elem_saveptr))); + + err = coap_append_block2_option(&request, &coap->block_ctx); + if (err) { + LOG_ERR("Unable to add block2 option"); + return err; + } + + err = coap_append_size2_option(&request, &coap->block_ctx); + if (err) { + LOG_ERR("Unable to add size2 option"); + return err; + } + + if (!has_pending(dl)) { + struct coap_transmission_parameters params = coap_get_transmission_parameters(); + + params.max_retransmission = + CONFIG_DOWNLOADER_COAP_MAX_RETRANSMIT_REQUEST_COUNT; + err = coap_pending_init(&coap->pending, &request, &coap->sock.remote_addr, + ¶ms); + if (err < 0) { + return -EINVAL; + } + + coap_pending_cycle(&coap->pending); + } + + LOG_DBG("CoAP next block: %d", coap->block_ctx.current); + + err = dl_socket_send_timeout_set(coap->sock.fd, coap->pending.timeout); + if (err) { + return err; + } + + err = dl_socket_send(coap->sock.fd, dl->config.buf, request.offset); + if (err) { + LOG_ERR("Failed to send CoAP request, errno %d", errno); + return err; + } + + if (IS_ENABLED(CONFIG_DOWNLOADER_LOG_HEADERS)) { + LOG_HEXDUMP_DBG(request.data, request.offset, "CoAP request"); + } + + return 0; +} + +static bool dl_coap_proto_supported(struct downloader *dl, const char *uri) +{ + if (strncmp(uri, "coaps://", 8) == 0) { + return true; + } else if (strncmp(uri, "coap://", 7) == 0) { + return true; + } + + return false; +} + +static int dl_coap_init(struct downloader *dl, struct downloader_host_cfg *host_cfg, + const char *uri) +{ + int err; + struct transport_params_coap *coap; + + coap = (struct transport_params_coap *)dl->transport_internal; + memset(coap, 0, sizeof(struct transport_params_coap)); + + coap->sock.proto = IPPROTO_UDP; + coap->sock.type = SOCK_DGRAM; + + if (strncmp(uri, "coaps://", 8) == 0 || + (host_cfg->sec_tag_count != 0 && host_cfg->sec_tag_list != NULL)) { + coap->sock.proto = IPPROTO_DTLS_1_2; + coap->sock.type = SOCK_DGRAM; + + if (host_cfg->sec_tag_list == NULL || host_cfg->sec_tag_count == 0) { + LOG_WRN("No security tag provided for TLS/DTLS"); + return -EINVAL; + } + } + + err = dl_parse_url_port(uri, &coap->sock.port); + if (err) { + switch (coap->sock.proto) { + case IPPROTO_DTLS_1_2: + coap->sock.port = 5684; + break; + case IPPROTO_UDP: + coap->sock.port = 5683; + break; + } + LOG_DBG("Port not specified, using default: %d", coap->sock.port); + } + + if (host_cfg->set_native_tls) { + LOG_DBG("Enabled native TLS"); + coap->sock.type |= SOCK_NATIVE_TLS; + } + + return 0; +} + +static int dl_coap_deinit(struct downloader *dl) +{ + struct transport_params_coap *coap; + + coap = (struct transport_params_coap *)dl->transport_internal; + + if (coap->sock.fd != -1) { + dl_socket_close(&coap->sock.fd); + } + + return 0; +} + +static int dl_coap_connect(struct downloader *dl) +{ + int err; + struct transport_params_coap *coap; + + coap = (struct transport_params_coap *)dl->transport_internal; + + err = -1; + + err = dl_socket_configure_and_connect( + &coap->sock.fd, coap->sock.proto, coap->sock.type, coap->sock.port, + &coap->sock.remote_addr, dl->hostname, &dl->host_config); + if (err) { + goto cleanup; + } + + coap_block_init(dl, dl->progress); + +cleanup: + if (err) { + /* Unable to connect, close socket */ + dl_socket_close(&coap->sock.fd); + return err; + } + + coap->new_data_req = true; + + return err; +} + +static int dl_coap_close(struct downloader *dl) +{ + int err; + struct transport_params_coap *coap; + + coap = (struct transport_params_coap *)dl->transport_internal; + + if (coap->sock.fd != -1) { + err = dl_socket_close(&coap->sock.fd); + return err; + } + + return -EBADF; +} + +static int dl_coap_download(struct downloader *dl) +{ + int ret, len, timeout; + struct transport_params_coap *coap; + + coap = (struct transport_params_coap *)dl->transport_internal; + + if (coap->new_data_req) { + /* Request next fragment */ + dl->buf_offset = 0; + ret = coap_request_send(dl); + if (ret) { + LOG_DBG("data_req failed, err %d", ret); + /** Attempt reconnection. */ + return -ECONNRESET; + } + + coap->new_data_req = false; + } + + if (coap->retransmission_req) { + dl->buf_offset = 0; + ret = coap_initiate_retransmission(dl); + if (ret) { + LOG_DBG("retransmission_req failed, err %d", ret); + /** Attempt reconnection. */ + return -ECONNRESET; + } + + coap->retransmission_req = false; + } + + __ASSERT(dl->buf_offset < dl->config.buf_size, "Buffer overflow"); + + LOG_DBG("Receiving up to %d bytes at %p...", + (dl->config.buf_size - dl->buf_offset), + (void *)(dl->config.buf + dl->buf_offset)); + + timeout = coap_get_recv_timeout(dl); + if (!timeout) { + LOG_DBG("CoAP timeout"); + return -ETIMEDOUT; + } + + ret = dl_socket_recv_timeout_set(coap->sock.fd, timeout); + if (ret) { + LOG_DBG("Failed to set CoAP recv timeout, err %d", ret); + return ret; + } + + len = dl_socket_recv(coap->sock.fd, + dl->config.buf + dl->buf_offset, + dl->config.buf_size - dl->buf_offset); + if (len < 0) { + if ((len == ETIMEDOUT) || + (len == EWOULDBLOCK) || + (len == EAGAIN)) { + /* Request data again */ + coap->retransmission_req = true; + return 0; + } + + return len; + } + + ret = coap_parse(dl, len); + if (ret < 0) { + /* Request data again */ + coap->retransmission_req = true; + return 0; + } + + if (dl->progress == dl->file_size) { + dl_transport_evt_download_complete(dl); + } + + return 0; +} + +static struct dl_transport dl_transport_coap = { + .proto_supported = dl_coap_proto_supported, + .init = dl_coap_init, + .deinit = dl_coap_deinit, + .connect = dl_coap_connect, + .close = dl_coap_close, + .download = dl_coap_download, +}; + +DLC_TRANSPORT(coap, &dl_transport_coap); diff --git a/subsys/net/lib/downloader/src/transports/http.c b/subsys/net/lib/downloader/src/transports/http.c new file mode 100644 index 000000000000..8b8438c27843 --- /dev/null +++ b/subsys/net/lib/downloader/src/transports/http.c @@ -0,0 +1,569 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dl_socket.h" +#include "dl_parse.h" + +LOG_MODULE_DECLARE(downloader, CONFIG_DOWNLOADER_LOG_LEVEL); + +#define HOSTNAME_SIZE CONFIG_DOWNLOADER_MAX_HOSTNAME_SIZE +#define FILENAME_SIZE CONFIG_DOWNLOADER_MAX_FILENAME_SIZE + +/* nRF91 modem TLS secure socket buffer limited to 2kB including header */ +#define TLS_RANGE_MAX 2048 + +/* Request whole file; use with HTTP */ +#define HTTP_GET \ + "GET /%s HTTP/1.1\r\n" \ + "Host: %s\r\n" \ + "Connection: keep-alive\r\n" \ + "\r\n" + +/* Request remaining bytes from offset; use with HTTP */ +#define HTTP_GET_OFFSET \ + "GET /%s HTTP/1.1\r\n" \ + "Host: %s\r\n" \ + "Range: bytes=%u-\r\n" \ + "Connection: keep-alive\r\n" \ + "\r\n" + +/* Request a range of bytes; use with HTTPS due to modem limitations */ +#define HTTP_GET_RANGE \ + "GET /%s HTTP/1.1\r\n" \ + "Host: %s\r\n" \ + "Range: bytes=%u-%u\r\n" \ + "Connection: keep-alive\r\n" \ + "\r\n" + +struct transport_params_http { + /** 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; + + struct { + /** 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; + + /** Request new data */ + bool new_data_req; +}; + +BUILD_ASSERT(CONFIG_DOWNLOADER_TRANSPORT_PARAMS_SIZE >= sizeof(struct transport_params_http)); + + +/* Include size flavour of strstr for safety. */ +#if defined(CONFIG_EXTERNAL_LIBC) +/* Pull in memmem and strnstr due to being an extension to the C library and + * not included by default. + */ +extern void *memmem(const void *haystack, size_t hs_len, const void *needle, size_t ne_len); +extern size_t strnlen(const char *s, size_t maxlen); +static char *strnstr(const char *haystack, const char *needle, size_t haystack_len) +{ + if (!haystack || !needle) { + return NULL; + } + size_t needle_len = strnlen(needle, haystack_len); + + if (needle_len < haystack_len || !needle[needle_len]) { + char *x = memmem(haystack, haystack_len, needle, needle_len); + if (x && !memchr(haystack, 0, x - haystack)) + return x; + } + + return NULL; +} +#else +extern char *strnstr(const char *haystack, const char *needle, size_t haystack_sz); +#endif + +static int http_get_request_send(struct downloader *dl) +{ + int err; + int len; + size_t off = 0; + bool tls_force_range; + struct transport_params_http *http; + + http = (struct transport_params_http *)dl->transport_internal; + + http->header.has_end = false; + + /* nRF91 series has a limitation of decoding ~2k of data at once when using TLS */ + tls_force_range = (http->sock.proto == IPPROTO_TLS_1_2 && + !dl->host_config.set_native_tls && + IS_ENABLED(CONFIG_SOC_SERIES_NRF91X)); + + if (dl->host_config.range_override) { + if (tls_force_range && dl->host_config.range_override > (TLS_RANGE_MAX - 1)) { + LOG_WRN("Range override > TLS max range, setting to TLS max range"); + dl->host_config.range_override = (TLS_RANGE_MAX - 1); + } + } else if (tls_force_range) { + dl->host_config.range_override = TLS_RANGE_MAX - 1; + } + + if (dl->host_config.range_override) { + off = dl->progress + dl->host_config.range_override; + + if (dl->file_size && (off > dl->file_size - 1)) { + /* Don't request bytes past the end of file */ + off = dl->file_size - 1; + } + + len = snprintf(dl->config.buf, + dl->config.buf_size, + HTTP_GET_RANGE, dl->file, dl->hostname, dl->progress, off); + http->ranged = true; + http->ranged_progress = 0; + LOG_DBG("Range request up to %d bytes", dl->host_config.range_override); + goto send; + } else if (dl->progress) { + len = snprintf(dl->config.buf, + dl->config.buf_size, + HTTP_GET_OFFSET, dl->file, dl->hostname, dl->progress); + http->ranged = false; + } else { + len = snprintf(dl->config.buf, + dl->config.buf_size, + HTTP_GET, dl->file, dl->hostname); + http->ranged = false; + } + +send: + if (len < 0 || len > dl->config.buf_size) { + LOG_ERR("Cannot create GET request, buffer too small"); + return -ENOMEM; + } + + if (IS_ENABLED(CONFIG_DOWNLOADER_LOG_HEADERS)) { + LOG_HEXDUMP_DBG(dl->config.buf, len, "HTTP request"); + } + + LOG_DBG("http request:\n%s\n", dl->config.buf); + + err = dl_socket_send(http->sock.fd, dl->config.buf, len); + if (err) { + LOG_ERR("Failed to send HTTP request, errno %d", errno); + return err; + } + + return 0; +} + +/* Returns: + * Number of bytes parsed on success. + * Negative errno on error. + */ +static int http_header_parse(struct downloader *dl, size_t buf_len) +{ + char *p; + char *q; + size_t parse_len; + unsigned int expected_status; + struct transport_params_http *http; + + http = (struct transport_params_http *)dl->transport_internal; + + LOG_DBG("(partial) http header response:\n%s", dl->config.buf); + + p = strnstr(dl->config.buf, "\r\n\r\n", dl->config.buf_size); + if (p) { + /* End of header received */ + http->header.has_end = true; + parse_len = p + strlen("\r\n\r\n") - (char *)dl->config.buf; + } else { + parse_len = buf_len; + } + + for (size_t i = 0; i < parse_len; i++) { + dl->config.buf[i] = tolower(dl->config.buf[i]); + } + + /* Look for the status code just after "http/1.1 " */ + p = strnstr(dl->config.buf, "http/1.1 ", parse_len); + if (p) { + q = strnstr(p, "\r\n", parse_len - (p - dl->config.buf)); + if (q) { + /* Received entire line */ + p += strlen("http/1.1 "); + http->header.status_code = strtoul(p, &q, 10); + } + + } + + /* The file size is returned via "Content-Length" in case of HTTP, + * and via "Content-Range" in case of HTTPS with range requests. + */ + do { + if (dl->file_size == 0) { + if (http->ranged) { + p = strnstr(dl->config.buf, "\r\ncontent-range", parse_len); + if (!p) { + break; + } + p = strnstr(p, "/", parse_len - (p - dl->config.buf)); + if (!p) { + break; + } + q = strnstr(p, "\r\n", parse_len - (p - dl->config.buf)); + if (!q) { + /* Missing end of line */ + break; + } + } else { /* proto == PROTO_HTTP */ + p = strnstr(dl->config.buf, "\r\ncontent-length", parse_len); + if (!p) { + break; + } + p = strstr(p, ":"); + if (!p) { + break; + } + q = strnstr(p, "\r\n", parse_len - (p - dl->config.buf)); + if (!q) { + /* Missing end of line */ + break; + } + /* Accumulate any eventual progress (starting offset) + * when reading the file size from Content-Length + */ + dl->file_size = dl->progress; + } + + dl->file_size += atoi(p + 1); + LOG_DBG("File size = %u", dl->file_size); + } + } while (0); + + p = strnstr(dl->config.buf, "\r\nconnection: close", parse_len); + if (p) { + LOG_WRN("Peer closed connection, will re-connect"); + http->connection_close = true; + } + + if (http->header.has_end) { + /* We have received the end of the header. + * Verify that we have received everything that we need. + */ + + if (!http->header.status_code) { + LOG_ERR("Server response malformed: status code not found"); + return -EBADMSG; + } + + expected_status = (http->ranged || dl->progress) ? 206 : 200; + if (http->header.status_code != expected_status) { + LOG_ERR("Unexpected HTTP response code %ld", http->header.status_code); + return -EBADMSG; + } + + if (!dl->file_size) { + LOG_ERR("File size not set"); + return -EBADMSG; + } + + return parse_len; + } + + q = dl->config.buf + buf_len; + /* We are still missing part of the header. + * Return the lines (in number of bytes) that we have parsed. + */ + while (q > dl->config.buf && (*q != '\r') && (*q != '\n')) { + q--; + } + + /* Keep \r and \n in the buffer in case it is part of the header ending. */ + while (*(q - 1) == '\r' || *(q - 1) == '\n') { + q--; + } + + parse_len = (q - dl->config.buf); + + return parse_len; +} + +/* Returns: + * Length of data payload left to process on success + * Negative errno on error. + */ +static int http_parse(struct downloader *dl, size_t len) +{ + int parsed_len; + struct transport_params_http *http; + + http = (struct transport_params_http *)dl->transport_internal; + + if (!http->header.has_end) { + /* Parse what we can from the header */ + parsed_len = http_header_parse(dl, len); + if (parsed_len < 0) { + /* Something is wrong with the header */ + return -EBADMSG; + } + + if (parsed_len == len) { + dl->buf_offset = 0; + return 0; + } else if (parsed_len) { + /* Keep remaining payload */ + len = len - parsed_len; + memmove(dl->config.buf, dl->config.buf + parsed_len, len); + dl->buf_offset = len; + } + + if (!http->header.has_end) { + if (dl->config.buf_size == dl->buf_offset) { + LOG_ERR("Could not parse HTTP header lines from server (> %d)", + dl->config.buf_size); + return -E2BIG; + } + /* Wait for rest of header */ + return 0; + } + } + + /* Have we received a whole fragment or the whole file? */ + if (dl->progress + len != dl->file_size) { + if (http->ranged) { + http->ranged_progress += len; + if (http->ranged_progress < (dl->host_config.range_override ? + dl->host_config.range_override : + TLS_RANGE_MAX - 1)) { + /* Ranged query: read until a full fragment */ + return len; + } + } else { + /* Non-ranged query: just keep on reading, ignore fragment size */ + return len; + } + } + + /* Either we have a full file, or we need to request a next fragment */ + http->new_data_req = true; + return len; +} + +static bool dl_http_proto_supported(struct downloader *dl, const char *uri) +{ + if (strncmp(uri, "https://", 8) == 0) { + return true; + } + + if (strncmp(uri, "http://", 7) == 0) { + return true; + } + + return false; +} + +static int dl_http_init(struct downloader *dl, struct downloader_host_cfg *host_cfg, + const char *uri) +{ + int err; + struct transport_params_http *http; + + http = (struct transport_params_http *)dl->transport_internal; + memset(http, 0, sizeof(struct transport_params_http)); + + http->sock.proto = IPPROTO_TCP; + http->sock.type = SOCK_STREAM; + + if (strncmp(uri, "https://", 8) == 0 || + (host_cfg->sec_tag_count != 0 && host_cfg->sec_tag_list != NULL)) { + http->sock.proto = IPPROTO_TLS_1_2; + http->sock.type = SOCK_STREAM; + + if (host_cfg->sec_tag_list == NULL || host_cfg->sec_tag_count == 0) { + LOG_WRN("No security tag provided for TLS/DTLS"); + return -EINVAL; + } + } + + err = dl_parse_url_port(uri, &http->sock.port); + if (err) { + switch (http->sock.proto) { + case IPPROTO_TLS_1_2: + http->sock.port = 443; + break; + case IPPROTO_TCP: + http->sock.port = 80; + break; + } + LOG_DBG("Port not specified, using default: %d", http->sock.port); + } + + if (host_cfg->set_native_tls) { + LOG_DBG("Enabled native TLS"); + http->sock.type |= SOCK_NATIVE_TLS; + } + + return 0; +} + +static int dl_http_deinit(struct downloader *dl) +{ + struct transport_params_http *http; + + http = (struct transport_params_http *)dl->transport_internal; + + if (http->sock.fd != -1) { + dl_socket_close(&http->sock.fd); + } + + return 0; +} + +static int dl_http_connect(struct downloader *dl) +{ + int err; + struct transport_params_http *http; + + http = (struct transport_params_http *)dl->transport_internal; + + err = -1; + + err = dl_socket_configure_and_connect( + &http->sock.fd, http->sock.proto, http->sock.type, http->sock.port, + &http->sock.remote_addr, dl->hostname, &dl->host_config); + if (err) { + return err; + } + + err = dl_socket_recv_timeout_set(http->sock.fd, CONFIG_DOWNLOADER_HTTP_TIMEO_MS); + if (err) { + /* Unable to set timeout, close socket */ + LOG_ERR("Failed to set http recv timeout, err %d", err); + dl_socket_close(&http->sock.fd); + return err; + } + + http->connection_close = false; + http->new_data_req = true; + + return err; +} + +static int dl_http_close(struct downloader *dl) +{ + int err; + struct transport_params_http *http; + + http = (struct transport_params_http *)dl->transport_internal; + + if (http->sock.fd != -1) { + err = dl_socket_close(&http->sock.fd); + return err; + } + + memset(&http->sock.remote_addr, 0, sizeof(http->sock.remote_addr)); + + return -EBADF; +} + +static int dl_http_download(struct downloader *dl) +{ + int ret, len; + struct transport_params_http *http; + + http = (struct transport_params_http *)dl->transport_internal; + + if (http->connection_close) { + return -ECONNRESET; + } + + if (http->new_data_req) { + /* Request next fragment */ + dl->buf_offset = 0; + ret = http_get_request_send(dl); + if (ret) { + LOG_DBG("data_req failed, err %d", ret); + /** Attempt reconnection. */ + return -ECONNRESET; + } + + http->new_data_req = false; + } + + __ASSERT(dl->buf_offset < dl->config.buf_size, "Buffer overflow"); + + LOG_DBG("Receiving up to %d bytes at %p...", + (dl->config.buf_size - dl->buf_offset), + (void *)(dl->config.buf + dl->buf_offset)); + + len = dl_socket_recv(http->sock.fd, + dl->config.buf + dl->buf_offset, + dl->config.buf_size - dl->buf_offset); + + if (len < 0) { + return len; + } + + if (len == 0) { + return -ECONNRESET; + } + + ret = http_parse(dl, len); + if (ret <= 0) { + return ret; + } + + if (http->header.has_end) { + /* Accumulate progress */ + dl->progress += ret; + dl_transport_evt_data(dl, dl->config.buf, ret); + if (dl->progress == dl->file_size) { + dl_transport_evt_download_complete(dl); + } + dl->buf_offset = 0; + } + + return 0; +} + +static struct dl_transport dl_transport_http = { + .proto_supported = dl_http_proto_supported, + .init = dl_http_init, + .deinit = dl_http_deinit, + .connect = dl_http_connect, + .close = dl_http_close, + .download = dl_http_download, +}; + +DLC_TRANSPORT(http, &dl_transport_http); diff --git a/subsys/net/lib/fota_download/CMakeLists.txt b/subsys/net/lib/fota_download/CMakeLists.txt index 5f11d17efd40..eec5e8374d07 100644 --- a/subsys/net/lib/fota_download/CMakeLists.txt +++ b/subsys/net/lib/fota_download/CMakeLists.txt @@ -28,5 +28,5 @@ zephyr_library_sources_ifdef(CONFIG_DFU_TARGET_SMP zephyr_include_directories(./include) zephyr_include_directories_ifdef(CONFIG_SECURE_BOOT ${ZEPHYR_NRF_MODULE_DIR}/subsys/dfu/include) -zephyr_include_directories_ifdef(CONFIG_DOWNLOAD_CLIENT - ${ZEPHYR_NRF_MODULE_DIR}/subsys/net/lib/download_client/include) +zephyr_include_directories_ifdef(CONFIG_DOWNLOADER + ${ZEPHYR_NRF_MODULE_DIR}/subsys/net/lib/downloader/include) diff --git a/subsys/net/lib/fota_download/Kconfig b/subsys/net/lib/fota_download/Kconfig index 28c702d330b9..5c99846fa9a8 100644 --- a/subsys/net/lib/fota_download/Kconfig +++ b/subsys/net/lib/fota_download/Kconfig @@ -6,12 +6,12 @@ menuconfig FOTA_DOWNLOAD bool "FOTA Download" - depends on DOWNLOAD_CLIENT + depends on DOWNLOADER depends on DFU_TARGET select SYS_HASH_FUNC32 imply FW_INFO -if (FOTA_DOWNLOAD) +if FOTA_DOWNLOAD config FOTA_SOCKET_RETRIES int "Number of retries for socket-related download issues" @@ -31,6 +31,10 @@ config FOTA_DOWNLOAD_MCUBOOT_FLASH_BUF_SZ help Buffer size must be aligned to the minimal flash write block size +config FOTA_DOWNLOAD_BUF_SZ + int "Size of buffer used for downloader library" + default 2048 + config FOTA_DOWNLOAD_FULL_MODEM_BUF_SZ int "Size of buffer used for flash write operations during full modem updates" depends on DFU_TARGET_FULL_MODEM @@ -71,4 +75,4 @@ module-dep=LOG module-str=Firmware Over the Air Download source "${ZEPHYR_BASE}/subsys/logging/Kconfig.template.log_config" -endif # FOTA_DOWNLOAD +endif #FOTA_DOWNLOAD diff --git a/subsys/net/lib/fota_download/src/fota_download.c b/subsys/net/lib/fota_download/src/fota_download.c index 96aaa11cc618..fcb2b37db1af 100644 --- a/subsys/net/lib/fota_download/src/fota_download.c +++ b/subsys/net/lib/fota_download/src/fota_download.c @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include #include @@ -35,7 +35,16 @@ static const char *dl_host; static const char *dl_file; static uint32_t dl_host_hash; static uint32_t dl_file_hash; -static struct download_client dlc; + +static struct downloader dlc; +static int downloader_callback(const struct downloader_evt *event); +static char dlc_buf[CONFIG_FOTA_DOWNLOAD_BUF_SZ]; +static struct downloader_cfg dlc_config = { + .callback = downloader_callback, + .buf = dlc_buf, + .buf_size = sizeof(dlc_buf), +}; +static struct downloader_host_cfg host_config; /** SMP MCUBoot image type */ static bool use_smp_dfu_target; static struct k_work_delayable dlc_with_offset_work; @@ -138,7 +147,7 @@ static size_t file_size_get(size_t *size) *size = ext_file_sz; return 0; #endif - return download_client_file_size_get(&dlc, size); + return downloader_file_size_get(&dlc, size); } static size_t downloaded_size_get(size_t *size) @@ -147,18 +156,18 @@ static size_t downloaded_size_get(size_t *size) *size = ext_rcvd_sz; return 0; #endif - return download_client_downloaded_size_get(&dlc, size); + return downloader_downloaded_size_get(&dlc, size); } -static int disconnect(void) +static int dl_cancel(void) { #if defined(CONFIG_FOTA_DOWNLOAD_EXTERNAL_DL) return 0; #endif - return download_client_disconnect(&dlc); + return downloader_cancel(&dlc); } -static int download_client_callback(const struct download_client_evt *event) +static int downloader_callback(const struct downloader_evt *event) { static size_t file_size; size_t offset; @@ -169,7 +178,7 @@ static int download_client_callback(const struct download_client_evt *event) } switch (event->id) { - case DOWNLOAD_CLIENT_EVT_FRAGMENT: { + case DOWNLOADER_EVT_FRAGMENT: { if (atomic_test_and_clear_bit(&flags, FLAG_FIRST_FRAGMENT)) { err = file_size_get(&file_size); if (err != 0) { @@ -245,7 +254,7 @@ static int download_client_callback(const struct download_client_evt *event) * schedule new download from offset. */ atomic_set_bit(&flags, FLAG_RESUME); - (void)disconnect(); + (void)dl_cancel(); k_work_schedule(&dlc_with_offset_work, K_SECONDS(1)); LOG_INF("Refuse fragment, restart with offset"); @@ -291,7 +300,7 @@ static int download_client_callback(const struct download_client_evt *event) break; } - case DOWNLOAD_CLIENT_EVT_DONE: + case DOWNLOADER_EVT_DONE: err = dfu_target_done(true); if (err == 0 && IS_ENABLED(CONFIG_FOTA_CLIENT_AUTOSCHEDULE_UPDATE)) { err = dfu_target_schedule_update(0); @@ -303,14 +312,14 @@ static int download_client_callback(const struct download_client_evt *event) goto error_and_close; } - err = disconnect(); + err = dl_cancel(); if (err != 0) { set_error_state(FOTA_DOWNLOAD_ERROR_CAUSE_INTERNAL); goto error_and_close; } break; - case DOWNLOAD_CLIENT_EVT_ERROR: + case DOWNLOADER_EVT_ERROR: /* In case of socket errors we can return 0 to retry/continue, * or non-zero to stop */ @@ -319,15 +328,17 @@ static int download_client_callback(const struct download_client_evt *event) socket_retries_left); socket_retries_left--; /* Fall through and return 0 below to tell - * download_client to retry + * downloader to retry */ - } else if ((event->error == -ECONNABORTED) || (event->error == -ECONNREFUSED)) { + } else if ((event->error == -ECONNABORTED) || + (event->error == -ECONNREFUSED) || + (event->error == -EHOSTUNREACH)) { LOG_ERR("Download client failed to connect to server"); set_error_state(FOTA_DOWNLOAD_ERROR_CAUSE_CONNECT_FAILED); goto error_and_close; } else { - LOG_ERR("Download client error"); + LOG_ERR("Download client error event %d", event->error); err = dfu_target_done(false); if (err == -EACCES) { LOG_DBG("No DFU target was initialized"); @@ -339,13 +350,15 @@ static int download_client_callback(const struct download_client_evt *event) goto error_and_close; } break; - case DOWNLOAD_CLIENT_EVT_CLOSED: + case DOWNLOADER_EVT_STOPPED: atomic_set_bit(&flags, FLAG_CLOSED); /* Only clear flags if we are not going to resume */ if (!atomic_test_bit(&flags, FLAG_RESUME)) { stopped(); } break; + case DOWNLOADER_EVT_DEINITIALIZED: + /* Not implemented in fota download */ default: break; } @@ -354,7 +367,7 @@ static int download_client_callback(const struct download_client_evt *event) error_and_close: atomic_clear_bit(&flags, FLAG_RESUME); - (void)disconnect(); + (void)dl_cancel(); dfu_target_done(false); return -1; } @@ -366,7 +379,7 @@ static int get_from_offset(const size_t offset) return 0; } - int err = download_client_get(&dlc, dl_host, &dlc.config, dl_file, offset); + int err = downloader_get_with_host_and_path(&dlc, &host_config, dl_host, dl_file, offset); if (err != 0) { LOG_ERR("%s failed to start download with error %d", __func__, err); @@ -407,20 +420,6 @@ static void download_with_offset(struct k_work *unused) stop_and_clear_flags: stopped(); - return; -} - -static bool is_ip_address(const char *host) -{ - struct sockaddr sa; - - if (zsock_inet_pton(AF_INET, host, sa.data) == 1) { - return true; - } else if (zsock_inet_pton(AF_INET6, host, sa.data) == 1) { - return true; - } - - return false; } int fota_download_b1_file_parse(char *s0_s1_files) @@ -509,17 +508,19 @@ int fota_download_s0_active_get(bool *const s0_active) int fota_download_any(const char *host, const char *file, const int *sec_tag_list, uint8_t sec_tag_count, uint8_t pdn_id, size_t fragment_size) { - return fota_download(host, file, sec_tag_list, sec_tag_count, pdn_id, - fragment_size, DFU_TARGET_IMAGE_TYPE_ANY); + return fota_download(host, file, sec_tag_list, sec_tag_count, pdn_id, fragment_size, + DFU_TARGET_IMAGE_TYPE_ANY); } static void set_host_and_file(char const *const host, char const *const file) { - uint32_t host_hash = sys_hash32(host, strlen(host)); - uint32_t file_hash = sys_hash32(file, strlen(file)); + uint32_t host_hash; + uint32_t file_hash; + + host_hash = sys_hash32(host, strlen(host)); + file_hash = sys_hash32(file, strlen(file)); - LOG_DBG("URI checksums %d,%d,%d,%d\r\n", host_hash, file_hash, - dl_host_hash, dl_file_hash); + LOG_DBG("URI checksums %d,%d,%d,%d\r\n", host_hash, file_hash, dl_host_hash, dl_file_hash); /* Verify if the URI is same as last time, if not, prevent resuming. */ if (dl_host_hash != host_hash || dl_file_hash != file_hash) { @@ -536,9 +537,9 @@ static void set_host_and_file(char const *const host, char const *const file) } #if defined(CONFIG_FOTA_DOWNLOAD_EXTERNAL_DL) -int fota_download_external_evt_handle(struct download_client_evt const *const evt) +int fota_download_external_evt_handle(struct downloader_evt const *const evt) { - return download_client_callback(evt); + return downloader_callback(evt); } int fota_download_external_start(const char *host, const char *file, @@ -587,10 +588,8 @@ 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 = { - .pdn_id = pdn_id, - .frag_size_override = fragment_size, - }; + host_config.pdn_id = pdn_id; + host_config.range_override = fragment_size; if (sec_tag_count > ARRAY_SIZE(sec_tag_list_copy)) { return -E2BIG; @@ -605,12 +604,8 @@ int fota_download(const char *host, const char *file, if ((sec_tag_list != NULL) && (sec_tag_count > 0)) { memcpy(sec_tag_list_copy, sec_tag_list, sec_tag_count * sizeof(sec_tag_list[0])); - config.sec_tag_count = sec_tag_count; - config.sec_tag_list = sec_tag_list_copy; - - if (!is_ip_address(host)) { - config.set_tls_hostname = true; - } + host_config.sec_tag_count = sec_tag_count; + host_config.sec_tag_list = sec_tag_list_copy; } socket_retries_left = CONFIG_FOTA_SOCKET_RETRIES; @@ -636,18 +631,18 @@ int fota_download(const char *host, const char *file, atomic_set_bit(&flags, FLAG_FIRST_FRAGMENT); - err = download_client_get(&dlc, dl_host, &config, dl_file, 0); + err = downloader_get_with_host_and_path(&dlc, &host_config, dl_host, dl_file, 0); if (err != 0) { atomic_clear_bit(&flags, FLAG_DOWNLOADING); - (void)disconnect(); + (void)dl_cancel(); return err; } return 0; } -int fota_download_start(const char *host, const char *file, int sec_tag, - uint8_t pdn_id, size_t fragment_size) +int fota_download_start(const char *host, const char *file, int sec_tag, uint8_t pdn_id, + size_t fragment_size) { int sec_tag_list[1] = { sec_tag }; uint8_t sec_tag_count = sec_tag < 0 ? 0 : 1; @@ -662,28 +657,28 @@ int fota_download_start_with_image_type(const char *host, const char *file, int sec_tag_list[1] = { sec_tag }; uint8_t sec_tag_count = sec_tag < 0 ? 0 : 1; - return fota_download(host, file, sec_tag_list, sec_tag_count, pdn_id, - fragment_size, expected_type); + return fota_download(host, file, sec_tag_list, sec_tag_count, pdn_id, fragment_size, + expected_type); } static int fota_download_object_init(void) { int err; - k_work_init_delayable(&dlc_with_offset_work, download_with_offset); - - err = download_client_init(&dlc, download_client_callback); - if (err != 0) { - return err; - } - #ifdef CONFIG_FOTA_DOWNLOAD_NATIVE_TLS /* Enable native TLS for the download client socket * if configured. */ - dlc.set_native_tls = true; + host_config.native_tls = CONFIG_FOTA_DOWNLOAD_NATIVE_TLS; #endif + k_work_init_delayable(&dlc_with_offset_work, download_with_offset); + + err = downloader_init(&dlc, &dlc_config); + if (err != 0) { + return err; + } + initialized = true; return 0; } @@ -750,9 +745,9 @@ int fota_download_cancel(void) atomic_set_bit(&flags, FLAG_CANCEL); - err = disconnect(); + err = dl_cancel(); if (err) { - LOG_ERR("%s failed to disconnect: %d", __func__, err); + LOG_ERR("%s failed to stop download: %d", __func__, err); return err; } diff --git a/subsys/net/lib/fota_download/src/util/fota_download_delta_modem.c b/subsys/net/lib/fota_download/src/util/fota_download_delta_modem.c index efadf6b4a95f..c70a7dfd03e0 100644 --- a/subsys/net/lib/fota_download/src/util/fota_download_delta_modem.c +++ b/subsys/net/lib/fota_download/src/util/fota_download_delta_modem.c @@ -13,8 +13,7 @@ LOG_MODULE_REGISTER(fota_download_delta_modem, CONFIG_LOG_DEFAULT_LEVEL); /* Initialized to value different than success (0) */ static int dfu_result = -1; -NRF_MODEM_LIB_ON_DFU_RES(fota_delta_modem_dfu_res_hook, - on_modem_dfu_res, NULL); +NRF_MODEM_LIB_ON_DFU_RES(fota_delta_modem_dfu_res_hook, on_modem_dfu_res, NULL); static void on_modem_dfu_res(int dfu_res, void *ctx) { diff --git a/subsys/net/lib/fota_download/src/util/fota_download_full_modem.c b/subsys/net/lib/fota_download/src/util/fota_download_full_modem.c index 390bd45ff36f..fb363f965efe 100644 --- a/subsys/net/lib/fota_download/src/util/fota_download_full_modem.c +++ b/subsys/net/lib/fota_download/src/util/fota_download_full_modem.c @@ -70,7 +70,11 @@ int fota_download_full_modem_stream_params_init(void) const struct dfu_target_full_modem_params params = { .buf = fota_download_fulmodem_buf, .len = sizeof(fota_download_fulmodem_buf), - .dev = &(struct dfu_target_fmfu_fdev){ .dev = flash_dev, .offset = 0, .size = 0 } + .dev = &(struct dfu_target_fmfu_fdev){ + .dev = flash_dev, + .offset = 0, + .size = 0 + } }; ret = dfu_target_full_modem_cfg(¶ms); diff --git a/subsys/net/lib/fota_download/src/util/fota_download_util.c b/subsys/net/lib/fota_download/src/util/fota_download_util.c index 824af65e4f55..2a09bf135869 100644 --- a/subsys/net/lib/fota_download/src/util/fota_download_util.c +++ b/subsys/net/lib/fota_download/src/util/fota_download_util.c @@ -32,14 +32,12 @@ #include "fota_download_smp.h" #endif -#include "download_client_internal.h" - LOG_MODULE_REGISTER(fota_download_util, CONFIG_FOTA_DOWNLOAD_LOG_LEVEL); /** * @brief FOTA download url data. */ -struct fota_download_client_url_data { +struct fota_download_url_data { /** Host name */ const char *host; /** Host name length */ @@ -62,7 +60,7 @@ static bool download_active; static enum dfu_target_image_type active_dfu_type; int fota_download_parse_dual_resource_locator(char *const file, bool s0_active, - const char **selected_path) + const char **selected_path) { if (file == NULL || selected_path == NULL) { LOG_ERR("Got NULL pointer"); @@ -119,10 +117,10 @@ static void fota_download_callback(const struct fota_download_evt *evt) } } -static int fota_download_client_url_parse(const char *uri, - struct fota_download_client_url_data *parsed_uri) +static int fota_download_url_parse(const char *uri, + struct fota_download_url_data *parsed_uri) { - int len, err, proto, type; + int len; char *e, *s; len = strlen(uri); @@ -136,12 +134,6 @@ static int fota_download_client_url_parse(const char *uri, } s += strlen("://"); - /* Verify that download client knows the protocol */ - err = url_parse_proto(uri, &proto, &type); - if (err) { - return err; - } - /* Find the end of host name, which is start of path */ e = strchr(s, '/'); @@ -163,11 +155,11 @@ static int fota_download_client_url_parse(const char *uri, static int download_url_parse(const char *uri, int sec_tag) { int ret; - struct fota_download_client_url_data parsed_uri; + struct fota_download_url_data parsed_uri; LOG_INF("Download url %s", uri); - ret = fota_download_client_url_parse(uri, &parsed_uri); + ret = fota_download_url_parse(uri, &parsed_uri); if (ret) { return ret; } @@ -237,8 +229,8 @@ int fota_download_util_stream_init(void) } int fota_download_util_download_start(const char *download_uri, - enum dfu_target_image_type dfu_target_type, int sec_tag, - fota_download_callback_t client_callback) + enum dfu_target_image_type dfu_target_type, int sec_tag, + fota_download_callback_t client_callback) { int ret; diff --git a/subsys/net/lib/mcumgr_smp_client/Kconfig b/subsys/net/lib/mcumgr_smp_client/Kconfig index 9e35c72a62a1..80f07babeb5a 100644 --- a/subsys/net/lib/mcumgr_smp_client/Kconfig +++ b/subsys/net/lib/mcumgr_smp_client/Kconfig @@ -9,7 +9,7 @@ menuconfig NRF_MCUMGR_SMP_CLIENT select BASE64 select DFU_TARGET select DFU_TARGET_SMP - select DOWNLOAD_CLIENT + select DOWNLOADER select FOTA_DOWNLOAD select NET_BUF select ZCBOR diff --git a/subsys/net/lib/mcumgr_smp_client/src/mcumgr_smp_client_shell.c b/subsys/net/lib/mcumgr_smp_client/src/mcumgr_smp_client_shell.c index 85ba442bb5a5..d1fa7684f56b 100644 --- a/subsys/net/lib/mcumgr_smp_client/src/mcumgr_smp_client_shell.c +++ b/subsys/net/lib/mcumgr_smp_client/src/mcumgr_smp_client_shell.c @@ -56,7 +56,7 @@ static void fota_download_shell_callback(const struct fota_download_evt *evt) break; case FOTA_DOWNLOAD_EVT_FINISHED: - LOG_INF("FOTA download finished"); + LOG_INF("FOTA downloader finished"); active_download = false; break; } diff --git a/subsys/net/lib/nrf_cloud/Kconfig.nrf_cloud_fota b/subsys/net/lib/nrf_cloud/Kconfig.nrf_cloud_fota index 4a2d47c7a5bd..95a9c8779f9c 100644 --- a/subsys/net/lib/nrf_cloud/Kconfig.nrf_cloud_fota +++ b/subsys/net/lib/nrf_cloud/Kconfig.nrf_cloud_fota @@ -9,7 +9,7 @@ menuconfig NRF_CLOUD_FOTA select FOTA_DOWNLOAD select FOTA_DOWNLOAD_PROGRESS_EVT select DFU_TARGET - select DOWNLOAD_CLIENT + select DOWNLOADER select REBOOT select CJSON_LIB select SETTINGS diff --git a/subsys/net/lib/nrf_cloud/Kconfig.nrf_cloud_pgps b/subsys/net/lib/nrf_cloud/Kconfig.nrf_cloud_pgps index 7d3d68762fbb..78f712fa7ce3 100644 --- a/subsys/net/lib/nrf_cloud/Kconfig.nrf_cloud_pgps +++ b/subsys/net/lib/nrf_cloud/Kconfig.nrf_cloud_pgps @@ -8,7 +8,7 @@ menuconfig NRF_CLOUD_PGPS depends on MODEM_INFO depends on MODEM_INFO_ADD_NETWORK depends on DATE_TIME - imply DOWNLOAD_CLIENT + imply DOWNLOADER select STREAM_FLASH_ERASE select SETTINGS select CJSON_LIB @@ -111,7 +111,7 @@ config NRF_CLOUD_PGPS_DOWNLOAD_TRANSPORT_HTTP bool "Transport P-GPS prediction data over HTTP(S)" help Enabling this option will make the nRF Cloud P-GPS library use the - download_client library to download prediction data. + downloader library to download prediction data. config NRF_CLOUD_PGPS_DOWNLOAD_TRANSPORT_CUSTOM bool "Transport P-GPS data using application-supplied transport" 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 8115e5ad6064..a2219dc7021d 100644 --- a/subsys/net/lib/nrf_cloud/include/nrf_cloud_download.h +++ b/subsys/net/lib/nrf_cloud/include/nrf_cloud_download.h @@ -8,7 +8,7 @@ #define NRF_CLOUD_DOWNLOAD_H__ #include -#include +#include #ifdef __cplusplus extern "C" { @@ -19,7 +19,7 @@ enum nrf_cloud_download_type { /* Download a FOTA update using the fota_download library */ NRF_CLOUD_DL_TYPE_FOTA, - /* Download data using the download_client library */ + /* Download data using the download library */ NRF_CLOUD_DL_TYPE_DL_CLIENT, NRF_CLOUD_DL_TYPE_DL__LAST @@ -42,14 +42,14 @@ struct nrf_cloud_download_data { /* File download path */ const char *path; - /* Download client configuration */ - struct download_client_cfg dl_cfg; + /* Download client host configuration */ + struct downloader_host_cfg dlc_host_cfg; union { /* FOTA type data */ struct nrf_cloud_download_fota fota; /* Download client type data */ - struct download_client *dlc; + struct downloader *dlc; }; #if defined(CONFIG_NRF_CLOUD_COAP_DOWNLOADS) diff --git a/subsys/net/lib/nrf_cloud/include/nrf_cloud_pgps_utils.h b/subsys/net/lib/nrf_cloud/include/nrf_cloud_pgps_utils.h index 74920b73bf8d..38adc879b9da 100644 --- a/subsys/net/lib/nrf_cloud/include/nrf_cloud_pgps_utils.h +++ b/subsys/net/lib/nrf_cloud/include/nrf_cloud_pgps_utils.h @@ -84,7 +84,6 @@ int npgps_download_init(npgps_buffer_handler_t buf_handler, npgps_eot_handler_t int npgps_download_start(const char *host, const char *file, int sec_tag, uint8_t pdn_id, size_t fragment_size); - #ifdef __cplusplus } #endif diff --git a/subsys/net/lib/nrf_cloud/src/nrf_cloud_codec_internal.c b/subsys/net/lib/nrf_cloud/src/nrf_cloud_codec_internal.c index f5fff7523632..9deb0b3a526c 100644 --- a/subsys/net/lib/nrf_cloud/src/nrf_cloud_codec_internal.c +++ b/subsys/net/lib/nrf_cloud/src/nrf_cloud_codec_internal.c @@ -1983,6 +1983,7 @@ int nrf_cloud_fota_job_decode(struct nrf_cloud_fota_job_info *const job_info, job_info->host = json_strdup(cJSON_GetArrayItem(array, RCV_ITEM_IDX_FILE_HOST - offset)); job_info->path = json_strdup(cJSON_GetArrayItem(array, RCV_ITEM_IDX_FILE_PATH - offset)); + /* Get type and file size */ if ((job_info->host == NULL) || (job_info->path == NULL) || json_array_num_get(array, RCV_ITEM_IDX_FW_TYPE - offset, (int *)&job_info->type) || @@ -2012,7 +2013,6 @@ int nrf_cloud_fota_job_decode(struct nrf_cloud_fota_job_info *const job_info, job_info->host = NULL; nrf_cloud_free(job_info->path); job_info->path = NULL; - job_info->type = NRF_CLOUD_FOTA_TYPE__INVALID; } @@ -3555,6 +3555,7 @@ int nrf_cloud_pgps_response_decode(const char *const response, strncpy(result->path, path_ptr, result->path_sz); LOG_DBG("path: %s", result->path); + cleanup: if (rsp_obj) { cJSON_Delete(rsp_obj); 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 35e34c350714..c9482e8bcd04 100644 --- a/subsys/net/lib/nrf_cloud/src/nrf_cloud_download.c +++ b/subsys/net/lib/nrf_cloud/src/nrf_cloud_download.c @@ -70,7 +70,7 @@ static int coap_dl_connect_and_auth(void) return 0; } -static int fota_dl_evt_send(const struct download_client_evt *evt) +static int fota_dl_evt_send(const struct downloader_evt *evt) { #if defined(CONFIG_FOTA_DOWNLOAD_EXTERNAL_DL) return fota_download_external_evt_handle(evt); @@ -79,13 +79,13 @@ static int fota_dl_evt_send(const struct download_client_evt *evt) } static int coap_dl_event_send(struct nrf_cloud_download_data const *const dl, - const struct download_client_evt *const evt) + const struct downloader_evt *const evt) { /* Send events as if we are the downoad_client */ if (dl->type == NRF_CLOUD_DL_TYPE_FOTA) { return fota_dl_evt_send(evt); } else if (dl->type == NRF_CLOUD_DL_TYPE_DL_CLIENT) { - return dl->dlc->callback(evt); + return dl->dlc->config.callback(evt); } return -EINVAL; @@ -104,13 +104,13 @@ static void coap_dl_cb(int16_t result_code, size_t offset, const uint8_t *payloa bool send_done_evt = last_block; bool stop_on_err = false; struct nrf_cloud_download_data *dl = (struct nrf_cloud_download_data *)user_data; - struct download_client_evt evt = {0}; + struct downloader_evt evt = {0}; LOG_DBG("CoAP result: %d, offset: 0x%X, len: 0x%X, last_block: %d", result_code, offset, len, last_block); if (result_code == COAP_RESPONSE_CODE_CONTENT) { - evt.id = DOWNLOAD_CLIENT_EVT_FRAGMENT; + evt.id = DOWNLOADER_EVT_FRAGMENT; evt.fragment.buf = payload; evt.fragment.len = len; } else if (result_code == -ECANCELED) { @@ -118,14 +118,14 @@ static void coap_dl_cb(int16_t result_code, size_t offset, const uint8_t *payloa /* This is not actually an error, just use the error event to indicate that * the transfer has been canceled */ - evt.id = DOWNLOAD_CLIENT_EVT_ERROR; + evt.id = DOWNLOADER_EVT_ERROR; evt.error = -ECANCELED; (void)coap_dl_event_send(dl, &evt); return; } else if (result_code != COAP_RESPONSE_CODE_OK) { LOG_ERR("Unexpected CoAP result: %d", result_code); LOG_DBG("CoAP response: %.*s", len, payload); - evt.id = DOWNLOAD_CLIENT_EVT_ERROR; + evt.id = DOWNLOADER_EVT_ERROR; /* Use -ECONNRESET to trigger retry mechanism used by fota_download and * the P-GPS download event handler */ @@ -135,7 +135,7 @@ static void coap_dl_cb(int16_t result_code, size_t offset, const uint8_t *payloa ret = coap_dl_event_send(dl, &evt); - if (evt.id == DOWNLOAD_CLIENT_EVT_FRAGMENT) { + if (evt.id == DOWNLOADER_EVT_FRAGMENT) { if (ret == 0) { /* Fragment was successfully processed */ dl->coap_rcvd_bytes += len; @@ -164,7 +164,7 @@ static void coap_dl_cb(int16_t result_code, size_t offset, const uint8_t *payloa LOG_INF("Download complete"); memset(&evt, 0, sizeof(evt)); - evt.id = DOWNLOAD_CLIENT_EVT_DONE; + evt.id = DOWNLOADER_EVT_DONE; ret = coap_dl_event_send(dl, &evt); if (ret) { @@ -186,7 +186,7 @@ static void coap_dl_cb(int16_t result_code, size_t offset, const uint8_t *payloa if (send_closed_evt) { memset(&evt, 0, sizeof(evt)); - evt.id = DOWNLOAD_CLIENT_EVT_CLOSED; + evt.id = DOWNLOADER_EVT_STOPPED; (void)coap_dl_event_send(dl, &evt); } @@ -351,19 +351,19 @@ static void resume_work_fn(struct k_work *unused) /* On failure, send the events required to generate the error/done status */ if (ret) { - struct download_client_evt evt = {0}; + struct downloader_evt evt = {0}; LOG_ERR("Failed to resume CoAP download"); /* Send a non-recoverable error event (not ECONN) */ - evt.id = DOWNLOAD_CLIENT_EVT_ERROR; + evt.id = DOWNLOADER_EVT_ERROR; evt.error = -EIO; (void)coap_dl_event_send(&active_dl, &evt); /* Send a closed event to ensure the terminal fota_download event is generated */ if (active_dl.type == NRF_CLOUD_DL_TYPE_FOTA) { memset(&evt, 0, sizeof(evt)); - evt.id = DOWNLOAD_CLIENT_EVT_CLOSED; + evt.id = DOWNLOADER_EVT_STOPPED; (void)coap_dl_event_send(&active_dl, &evt); } } @@ -406,8 +406,8 @@ static int fota_start(struct nrf_cloud_download_data *const dl) #endif /* CONFIG_NRF_CLOUD_COAP_DOWNLOADS */ return fota_download_start_with_image_type(dl->host, dl->path, - dl->dl_cfg.sec_tag_count ? dl->dl_cfg.sec_tag_list[0] : -1, - dl->dl_cfg.pdn_id, dl->dl_cfg.frag_size_override, dl->fota.expected_type); + dl->dlc_host_cfg.sec_tag_count ? dl->dlc_host_cfg.sec_tag_list[0] : -1, + dl->dlc_host_cfg.pdn_id, dl->dlc_host_cfg.range_override, dl->fota.expected_type); #endif /* CONFIG_FOTA_DOWNLOAD */ @@ -417,13 +417,12 @@ static int fota_start(struct nrf_cloud_download_data *const dl) static int dlc_start(struct nrf_cloud_download_data *const dl) { __ASSERT(dl->dlc != NULL, "Download client is NULL"); - __ASSERT(dl->dlc->callback != NULL, "Download client callback is NULL"); #if defined(CONFIG_NRF_CLOUD_COAP_DOWNLOADS) return coap_dl(dl); #endif /* CONFIG_NRF_CLOUD_COAP_DOWNLOADS */ - return download_client_get(dl->dlc, dl->host, &dl->dl_cfg, dl->path, 0); + return downloader_get_with_host_and_path(dl->dlc, &dl->dlc_host_cfg, dl->host, dl->path, 0); } static int dlc_disconnect(struct nrf_cloud_download_data *const dl) @@ -432,7 +431,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 downloader_cancel(dl->dlc); } static void active_dl_reset(void) @@ -499,6 +498,8 @@ static bool check_fota_file_path_len(char const *const file_path) int nrf_cloud_download_start(struct nrf_cloud_download_data *const dl) { + int ret = 0; + if (!dl || !dl->path || (dl->type <= NRF_CLOUD_DL_TYPE_NONE) || (dl->type >= NRF_CLOUD_DL_TYPE_DL__LAST)) { return -EINVAL; @@ -522,8 +523,6 @@ int nrf_cloud_download_start(struct nrf_cloud_download_data *const dl) return -E2BIG; } - int ret = 0; - k_mutex_lock(&active_dl_mutex, K_FOREVER); /* FOTA has priority */ diff --git a/subsys/net/lib/nrf_cloud/src/nrf_cloud_fota.c b/subsys/net/lib/nrf_cloud/src/nrf_cloud_fota.c index 8ad69005d5df..79542f008426 100644 --- a/subsys/net/lib/nrf_cloud/src/nrf_cloud_fota.c +++ b/subsys/net/lib/nrf_cloud/src/nrf_cloud_fota.c @@ -873,11 +873,11 @@ static int start_job(struct nrf_cloud_fota_job *const job, const bool send_evt) .type = NRF_CLOUD_DL_TYPE_FOTA, .host = job->info.host, .path = job->info.path, - .dl_cfg = { + .dlc_host_cfg = { .sec_tag_list = &sec_tag, .sec_tag_count = (sec_tag < 0 ? 0 : 1), .pdn_id = 0, - .frag_size_override = CONFIG_NRF_CLOUD_FOTA_DOWNLOAD_FRAGMENT_SIZE, + .range_override = CONFIG_NRF_CLOUD_FOTA_DOWNLOAD_FRAGMENT_SIZE, }, .fota = { .expected_type = img_type, @@ -1118,11 +1118,10 @@ static int handle_mqtt_evt_publish(const struct mqtt_evt *evt) LOG_INF("Job %s already completed... skipping", last_job); nrf_cloud_fota_job_free(job_info); } else { - LOG_DBG("Job ID: %s, type: %d, size: %d", + LOG_DBG("Job ID: %s, type: %d, size: %d, file: %s/%s", job_info->id, job_info->type, - job_info->file_size); - LOG_DBG("File: %s/%s", + job_info->file_size, job_info->host, job_info->path); } diff --git a/subsys/net/lib/nrf_cloud/src/nrf_cloud_fota_poll.c b/subsys/net/lib/nrf_cloud/src/nrf_cloud_fota_poll.c index c506edb4fa07..daa46b969d05 100644 --- a/subsys/net/lib/nrf_cloud/src/nrf_cloud_fota_poll.c +++ b/subsys/net/lib/nrf_cloud/src/nrf_cloud_fota_poll.c @@ -462,19 +462,20 @@ static int start_download(void) } LOG_INF("Starting FOTA download of %s/%s", job.host, job.path); - sec_tag = nrf_cloud_sec_tag_get(); struct nrf_cloud_download_data dl = { .type = NRF_CLOUD_DL_TYPE_FOTA, .host = job.host, .path = job.path, - .dl_cfg = { + .dlc_host_cfg = { .sec_tag_list = &sec_tag, .sec_tag_count = (sec_tag < 0 ? 0 : 1), .pdn_id = 0, - .frag_size_override = ctx_ptr->fragment_size ? ctx_ptr->fragment_size : - CONFIG_NRF_CLOUD_FOTA_DOWNLOAD_FRAGMENT_SIZE, + .range_override = + ctx_ptr->fragment_size + ? ctx_ptr->fragment_size + : CONFIG_NRF_CLOUD_FOTA_DOWNLOAD_FRAGMENT_SIZE, }, .fota = { .expected_type = ctx_ptr->img_type, diff --git a/subsys/net/lib/nrf_cloud/src/nrf_cloud_pgps.c b/subsys/net/lib/nrf_cloud/src/nrf_cloud_pgps.c index 10d49943e9dc..9e8c1c10edb4 100644 --- a/subsys/net/lib/nrf_cloud/src/nrf_cloud_pgps.c +++ b/subsys/net/lib/nrf_cloud/src/nrf_cloud_pgps.c @@ -864,14 +864,16 @@ static int pgps_request_all(void) /* handle incoming P-GPS response packets */ int nrf_cloud_pgps_process(const char *buf, size_t buf_len) { - static char host[CONFIG_DOWNLOAD_CLIENT_MAX_HOSTNAME_SIZE]; - static char path[CONFIG_DOWNLOAD_CLIENT_MAX_FILENAME_SIZE]; int err; + static char host[CONFIG_DOWNLOADER_MAX_HOSTNAME_SIZE]; + static char path[CONFIG_DOWNLOADER_MAX_FILENAME_SIZE]; + struct nrf_cloud_pgps_result pgps_dl = { .host = host, .host_sz = sizeof(host), .path = path, .path_sz = sizeof(path) + }; #if defined(CONFIG_NRF_CLOUD_MQTT) @@ -921,11 +923,13 @@ int nrf_cloud_pgps_update(struct nrf_cloud_pgps_result *file_location) memmove(&file_location->host[4], &file_location->host[5], strlen(&file_location->host[4])); + sec_tag = -1; } err = npgps_download_start(file_location->host, file_location->path, sec_tag, 0, FRAGMENT_SIZE); + if (err) { state = PGPS_REQUEST_NEEDED; /* Will try again next time. */ } 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 eab4e8cd83dc..da992edc2b99 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 @@ -47,13 +47,20 @@ static struct nrf_cloud_pgps_header saved_header; static K_SEM_DEFINE(dl_active, 1, 1); -static struct download_client dlc; +static char dlc_buf[2048]; +static struct downloader dlc; +static int downloader_callback(const struct downloader_evt *event); +static struct downloader_cfg dlc_config = { + .callback = downloader_callback, + .buf = dlc_buf, + .buf_size = sizeof(dlc_buf), +}; + static int sec_tag_list[1]; static int socket_retries_left; static npgps_buffer_handler_t buffer_handler; static npgps_eot_handler_t eot_handler; -static int download_client_callback(const struct download_client_evt *event); static int settings_set(const char *key, size_t len_rd, settings_read_cb read_cb, void *cb_arg); @@ -473,15 +480,13 @@ int npgps_download_init(npgps_buffer_handler_t buf_handler, npgps_eot_handler_t buffer_handler = buf_handler; eot_handler = end_handler; - return download_client_init(&dlc, download_client_callback); + return downloader_init(&dlc, &dlc_config); } int npgps_download_start(const char *host, const char *file, int sec_tag, uint8_t pdn_id, size_t fragment_size) { - if (host == NULL || file == NULL) { - return -EINVAL; - } + int err; #if defined(CONFIG_NET_IPV6) && defined(CONFIG_NET_IPV4) int family = AF_UNSPEC; @@ -492,27 +497,29 @@ int npgps_download_start(const char *host, const char *file, int sec_tag, #else int family = AF_UNSPEC; #endif /* defined(CONFIG_NET_IPV6) && defined(CONFIG_NET_IPV4) */ - int err; + + if (host == NULL || file == NULL) { + return -EINVAL; + } + struct nrf_cloud_download_data dl = { .type = NRF_CLOUD_DL_TYPE_DL_CLIENT, .host = host, .path = file, - .dl_cfg = { + .dlc_host_cfg = { .sec_tag_count = 0, .sec_tag_list = NULL, .pdn_id = pdn_id, - .frag_size_override = fragment_size, - .set_tls_hostname = false, - .family = family + .range_override = fragment_size, + .family = family, }, .dlc = &dlc }; if (sec_tag != -1) { sec_tag_list[0] = sec_tag; - dl.dl_cfg.sec_tag_list = sec_tag_list; - dl.dl_cfg.sec_tag_count = 1; - dl.dl_cfg.set_tls_hostname = true; + dl.dlc_host_cfg.sec_tag_list = sec_tag_list; + dl.dlc_host_cfg.sec_tag_count = 1; } socket_retries_left = SOCKET_RETRIES; @@ -526,7 +533,7 @@ int npgps_download_start(const char *host, const char *file, int sec_tag, return err; } -static int download_client_callback(const struct download_client_evt *event) +static int downloader_callback(const struct downloader_evt *event) { int err = 0; @@ -535,17 +542,17 @@ static int download_client_callback(const struct download_client_evt *event) } switch (event->id) { - case DOWNLOAD_CLIENT_EVT_FRAGMENT: + case DOWNLOADER_EVT_FRAGMENT: err = buffer_handler((uint8_t *)event->fragment.buf, event->fragment.len); if (!err) { return 0; } break; - case DOWNLOAD_CLIENT_EVT_DONE: + case DOWNLOADER_EVT_DONE: LOG_DBG("Download client done"); break; - case DOWNLOAD_CLIENT_EVT_ERROR: { + case DOWNLOADER_EVT_ERROR: { if ((event->error == -ECANCELED) && IS_ENABLED(CONFIG_NRF_CLOUD_COAP_DOWNLOADS)) { eot_handler(event->error); return 0; @@ -568,9 +575,11 @@ static int download_client_callback(const struct download_client_evt *event) return 0; } - /* CoAP downloads do not need to disconnect since they don't directly use download_client */ + /* CoAP downloads do not need to disconnect since they don't directly use downloader */ #if !defined(CONFIG_NRF_CLOUD_COAP_DOWNLOADS) - int ret = download_client_disconnect(&dlc); + int ret; + + ret = downloader_cancel(&dlc); if (ret) { LOG_ERR("Error disconnecting from download client:%d", ret); diff --git a/tests/subsys/net/lib/aws_fota/aws_fota_json/CMakeLists.txt b/tests/subsys/net/lib/aws_fota/aws_fota_json/CMakeLists.txt index 04f281620220..2aaa8852fdc7 100644 --- a/tests/subsys/net/lib/aws_fota/aws_fota_json/CMakeLists.txt +++ b/tests/subsys/net/lib/aws_fota/aws_fota_json/CMakeLists.txt @@ -30,6 +30,6 @@ target_include_directories(app # is not executed. Hence these can not be set through prj.conf. target_compile_options(app PRIVATE - -DCONFIG_DOWNLOAD_CLIENT_MAX_HOSTNAME_SIZE=1024 - -DCONFIG_DOWNLOAD_CLIENT_MAX_FILENAME_SIZE=1024 + -DCONFIG_DOWNLOADER_MAX_HOSTNAME_SIZE=1024 + -DCONFIG_DOWNLOADER_MAX_FILENAME_SIZE=1024 ) diff --git a/tests/subsys/net/lib/downloader/CMakeLists.txt b/tests/subsys/net/lib/downloader/CMakeLists.txt new file mode 100644 index 000000000000..72319f0532b2 --- /dev/null +++ b/tests/subsys/net/lib/downloader/CMakeLists.txt @@ -0,0 +1,50 @@ +# +# Copyright (c) 2024 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(downloader) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) + +test_runner_generate(src/main.c) + +target_sources(app + PRIVATE + ${ZEPHYR_NRF_MODULE_DIR}/subsys/net/lib/downloader/src/downloader.c + ${ZEPHYR_NRF_MODULE_DIR}/subsys/net/lib/downloader/src/dl_socket.c + ${ZEPHYR_NRF_MODULE_DIR}/subsys/net/lib/downloader/src/dl_parse.c + ${ZEPHYR_NRF_MODULE_DIR}/subsys/net/lib/downloader/src/sanity.c + ${ZEPHYR_NRF_MODULE_DIR}/subsys/net/lib/downloader/src/transports/coap.c + ${ZEPHYR_NRF_MODULE_DIR}/subsys/net/lib/downloader/src/transports/http.c +) + +zephyr_include_directories(${ZEPHYR_NRF_MODULE_DIR}/include/net/) +zephyr_include_directories(${ZEPHYR_NRF_MODULE_DIR}/subsys/net/lib/downloader/include/) +zephyr_include_directories(${ZEPHYR_BASE}/subsys/net/ip/) +zephyr_include_directories(${ZEPHYR_BASE}/subsys/net/lib/sockets) +zephyr_include_directories(${ZEPHYR_BASE}/subsys/testsuite/include) + +zephyr_linker_sources(RODATA ${ZEPHYR_NRF_MODULE_DIR}/subsys/net/lib/downloader/dl_transports.ld) + +target_compile_options(app + PRIVATE + -DCONFIG_DOWNLOADER_MAX_HOSTNAME_SIZE=256 + -DCONFIG_DOWNLOADER_MAX_FILENAME_SIZE=256 + -DCONFIG_DOWNLOADER_TRANSPORT_PARAMS_SIZE=128 + -DCONFIG_DOWNLOADER_HTTP_TIMEO_MS=1000 + -DCONFIG_DOWNLOADER_COAP_MAX_RETRANSMIT_REQUEST_COUNT=10 + -DCONFIG_NET_SOCKETS_POSIX_NAMES=y + -DCONFIG_DOWNLOADER_STACK_SIZE=2048 + -DCONFIG_NET_IPV6=y + -DCONFIG_NET_IPV4=y + -DCONFIG_COAP_MAX_RETRANSMIT=2 + -DCONFIG_COAP_INIT_ACK_TIMEOUT_MS=100 + -DCONFIG_COAP_BACKOFF_PERCENT=5 + -DCONFIG_COAP_BLOCK_SIZE=5 +) diff --git a/tests/subsys/net/lib/downloader/boards/native_sim.conf b/tests/subsys/net/lib/downloader/boards/native_sim.conf new file mode 100644 index 000000000000..be6932dcefe1 --- /dev/null +++ b/tests/subsys/net/lib/downloader/boards/native_sim.conf @@ -0,0 +1,5 @@ +CONFIG_ASAN=y +CONFIG_NET_TCP=y +CONFIG_NET_TCP_ISN_RFC6528=n +CONFIG_NET_UDP=y +CONFIG_MBEDTLS=n diff --git a/tests/subsys/net/lib/downloader/boards/qemu_cortex_m3.conf b/tests/subsys/net/lib/downloader/boards/qemu_cortex_m3.conf new file mode 100644 index 000000000000..5901b68d74da --- /dev/null +++ b/tests/subsys/net/lib/downloader/boards/qemu_cortex_m3.conf @@ -0,0 +1,5 @@ +CONFIG_NET_TCP=y +CONFIG_NET_TCP_ISN_RFC6528=n +CONFIG_NET_UDP=y +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_NET_SLIP_TAP=n diff --git a/tests/subsys/net/lib/downloader/prj.conf b/tests/subsys/net/lib/downloader/prj.conf new file mode 100644 index 000000000000..6f759a27f5c5 --- /dev/null +++ b/tests/subsys/net/lib/downloader/prj.conf @@ -0,0 +1,7 @@ +# +# Copyright (c) 2022-2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +CONFIG_UNITY=y +CONFIG_PIPES=y diff --git a/tests/subsys/net/lib/downloader/src/main.c b/tests/subsys/net/lib/downloader/src/main.c new file mode 100644 index 000000000000..ae6696a3d328 --- /dev/null +++ b/tests/subsys/net/lib/downloader/src/main.c @@ -0,0 +1,1373 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include + +#include +#include +#include + +#include +#include +#include + +#define HOSTNAME "server.com" + +#define NO_PROTO_URI "server.com/path/to/file.end" +#define HTTP_URI "http://server.com/path/to/file.end" +#define HTTPS_URI "https://server.com/path/to/file.end" +#define COAP_URI "coap://server.com/path/to/file.end" +#define COAPS_URI "coaps://server.com/path/to/file.end" +#define BAD_URI "bad://server.com/path/to/file.end" +#define BAD_URI_NO_FILE "http://server.com" + +#define HTTP_HDR_OK "HTTP/1.1 200 OK\r\n" \ +"Accept-Ranges: bytes\r\n" \ +"Age: 497805\r\n" \ +"Cache-Control: max-age=604800\r\n" \ +"Content-Encoding: gzip\r\n" \ +"Content-Length: 128\r\n" \ +"Content-Type: text/html; charset=UTF-8\r\n" \ +"Date: Wed, 06 Nov 2024 13:00:48 GMT\r\n" \ +"Etag: \"3147526947\"\r\n" \ +"Expires: Wed, 23 Nov 2124 23:12:95 GMT\r\n" \ +"Last-Modified: Thu, 06 Nov 2024 14:17:23 GMT\r\n" \ +"Server: ECAcc (nyd/D184)\r\n" \ +"Vary: Accept-Encoding\r\n" \ +"X-Cache: HIT\r\n\r\n" + +#define HTTP_HDR_OK_WITH_PAYLOAD "HTTP/1.1 200 OK\r\n" \ +"Accept-Ranges: bytes\r\n" \ +"Age: 497805\r\n" \ +"Cache-Control: max-age=604800\r\n" \ +"Content-Encoding: gzip\r\n" \ +"Content-Length: 20\r\n" \ +"Content-Type: text/html; charset=UTF-8\r\n" \ +"Date: Wed, 06 Nov 2024 13:00:48 GMT\r\n" \ +"Etag: \"3147526947\"\r\n" \ +"Expires: Wed, 23 Nov 2124 23:12:95 GMT\r\n" \ +"Last-Modified: Thu, 06 Nov 2024 14:17:23 GMT\r\n" \ +"Server: ECAcc (nyd/D184)\r\n" \ +"Vary: Accept-Encoding\r\n" \ +"X-Cache: HIT\r\n\r\n"\ +"This is the payload!" + +#define HTTP_HDR_OK_PROGRESS "HTTP/1.1 206 OK\r\n" \ +"Accept-Ranges: bytes\r\n" \ +"Age: 497805\r\n" \ +"Cache-Control: max-age=604800\r\n" \ +"Content-Encoding: gzip\r\n" \ +"Content-Length: 128\r\n" \ +"Content-Type: text/html; charset=UTF-8\r\n" \ +"Date: Wed, 06 Nov 2024 13:00:48 GMT\r\n" \ +"Etag: \"3147526947\"\r\n" \ +"Expires: Wed, 23 Nov 2124 23:12:95 GMT\r\n" \ +"Last-Modified: Thu, 06 Nov 2024 14:17:23 GMT\r\n" \ +"Server: ECAcc (nyd/D184)\r\n" \ +"Vary: Accept-Encoding\r\n" \ +"X-Cache: HIT\r\n\r\n" + +#define PAYLOAD "This is the payload!" + +#define FD 0 + +static int dlc_callback(const struct downloader_evt *event); +static int dlc_callback_abort(const struct downloader_evt *event); + +static struct downloader dlc; +char dlc_buf[2048]; +struct downloader_cfg dlc_config = { + .callback = dlc_callback, + .buf = dlc_buf, + .buf_size = sizeof(dlc_buf), +}; + +struct downloader_cfg dlc_config_cb_abort = { + .callback = dlc_callback_abort, + .buf = dlc_buf, + .buf_size = sizeof(dlc_buf), +}; + +static struct downloader_host_cfg dlc_host_cfg = { + .pdn_id = 0, +}; + +int sec_tags[] = {1, 2, 3}; +static struct downloader_host_cfg dlc_host_cfg_w_sec_tags = { + .pdn_id = 0, + .sec_tag_list = sec_tags, + .sec_tag_count = ARRAY_SIZE(sec_tags), +}; + +DEFINE_FFF_GLOBALS; + +FAKE_VALUE_FUNC(int, z_impl_zsock_setsockopt, int, int, int, const void *, socklen_t); +FAKE_VALUE_FUNC(int, z_impl_zsock_socket, int, int, int); +FAKE_VALUE_FUNC(int, z_impl_zsock_connect, int, const struct sockaddr *, socklen_t); +FAKE_VALUE_FUNC(int, z_impl_zsock_close, int) +FAKE_VALUE_FUNC(ssize_t, z_impl_zsock_send, int, const void *, size_t, int) +FAKE_VALUE_FUNC(ssize_t, z_impl_zsock_recv, int, void *, size_t, int) +FAKE_VALUE_FUNC(int, zsock_getaddrinfo, const char *, const char *, const struct zsock_addrinfo *, + struct zsock_addrinfo **) +FAKE_VOID_FUNC(zsock_freeaddrinfo, struct zsock_addrinfo *); + +FAKE_VALUE_FUNC(int, z_impl_zsock_inet_pton, sa_family_t, const char *, void *) +FAKE_VALUE_FUNC(char *, z_impl_net_addr_ntop, sa_family_t, const void *, char *, size_t) +FAKE_VALUE_FUNC(ssize_t, z_impl_zsock_sendto, int, const void *, size_t, int, + const struct sockaddr *, socklen_t); +FAKE_VALUE_FUNC(ssize_t, z_impl_zsock_recvfrom, int, void *, size_t, int, struct sockaddr *, + socklen_t *); +FAKE_VOID_FUNC(z_impl_sys_rand_get, void *, size_t); + +FAKE_VALUE_FUNC(int, coap_get_option_int, const struct coap_packet *, uint16_t); +FAKE_VALUE_FUNC(int, coap_block_transfer_init, struct coap_block_context *, enum coap_block_size, + size_t); +FAKE_VOID_FUNC(coap_pending_clear, struct coap_pending *); +FAKE_VALUE_FUNC(bool, coap_pending_cycle, struct coap_pending *); +FAKE_VALUE_FUNC(int, coap_update_from_block, const struct coap_packet *, + struct coap_block_context *); +FAKE_VALUE_FUNC(size_t, coap_next_block, const struct coap_packet *, struct coap_block_context *); +FAKE_VALUE_FUNC(int, coap_packet_parse, struct coap_packet *, uint8_t *, uint16_t, + struct coap_option *, uint8_t); +FAKE_VALUE_FUNC(uint16_t, coap_header_get_id, const struct coap_packet *); +FAKE_VALUE_FUNC(uint8_t, coap_header_get_type, const struct coap_packet *); +FAKE_VALUE_FUNC(uint8_t, coap_header_get_code, const struct coap_packet *); +FAKE_VALUE_FUNC(const uint8_t *, coap_packet_get_payload, const struct coap_packet *, uint16_t *); +FAKE_VALUE_FUNC(int, coap_packet_init, struct coap_packet *, uint8_t *, uint16_t, uint8_t, uint8_t, + uint8_t, const uint8_t *, uint8_t, uint16_t); +FAKE_VALUE_FUNC(uint8_t *, coap_next_token); +FAKE_VALUE_FUNC(int, coap_packet_append_option, struct coap_packet *, uint16_t, const uint8_t *, + uint16_t); +FAKE_VALUE_FUNC(int, coap_append_block2_option, struct coap_packet *, struct coap_block_context *); +FAKE_VALUE_FUNC(int, coap_append_size2_option, struct coap_packet *, struct coap_block_context *); +FAKE_VALUE_FUNC(struct coap_transmission_parameters, coap_get_transmission_parameters); +FAKE_VALUE_FUNC(int, coap_pending_init, struct coap_pending *, const struct coap_packet *, + const struct sockaddr *, const struct coap_transmission_parameters *); + +uint16_t message_id; +uint16_t coap_next_id(void) +{ + return message_id++; +} + +static struct sockaddr server_sockaddr = { + .sa_family = AF_INET, +}; + +static struct zsock_addrinfo server_addrinfo = { + .ai_addr = &server_sockaddr, + .ai_addrlen = sizeof(struct sockaddr), +}; + +static struct sockaddr server_sockaddr6 = { + .sa_family = AF_INET6, +}; + +static struct zsock_addrinfo server_addrinfo6 = { + .ai_addr = &server_sockaddr6, + .ai_addrlen = sizeof(struct sockaddr), +}; + +int zsock_getaddrinfo_server_ok(const char *host, const char *service, + const struct zsock_addrinfo *hints, + struct zsock_addrinfo **res) +{ + TEST_ASSERT_EQUAL_STRING(HOSTNAME, host); + TEST_ASSERT_EQUAL_PTR(NULL, service); + + if (hints->ai_family == AF_INET) { + *res = &server_addrinfo; + return 0; + } else if (hints->ai_family == AF_INET6) { + *res = &server_addrinfo6; + return 0; + } + + errno = ENOPROTOOPT; + return EAI_SYSTEM; +} + + +int zsock_getaddrinfo_server_ipv6_fail_ipv4_ok(const char *host, const char *service, + const struct zsock_addrinfo *hints, + struct zsock_addrinfo **res) +{ + if (hints->ai_family == AF_INET6) { + /* Fail on IPv6 to retry IPv4 */ + errno = ENOPROTOOPT; + return EAI_SYSTEM; + } + + TEST_ASSERT_EQUAL_STRING(HOSTNAME, host); + TEST_ASSERT_EQUAL_PTR(NULL, service); + TEST_ASSERT_EQUAL(AF_INET, hints->ai_family); + *res = &server_addrinfo; + + return 0; +} + +int zsock_getaddrinfo_server_enetunreach(const char *host, const char *service, + const struct zsock_addrinfo *hints, + struct zsock_addrinfo **res) +{ + errno = ENETUNREACH; + return EAI_SYSTEM; +} + +void zsock_freeaddrinfo_server_ipv4(struct zsock_addrinfo *addr) +{ + TEST_ASSERT_EQUAL_PTR(&server_addrinfo, addr); +} + +void zsock_freeaddrinfo_server_ipv6(struct zsock_addrinfo *addr) +{ + TEST_ASSERT_EQUAL_PTR(&server_addrinfo6, addr); +} + +void zsock_freeaddrinfo_server_ipv6_then_ipv4(struct zsock_addrinfo *addr) +{ + switch (zsock_freeaddrinfo_fake.call_count) { + case 1: + TEST_ASSERT_EQUAL_PTR(&server_addrinfo6, addr); + break; + case 2: + TEST_ASSERT_EQUAL_PTR(&server_addrinfo, addr); + default: + + } + +} + +int z_impl_zsock_socket_http_ipv4_ok(int family, int type, int proto) +{ + TEST_ASSERT_EQUAL(AF_INET, family); + TEST_ASSERT_EQUAL(SOCK_STREAM, type); + TEST_ASSERT_EQUAL(IPPROTO_TCP, proto); + + return FD; +} + +int z_impl_zsock_socket_http_ipv6_ok(int family, int type, int proto) +{ + TEST_ASSERT_EQUAL(AF_INET6, family); + TEST_ASSERT_EQUAL(SOCK_STREAM, type); + TEST_ASSERT_EQUAL(IPPROTO_TCP, proto); + + return FD; +} + +int z_impl_zsock_socket_http_ipv6_then_ipv4(int family, int type, int proto) +{ + switch (z_impl_zsock_socket_fake.call_count) { + case 1: + TEST_ASSERT_EQUAL(AF_INET6, family); + break; + case 2: + default: + TEST_ASSERT_EQUAL(AF_INET, family); + } + + TEST_ASSERT_EQUAL(SOCK_STREAM, type); + TEST_ASSERT_EQUAL(IPPROTO_TCP, proto); + + return FD; +} + +int z_impl_zsock_socket_https_ipv6_ok(int family, int type, int proto) +{ + TEST_ASSERT_EQUAL(AF_INET6, family); + TEST_ASSERT_EQUAL(SOCK_STREAM, type); + TEST_ASSERT_EQUAL(IPPROTO_TLS_1_2, proto); + + return FD; +} + +int z_impl_zsock_socket_coap_ipv4_ok(int family, int type, int proto) +{ + TEST_ASSERT_EQUAL(AF_INET, family); + TEST_ASSERT_EQUAL(SOCK_DGRAM, type); + TEST_ASSERT_EQUAL(IPPROTO_UDP, proto); + + return FD; +} + +int z_impl_zsock_socket_coap_ipv6_ok(int family, int type, int proto) +{ + TEST_ASSERT_EQUAL(AF_INET6, family); + TEST_ASSERT_EQUAL(SOCK_DGRAM, type); + TEST_ASSERT_EQUAL(IPPROTO_UDP, proto); + + return FD; +} + +int z_impl_zsock_socket_coaps_ipv4_ok(int family, int type, int proto) +{ + TEST_ASSERT_EQUAL(AF_INET, family); + TEST_ASSERT_EQUAL(SOCK_DGRAM, type); + TEST_ASSERT_EQUAL(IPPROTO_DTLS_1_2, proto); + + return FD; +} + +int z_impl_zsock_socket_coaps_ipv6_ok(int family, int type, int proto) +{ + TEST_ASSERT_EQUAL(AF_INET6, family); + TEST_ASSERT_EQUAL(SOCK_DGRAM, type); + TEST_ASSERT_EQUAL(IPPROTO_DTLS_1_2, proto); + + return FD; +} + +int z_impl_zsock_connect_ipv4_ok(int sock, const struct sockaddr *addr, + socklen_t addrlen) +{ + TEST_ASSERT_EQUAL(FD, sock); + TEST_ASSERT_EQUAL(AF_INET, addr->sa_family); + return 0; +} + +int z_impl_zsock_connect_ipv6_ok(int sock, const struct sockaddr *addr, + socklen_t addrlen) +{ + TEST_ASSERT_EQUAL(FD, sock); + TEST_ASSERT_EQUAL(AF_INET6, addr->sa_family); + return 0; +} + +int z_impl_zsock_connect_ipv6_fails_ipv4_ok(int sock, const struct sockaddr *addr, + socklen_t addrlen) +{ + TEST_ASSERT_EQUAL(FD, sock); + + if (addr->sa_family == AF_INET6) { + return -EHOSTUNREACH; + } + + return 0; +} + + +int z_impl_zsock_connect_enetunreach(int sock, const struct sockaddr *addr, + socklen_t addrlen) +{ + errno = ENETUNREACH; + return -1; +} + + +int z_impl_zsock_setsockopt_http_ok(int sock, int level, int optname, const void *optval, + socklen_t optlen) +{ + switch (z_impl_zsock_setsockopt_fake.call_count) { + case 1: + TEST_ASSERT_EQUAL(SOL_SOCKET, level); + TEST_ASSERT_EQUAL(SO_RCVTIMEO, optname); + TEST_ASSERT_EQUAL(sizeof(struct timeval), optlen); + /* Ignore value */ + break; + } + + return 0; +} + +int z_impl_zsock_setsockopt_coap_ok(int sock, int level, int optname, const void *optval, + socklen_t optlen) +{ + printk("Setsockopt sock: %d, level: %d, optname: %d, optlen: %d\n", + sock, level, optname, optlen); + switch (z_impl_zsock_setsockopt_fake.call_count) { + case 1: + TEST_ASSERT_EQUAL(SOL_SOCKET, level); + TEST_ASSERT_EQUAL(SO_SNDTIMEO, optname); + TEST_ASSERT_EQUAL(sizeof(struct timeval), optlen); + /* Ignore value */ + break; + case 2: + TEST_ASSERT_EQUAL(SOL_SOCKET, level); + TEST_ASSERT_EQUAL(SO_RCVTIMEO, optname); + TEST_ASSERT_EQUAL(sizeof(struct timeval), optlen); + /* Ignore value */ + break; + } + + return 0; +} + +int z_impl_zsock_setsockopt_https_ok(int sock, int level, int optname, const void *optval, + socklen_t optlen) +{ + TEST_ASSERT_EQUAL(FD, sock); + + switch (z_impl_zsock_setsockopt_fake.call_count) { + case 1: + TEST_ASSERT_EQUAL(SOL_TLS, level); + TEST_ASSERT_EQUAL(TLS_PEER_VERIFY, optname); + TEST_ASSERT_EQUAL(4, optlen); + TEST_ASSERT_EQUAL(2, *(int *)optval); + break; + case 2: + TEST_ASSERT_EQUAL(SOL_TLS, level); + TEST_ASSERT_EQUAL(TLS_SEC_TAG_LIST, optname); + TEST_ASSERT_EQUAL(sizeof(sec_tags), optlen); + TEST_ASSERT_EQUAL_MEMORY(sec_tags, optval, sizeof(sec_tags)); + break; + case 3: + TEST_ASSERT_EQUAL(SOL_TLS, level); + TEST_ASSERT_EQUAL(TLS_HOSTNAME, optname); + TEST_ASSERT_EQUAL(strlen(HOSTNAME), optlen); + TEST_ASSERT_EQUAL_MEMORY(HOSTNAME, optval, strlen(HOSTNAME)); + break; + case 4: + TEST_ASSERT_EQUAL(SOL_SOCKET, level); + TEST_ASSERT_EQUAL(SO_RCVTIMEO, optname); + TEST_ASSERT_EQUAL(sizeof(struct timeval), optlen); + /* Ignore value */ + break; + } + + return 0; +} + +int z_impl_zsock_setsockopt_coaps_ok(int sock, int level, int optname, const void *optval, + socklen_t optlen) +{ + TEST_ASSERT_EQUAL(FD, sock); + + switch (z_impl_zsock_setsockopt_fake.call_count) { + case 1: + TEST_ASSERT_EQUAL(SOL_TLS, level); + TEST_ASSERT_EQUAL(TLS_PEER_VERIFY, optname); + TEST_ASSERT_EQUAL(4, optlen); + TEST_ASSERT_EQUAL(2, *(int *)optval); + break; + case 2: + TEST_ASSERT_EQUAL(SOL_TLS, level); + TEST_ASSERT_EQUAL(TLS_SEC_TAG_LIST, optname); + TEST_ASSERT_EQUAL(sizeof(sec_tags), optlen); + TEST_ASSERT_EQUAL_MEMORY(sec_tags, optval, sizeof(sec_tags)); + break; + case 3: + TEST_ASSERT_EQUAL(SOL_SOCKET, level); + TEST_ASSERT_EQUAL(SO_SNDTIMEO, optname); + TEST_ASSERT_EQUAL(sizeof(struct timeval), optlen); + /* Ignore value */ + break; + case 4: + TEST_ASSERT_EQUAL(SOL_SOCKET, level); + TEST_ASSERT_EQUAL(SO_RCVTIMEO, optname); + TEST_ASSERT_EQUAL(sizeof(struct timeval), optlen); + /* Ignore value */ + break; + } + + return 0; +} + +ssize_t z_impl_zsock_sendto_ok(int sock, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen) +{ + TEST_ASSERT_EQUAL(FD, sock); + return len; +} + +static ssize_t z_impl_zsock_recvfrom_http_header_then_data( + int sock, void *buf, size_t max_len, int flags, struct sockaddr *src_addr, + socklen_t *addrlen) +{ + TEST_ASSERT_EQUAL(FD, sock); + TEST_ASSERT(sizeof(dlc_buf) >= max_len); + + switch (z_impl_zsock_recvfrom_fake.call_count) { + case 1: + memcpy(buf, HTTP_HDR_OK, strlen(HTTP_HDR_OK)); + return strlen(HTTP_HDR_OK); + case 2: + memset(buf, 23, 128); + return 128; + } + + return 0; +} + +static ssize_t z_impl_zsock_recvfrom_http_header_and_payload( + int sock, void *buf, size_t max_len, int flags, struct sockaddr *src_addr, + socklen_t *addrlen) +{ + memcpy(buf, HTTP_HDR_OK_WITH_PAYLOAD, strlen(HTTP_HDR_OK_WITH_PAYLOAD)); + return strlen(HTTP_HDR_OK_WITH_PAYLOAD); +} + +static ssize_t z_impl_zsock_recvfrom_http_header_and_frag_data_w_err( + int sock, void *buf, size_t max_len, int flags, struct sockaddr *src_addr, + socklen_t *addrlen) +{ + switch (z_impl_zsock_recvfrom_fake.call_count) { + case 1: + memcpy(buf, HTTP_HDR_OK, strlen(HTTP_HDR_OK)); + return strlen(HTTP_HDR_OK); + case 2: + memset(buf, 23, 32); + return 32; + case 3: + /* connection reset */ + errno = ECONNRESET; + return -1; + case 4: + memcpy(buf, HTTP_HDR_OK_PROGRESS, strlen(HTTP_HDR_OK_PROGRESS)); + return strlen(HTTP_HDR_OK_PROGRESS); + } + + return 32; +} + +static ssize_t z_impl_zsock_recvfrom_http_header_and_frag_data_peer_close( + int sock, void *buf, size_t max_len, int flags, struct sockaddr *src_addr, + socklen_t *addrlen) +{ + switch (z_impl_zsock_recvfrom_fake.call_count) { + case 1: + memcpy(buf, HTTP_HDR_OK, strlen(HTTP_HDR_OK)); + return strlen(HTTP_HDR_OK); + case 2: + memset(buf, 23, 32); + return 32; + case 3: + /* connection closed */ + return 0; + case 4: + memcpy(buf, HTTP_HDR_OK_PROGRESS, strlen(HTTP_HDR_OK_PROGRESS)); + return strlen(HTTP_HDR_OK_PROGRESS); + } + + return 32; +} + +static ssize_t z_impl_zsock_recvfrom_coap( + int sock, void *buf, size_t max_len, int flags, struct sockaddr *src_addr, + socklen_t *addrlen) +{ + memset(buf, 23, 32); + return 32; +} + +int coap_get_option_int_ok(const struct coap_packet *cpkt, uint16_t code) +{ + return 0; +} + +int coap_block_transfer_init_ok(struct coap_block_context *ctx, + enum coap_block_size block_size, + size_t total_size) +{ + return 0; +} + +void coap_pending_clear_ok(struct coap_pending *pending) +{ + /* empty */ +} + +bool coap_pending_cycle_ok(struct coap_pending *pending) +{ + pending->timeout = 10000; + return true; +} + +bool coap_pending_cycle_5(struct coap_pending *pending) +{ + pending->timeout = 1000 * (5 - coap_pending_cycle_fake.call_count); + + if (pending->timeout) { + return true; + } + + return false; + +} + +int coap_update_from_block_ok(const struct coap_packet *cpkt, + struct coap_block_context *ctx) +{ + return 0; +} + +size_t coap_next_block_empty(const struct coap_packet *cpkt, + struct coap_block_context *ctx) +{ + return 0; +} + +int coap_packet_parse_ok(struct coap_packet *cpkt, uint8_t *data, uint16_t len, + struct coap_option *options, uint8_t opt_num) +{ + return 0; +} + +uint16_t coap_header_get_id_ok(const struct coap_packet *cpkt) +{ + return 0; +} + +uint8_t coap_header_get_type_ack(const struct coap_packet *cpkt) +{ + return COAP_TYPE_ACK; +} + +uint8_t coap_header_get_code_ok(const struct coap_packet *cpkt) +{ + return COAP_RESPONSE_CODE_OK; +} + +uint8_t coap_header_get_code_bad_then_ok(const struct coap_packet *cpkt) +{ + switch (coap_header_get_code_fake.call_count) { + case 1: + return 0xba; + default: return COAP_RESPONSE_CODE_OK; + } + + + return COAP_RESPONSE_CODE_OK; +} + +uint8_t coap_header_get_code_bad(const struct coap_packet *cpkt) +{ + return 0xba; +} + +#define COAP_PAYLOAD "This is the payload" +const uint8_t *coap_packet_get_payload_ok(const struct coap_packet *cpkt, uint16_t *len) +{ + *len = sizeof(COAP_PAYLOAD); + + return COAP_PAYLOAD; +} +int coap_packet_init_ok(struct coap_packet *cpkt, uint8_t *data, uint16_t max_len, + uint8_t ver, uint8_t type, uint8_t token_len, + const uint8_t *token, uint8_t code, uint16_t id) +{ + return 0; +} +uint8_t *coap_next_token_ok(void) +{ + static uint8_t token[COAP_TOKEN_MAX_LEN]; + + return token; +} +int coap_packet_append_option_ok(struct coap_packet *cpkt, uint16_t code, + const uint8_t *value, uint16_t len) +{ + return 0; +} +int coap_append_block2_option_ok(struct coap_packet *cpkt, + struct coap_block_context *ctx) +{ + return 0; +} +int coap_append_size2_option_ok(struct coap_packet *cpkt, + struct coap_block_context *ctx) +{ + return 0; +} + +struct coap_transmission_parameters coap_transmission_params = { + .ack_timeout = 10000, + .coap_backoff_percent = 50, + .max_retransmission = 10, +}; + +struct coap_transmission_parameters coap_get_transmission_parameters_ok(void) +{ + return coap_transmission_params; +} + +K_PIPE_DEFINE(event_pipe, 10*sizeof(struct downloader_evt), + _Alignof(struct downloader_evt)); + +static const char *dlc_event_id_str(int evt_id) +{ + switch (evt_id) { + case DOWNLOADER_EVT_FRAGMENT: return "DOWNLOADER_EVT_FRAGMENT"; + case DOWNLOADER_EVT_ERROR: return "DOWNLOADER_EVT_ERROR"; + case DOWNLOADER_EVT_DONE: return "DOWNLOADER_EVT_DONE"; + case DOWNLOADER_EVT_STOPPED: return "DOWNLOADER_EVT_STOPPED"; + case DOWNLOADER_EVT_DEINITIALIZED: return "DOWNLOADER_EVT_DEINITIALIZED"; + } + + return "unknown"; +} + +static int dlc_callback(const struct downloader_evt *event) +{ + size_t written; + + TEST_ASSERT(event != NULL); + + printk("event: %s\n", dlc_event_id_str(event->id)); + if (event->id == DOWNLOADER_EVT_ERROR) { + /* avoid spamming error events during development */ + k_sleep(K_MSEC(100)); + } + + k_pipe_put(&event_pipe, (void *)event, sizeof(*event), &written, sizeof(*event), K_FOREVER); + + return 0; +} + +static int dlc_callback_abort(const struct downloader_evt *event) +{ + size_t written; + + TEST_ASSERT(event != NULL); + + printk("event: %s\n", dlc_event_id_str(event->id)); + if (event->id == DOWNLOADER_EVT_ERROR) { + /* avoid spamming error events during development */ + k_sleep(K_MSEC(100)); + } + k_pipe_put(&event_pipe, (void *)event, sizeof(*event), &written, sizeof(*event), K_FOREVER); + + return 1; /* stop download*/ +} + +static struct downloader_evt dlc_wait_for_event(enum downloader_evt_id event, + k_timeout_t timeout) +{ + size_t read; + struct downloader_evt evt; + int err; + + while (true) { + err = k_pipe_get(&event_pipe, &evt, sizeof(evt), &read, sizeof(evt), + timeout); + TEST_ASSERT_EQUAL(0, err); + if (evt.id == event) { + break; + } + } + TEST_ASSERT_EQUAL(evt.id, event); + return evt; +} + +void test_downloader_init_einval(void) +{ + int err; + char buf[1]; + struct downloader dlc = {}; + struct downloader_cfg config = { + .buf = buf, + .buf_size = 1, + .callback = dlc_callback, + }; + struct downloader_cfg config_no_buf = { + .buf = NULL, + .buf_size = 1, + .callback = dlc_callback, + }; + struct downloader_cfg config_no_buf_len = { + .buf = buf, + .buf_size = 0, + .callback = dlc_callback, + }; + struct downloader_cfg config_no_cb = { + .buf = buf, + .buf_size = 1, + .callback = NULL, + }; + + err = downloader_init(NULL, &config); + TEST_ASSERT_EQUAL(-EINVAL, err); + + err = downloader_init(&dlc, NULL); + TEST_ASSERT_EQUAL(-EINVAL, err); + + err = downloader_init(&dlc, &config_no_buf); + TEST_ASSERT_EQUAL(-EINVAL, err); + + err = downloader_init(&dlc, &config_no_buf_len); + TEST_ASSERT_EQUAL(-EINVAL, err); + + err = downloader_init(&dlc, &config_no_cb); + TEST_ASSERT_EQUAL(-EINVAL, err); +} + +void test_downloader_init_ealready(void) +{ + int err = 0; + + err = downloader_init(&dlc, &dlc_config); + TEST_ASSERT_EQUAL(0, err); + + err = downloader_init(&dlc, &dlc_config); + TEST_ASSERT_EQUAL(-EALREADY, err); + + downloader_deinit(&dlc); + dlc_wait_for_event(DOWNLOADER_EVT_DEINITIALIZED, K_SECONDS(1)); +} + +void test_downloader_deinit_einval(void) +{ + int err; + + err = downloader_deinit(NULL); + TEST_ASSERT_EQUAL(-EINVAL, err); +} + +void test_downloader_get_eperm(void) +{ + int err; + + err = downloader_get(&dlc, &dlc_host_cfg, HTTP_URI, 0); + TEST_ASSERT_EQUAL(-EPERM, err); +} + +void test_downloader_get_http(void) +{ + int err; + struct downloader_evt evt; + + err = downloader_init(&dlc, &dlc_config); + TEST_ASSERT_EQUAL(0, err); + + zsock_getaddrinfo_fake.custom_fake = zsock_getaddrinfo_server_ipv6_fail_ipv4_ok; + zsock_freeaddrinfo_fake.custom_fake = zsock_freeaddrinfo_server_ipv4; + z_impl_zsock_socket_fake.custom_fake = z_impl_zsock_socket_http_ipv4_ok; + z_impl_zsock_connect_fake.custom_fake = z_impl_zsock_connect_ipv4_ok; + z_impl_zsock_setsockopt_fake.custom_fake = z_impl_zsock_setsockopt_http_ok; + z_impl_zsock_sendto_fake.custom_fake = z_impl_zsock_sendto_ok; + z_impl_zsock_recvfrom_fake.custom_fake = z_impl_zsock_recvfrom_http_header_then_data; + + + err = downloader_get(&dlc, &dlc_host_cfg, HTTP_URI, 0); + TEST_ASSERT_EQUAL(0, err); + + evt = dlc_wait_for_event(DOWNLOADER_EVT_DONE, K_SECONDS(3)); + + downloader_deinit(&dlc); + dlc_wait_for_event(DOWNLOADER_EVT_DEINITIALIZED, K_SECONDS(1)); +} + +void test_downloader_get_https(void) +{ + int err; + struct downloader_evt evt; + + err = downloader_init(&dlc, &dlc_config); + TEST_ASSERT_EQUAL(0, err); + + zsock_getaddrinfo_fake.custom_fake = zsock_getaddrinfo_server_ok; + zsock_freeaddrinfo_fake.custom_fake = zsock_freeaddrinfo_server_ipv6; + z_impl_zsock_socket_fake.custom_fake = z_impl_zsock_socket_https_ipv6_ok; + z_impl_zsock_connect_fake.custom_fake = z_impl_zsock_connect_ipv6_ok; + z_impl_zsock_setsockopt_fake.custom_fake = z_impl_zsock_setsockopt_https_ok; + z_impl_zsock_sendto_fake.custom_fake = z_impl_zsock_sendto_ok; + z_impl_zsock_recvfrom_fake.custom_fake = z_impl_zsock_recvfrom_http_header_then_data; + + + err = downloader_get(&dlc, &dlc_host_cfg_w_sec_tags, HTTPS_URI, 0); + TEST_ASSERT_EQUAL(0, err); + + evt = dlc_wait_for_event(DOWNLOADER_EVT_DONE, K_SECONDS(3)); + + downloader_deinit(&dlc); + dlc_wait_for_event(DOWNLOADER_EVT_DEINITIALIZED, K_SECONDS(1)); +} + +void test_downloader_get_http_connect_enetunreach(void) +{ + int err; + struct downloader_evt evt; + + err = downloader_init(&dlc, &dlc_config_cb_abort); + TEST_ASSERT_EQUAL(0, err); + + zsock_getaddrinfo_fake.custom_fake = zsock_getaddrinfo_server_ipv6_fail_ipv4_ok; + zsock_freeaddrinfo_fake.custom_fake = zsock_freeaddrinfo_server_ipv4; + z_impl_zsock_socket_fake.custom_fake = z_impl_zsock_socket_http_ipv4_ok; + z_impl_zsock_connect_fake.custom_fake = z_impl_zsock_connect_enetunreach; + z_impl_zsock_setsockopt_fake.custom_fake = z_impl_zsock_setsockopt_http_ok; + z_impl_zsock_sendto_fake.custom_fake = z_impl_zsock_sendto_ok; + z_impl_zsock_recvfrom_fake.custom_fake = z_impl_zsock_recvfrom_http_header_then_data; + + + err = downloader_get(&dlc, &dlc_host_cfg, HTTP_URI, 0); + TEST_ASSERT_EQUAL(0, err); + + evt = dlc_wait_for_event(DOWNLOADER_EVT_ERROR, K_SECONDS(1)); + + downloader_deinit(&dlc); + dlc_wait_for_event(DOWNLOADER_EVT_DEINITIALIZED, K_SECONDS(1)); +} + +void test_downloader_get_http_getaddr_failed(void) +{ + int err; + struct downloader_evt evt; + + err = downloader_init(&dlc, &dlc_config); + TEST_ASSERT_EQUAL(0, err); + + zsock_getaddrinfo_fake.custom_fake = zsock_getaddrinfo_server_enetunreach; + zsock_freeaddrinfo_fake.custom_fake = zsock_freeaddrinfo_server_ipv4; + z_impl_zsock_socket_fake.custom_fake = z_impl_zsock_socket_http_ipv4_ok; + z_impl_zsock_connect_fake.custom_fake = z_impl_zsock_connect_ipv4_ok; + z_impl_zsock_setsockopt_fake.custom_fake = z_impl_zsock_setsockopt_http_ok; + z_impl_zsock_sendto_fake.custom_fake = z_impl_zsock_sendto_ok; + z_impl_zsock_recvfrom_fake.custom_fake = z_impl_zsock_recvfrom_http_header_then_data; + + err = downloader_get(&dlc, &dlc_host_cfg, HTTP_URI, 0); + TEST_ASSERT_EQUAL(0, err); + + evt = dlc_wait_for_event(DOWNLOADER_EVT_ERROR, K_SECONDS(3)); + + downloader_deinit(&dlc); + dlc_wait_for_event(DOWNLOADER_EVT_DEINITIALIZED, K_SECONDS(1)); +} + +void test_downloader_get_default_proto_http(void) +{ + int err; + struct downloader_evt evt; + + err = downloader_init(&dlc, &dlc_config_cb_abort); + TEST_ASSERT_EQUAL(0, err); + + zsock_getaddrinfo_fake.custom_fake = zsock_getaddrinfo_server_ipv6_fail_ipv4_ok; + zsock_freeaddrinfo_fake.custom_fake = zsock_freeaddrinfo_server_ipv4; + z_impl_zsock_socket_fake.custom_fake = z_impl_zsock_socket_http_ipv4_ok; + z_impl_zsock_connect_fake.custom_fake = z_impl_zsock_connect_ipv4_ok; + z_impl_zsock_setsockopt_fake.custom_fake = z_impl_zsock_setsockopt_http_ok; + z_impl_zsock_sendto_fake.custom_fake = z_impl_zsock_sendto_ok; + z_impl_zsock_recvfrom_fake.custom_fake = z_impl_zsock_recvfrom_http_header_then_data; + + /* Default to http when no proto is specified. */ + err = downloader_get(&dlc, &dlc_host_cfg, NO_PROTO_URI, 0); + TEST_ASSERT_EQUAL(0, err); + + evt = dlc_wait_for_event(DOWNLOADER_EVT_DONE, K_SECONDS(3)); + + downloader_deinit(&dlc); + dlc_wait_for_event(DOWNLOADER_EVT_DEINITIALIZED, K_SECONDS(1)); +} + +void test_downloader_get_default_proto_https(void) +{ + int err; + struct downloader_evt evt; + + err = downloader_init(&dlc, &dlc_config_cb_abort); + TEST_ASSERT_EQUAL(0, err); + + zsock_getaddrinfo_fake.custom_fake = zsock_getaddrinfo_server_ok; + zsock_freeaddrinfo_fake.custom_fake = zsock_freeaddrinfo_server_ipv6; + z_impl_zsock_socket_fake.custom_fake = z_impl_zsock_socket_https_ipv6_ok; + z_impl_zsock_connect_fake.custom_fake = z_impl_zsock_connect_ipv6_ok; + z_impl_zsock_setsockopt_fake.custom_fake = z_impl_zsock_setsockopt_https_ok; + z_impl_zsock_sendto_fake.custom_fake = z_impl_zsock_sendto_ok; + z_impl_zsock_recvfrom_fake.custom_fake = z_impl_zsock_recvfrom_http_header_then_data; + + /* Default to http when no proto is specified. */ + err = downloader_get(&dlc, &dlc_host_cfg_w_sec_tags, NO_PROTO_URI, 0); + TEST_ASSERT_EQUAL(0, err); + + evt = dlc_wait_for_event(DOWNLOADER_EVT_DONE, K_SECONDS(3)); + + downloader_deinit(&dlc); + dlc_wait_for_event(DOWNLOADER_EVT_DEINITIALIZED, K_SECONDS(1)); +} + +void test_downloader_get_reconnect_on_socket_error(void) +{ + int err; + struct downloader_evt evt; + + err = downloader_init(&dlc, &dlc_config); + TEST_ASSERT_EQUAL(0, err); + + zsock_getaddrinfo_fake.custom_fake = zsock_getaddrinfo_server_ok; + zsock_freeaddrinfo_fake.custom_fake = zsock_freeaddrinfo_server_ipv6; + z_impl_zsock_socket_fake.custom_fake = z_impl_zsock_socket_http_ipv6_ok; + z_impl_zsock_connect_fake.custom_fake = z_impl_zsock_connect_ipv6_ok; + z_impl_zsock_setsockopt_fake.custom_fake = z_impl_zsock_setsockopt_http_ok; + z_impl_zsock_sendto_fake.custom_fake = z_impl_zsock_sendto_ok; + z_impl_zsock_recvfrom_fake.custom_fake = + z_impl_zsock_recvfrom_http_header_and_frag_data_w_err; + + /* Default to http when no proto is specified. */ + err = downloader_get(&dlc, &dlc_host_cfg, HTTP_URI, 0); + TEST_ASSERT_EQUAL(0, err); + + evt = dlc_wait_for_event(DOWNLOADER_EVT_DONE, K_SECONDS(3)); + + downloader_deinit(&dlc); + dlc_wait_for_event(DOWNLOADER_EVT_DEINITIALIZED, K_SECONDS(1)); +} + +void test_downloader_get_reconnect_on_peer_close(void) +{ + int err; + struct downloader_evt evt; + + err = downloader_init(&dlc, &dlc_config); + TEST_ASSERT_EQUAL(0, err); + + zsock_getaddrinfo_fake.custom_fake = zsock_getaddrinfo_server_ok; + zsock_freeaddrinfo_fake.custom_fake = zsock_freeaddrinfo_server_ipv6; + z_impl_zsock_socket_fake.custom_fake = z_impl_zsock_socket_http_ipv6_ok; + z_impl_zsock_connect_fake.custom_fake = z_impl_zsock_connect_ipv6_ok; + z_impl_zsock_setsockopt_fake.custom_fake = z_impl_zsock_setsockopt_http_ok; + z_impl_zsock_sendto_fake.custom_fake = z_impl_zsock_sendto_ok; + z_impl_zsock_recvfrom_fake.custom_fake = + z_impl_zsock_recvfrom_http_header_and_frag_data_peer_close; + + /* Default to http when no proto is specified. */ + err = downloader_get(&dlc, &dlc_host_cfg, HTTP_URI, 0); + TEST_ASSERT_EQUAL(0, err); + + evt = dlc_wait_for_event(DOWNLOADER_EVT_DONE, K_SECONDS(3)); + + downloader_deinit(&dlc); + dlc_wait_for_event(DOWNLOADER_EVT_DEINITIALIZED, K_SECONDS(1)); +} + +void test_downloader_get_coap(void) +{ + int err; + struct downloader_evt evt; + + err = downloader_init(&dlc, &dlc_config); + TEST_ASSERT_EQUAL(0, err); + + zsock_getaddrinfo_fake.custom_fake = zsock_getaddrinfo_server_ok; + zsock_freeaddrinfo_fake.custom_fake = zsock_freeaddrinfo_server_ipv6; + z_impl_zsock_socket_fake.custom_fake = z_impl_zsock_socket_coap_ipv6_ok; + z_impl_zsock_connect_fake.custom_fake = z_impl_zsock_connect_ipv6_ok; + z_impl_zsock_setsockopt_fake.custom_fake = z_impl_zsock_setsockopt_coap_ok; + z_impl_zsock_sendto_fake.custom_fake = z_impl_zsock_sendto_ok; + z_impl_zsock_recvfrom_fake.custom_fake = z_impl_zsock_recvfrom_coap; + + coap_get_transmission_parameters_fake.custom_fake = coap_get_transmission_parameters_ok; + coap_pending_cycle_fake.custom_fake = coap_pending_cycle_ok; + coap_header_get_type_fake.custom_fake = coap_header_get_type_ack; + coap_header_get_code_fake.custom_fake = coap_header_get_code_ok; + coap_packet_get_payload_fake.custom_fake = coap_packet_get_payload_ok; + + err = downloader_get(&dlc, &dlc_host_cfg, COAP_URI, 0); + TEST_ASSERT_EQUAL(0, err); + + evt = dlc_wait_for_event(DOWNLOADER_EVT_DONE, K_SECONDS(3)); + + downloader_deinit(&dlc); + dlc_wait_for_event(DOWNLOADER_EVT_DEINITIALIZED, K_SECONDS(1)); +} + +void test_downloader_get_coaps(void) +{ + int err; + struct downloader_evt evt; + + err = downloader_init(&dlc, &dlc_config); + TEST_ASSERT_EQUAL(0, err); + + zsock_getaddrinfo_fake.custom_fake = zsock_getaddrinfo_server_ok; + zsock_freeaddrinfo_fake.custom_fake = zsock_freeaddrinfo_server_ipv6; + z_impl_zsock_socket_fake.custom_fake = z_impl_zsock_socket_coaps_ipv6_ok; + z_impl_zsock_connect_fake.custom_fake = z_impl_zsock_connect_ipv6_ok; + z_impl_zsock_setsockopt_fake.custom_fake = z_impl_zsock_setsockopt_coaps_ok; + z_impl_zsock_sendto_fake.custom_fake = z_impl_zsock_sendto_ok; + z_impl_zsock_recvfrom_fake.custom_fake = z_impl_zsock_recvfrom_coap; + + coap_get_transmission_parameters_fake.custom_fake = coap_get_transmission_parameters_ok; + coap_pending_cycle_fake.custom_fake = coap_pending_cycle_ok; + coap_header_get_type_fake.custom_fake = coap_header_get_type_ack; + coap_header_get_code_fake.custom_fake = coap_header_get_code_ok; + coap_packet_get_payload_fake.custom_fake = coap_packet_get_payload_ok; + + err = downloader_get(&dlc, &dlc_host_cfg_w_sec_tags, COAPS_URI, 0); + TEST_ASSERT_EQUAL(0, err); + + evt = dlc_wait_for_event(DOWNLOADER_EVT_DONE, K_SECONDS(3)); + + downloader_deinit(&dlc); + dlc_wait_for_event(DOWNLOADER_EVT_DEINITIALIZED, K_SECONDS(1)); +} + +void test_downloader_get_coap_one_bad_header_code(void) +{ + int err; + struct downloader_evt evt; + + err = downloader_init(&dlc, &dlc_config_cb_abort); + TEST_ASSERT_EQUAL(0, err); + + zsock_getaddrinfo_fake.custom_fake = zsock_getaddrinfo_server_ok; + zsock_freeaddrinfo_fake.custom_fake = zsock_freeaddrinfo_server_ipv6; + z_impl_zsock_socket_fake.custom_fake = z_impl_zsock_socket_coap_ipv6_ok; + z_impl_zsock_connect_fake.custom_fake = z_impl_zsock_connect_ipv6_ok; + z_impl_zsock_setsockopt_fake.custom_fake = z_impl_zsock_setsockopt_coap_ok; + z_impl_zsock_sendto_fake.custom_fake = z_impl_zsock_sendto_ok; + z_impl_zsock_recvfrom_fake.custom_fake = z_impl_zsock_recvfrom_coap; + + coap_get_transmission_parameters_fake.custom_fake = coap_get_transmission_parameters_ok; + coap_pending_cycle_fake.custom_fake = coap_pending_cycle_ok; + coap_header_get_type_fake.custom_fake = coap_header_get_type_ack; + coap_header_get_code_fake.custom_fake = coap_header_get_code_bad_then_ok; + coap_packet_get_payload_fake.custom_fake = coap_packet_get_payload_ok; + + err = downloader_get(&dlc, &dlc_host_cfg, COAP_URI, 0); + TEST_ASSERT_EQUAL(0, err); + + evt = dlc_wait_for_event(DOWNLOADER_EVT_DONE, K_SECONDS(3)); + + downloader_deinit(&dlc); + dlc_wait_for_event(DOWNLOADER_EVT_DEINITIALIZED, K_SECONDS(1)); +} + +void test_downloader_get_coap_bad_header_code_timeout(void) +{ + int err; + struct downloader_evt evt; + + err = downloader_init(&dlc, &dlc_config_cb_abort); + TEST_ASSERT_EQUAL(0, err); + + zsock_getaddrinfo_fake.custom_fake = zsock_getaddrinfo_server_ok; + zsock_freeaddrinfo_fake.custom_fake = zsock_freeaddrinfo_server_ipv6; + z_impl_zsock_socket_fake.custom_fake = z_impl_zsock_socket_coap_ipv6_ok; + z_impl_zsock_connect_fake.custom_fake = z_impl_zsock_connect_ipv6_ok; + z_impl_zsock_setsockopt_fake.custom_fake = z_impl_zsock_setsockopt_coap_ok; + z_impl_zsock_sendto_fake.custom_fake = z_impl_zsock_sendto_ok; + z_impl_zsock_recvfrom_fake.custom_fake = z_impl_zsock_recvfrom_coap; + + coap_get_transmission_parameters_fake.custom_fake = coap_get_transmission_parameters_ok; + coap_pending_cycle_fake.custom_fake = coap_pending_cycle_5; + coap_header_get_type_fake.custom_fake = coap_header_get_type_ack; + coap_header_get_code_fake.custom_fake = coap_header_get_code_bad; + coap_packet_get_payload_fake.custom_fake = coap_packet_get_payload_ok; + + err = downloader_get(&dlc, &dlc_host_cfg, COAP_URI, 0); + TEST_ASSERT_EQUAL(0, err); + + evt = dlc_wait_for_event(DOWNLOADER_EVT_ERROR, K_SECONDS(3)); + + downloader_deinit(&dlc); + dlc_wait_for_event(DOWNLOADER_EVT_DEINITIALIZED, K_SECONDS(1)); +} + +void test_downloader_get_einval(void) +{ + int err; + + err = downloader_init(&dlc, &dlc_config); + TEST_ASSERT_EQUAL(0, err); + + err = downloader_get(NULL, &dlc_host_cfg, HTTP_URI, 0); + TEST_ASSERT_EQUAL(-EINVAL, err); + + err = downloader_get(&dlc, NULL, HTTP_URI, 0); + TEST_ASSERT_EQUAL(-EINVAL, err); + + err = downloader_get(&dlc, &dlc_host_cfg, NULL, 0); + TEST_ASSERT_EQUAL(-EINVAL, err); + + err = downloader_get(&dlc, &dlc_host_cfg, BAD_URI_NO_FILE, 0); + TEST_ASSERT_EQUAL(-EINVAL, err); + + /* https uri, no sec tag specified */ + err = downloader_get(&dlc, &dlc_host_cfg, HTTPS_URI, 0); + TEST_ASSERT_EQUAL(-EINVAL, err); + + /* coaps uri, no sec tag specified */ + err = downloader_get(&dlc, &dlc_host_cfg, COAPS_URI, 0); + TEST_ASSERT_EQUAL(-EINVAL, err); + + downloader_deinit(&dlc); + dlc_wait_for_event(DOWNLOADER_EVT_DEINITIALIZED, K_SECONDS(1)); +} + +void test_downloader_get_eprotonotsupport(void) +{ + int err; + + err = downloader_init(&dlc, &dlc_config); + TEST_ASSERT_EQUAL(0, err); + + err = downloader_get(&dlc, &dlc_host_cfg, BAD_URI, 0); + TEST_ASSERT_EQUAL(-EPROTONOSUPPORT, err); + + downloader_deinit(&dlc); + dlc_wait_for_event(DOWNLOADER_EVT_DEINITIALIZED, K_SECONDS(1)); +} + +void test_downloader_get(void) +{ + int err; + struct downloader_evt evt; + + err = downloader_init(&dlc, &dlc_config); + TEST_ASSERT_EQUAL(0, err); + + zsock_getaddrinfo_fake.custom_fake = zsock_getaddrinfo_server_ok; + zsock_freeaddrinfo_fake.custom_fake = zsock_freeaddrinfo_server_ipv6; + z_impl_zsock_socket_fake.custom_fake = z_impl_zsock_socket_http_ipv6_ok; + z_impl_zsock_connect_fake.custom_fake = z_impl_zsock_connect_ipv6_ok; + z_impl_zsock_setsockopt_fake.custom_fake = z_impl_zsock_setsockopt_http_ok; + z_impl_zsock_sendto_fake.custom_fake = z_impl_zsock_sendto_ok; + z_impl_zsock_recvfrom_fake.custom_fake = z_impl_zsock_recvfrom_http_header_then_data; + + + err = downloader_get(&dlc, &dlc_host_cfg, HTTP_URI, 0); + TEST_ASSERT_EQUAL(0, err); + + evt = dlc_wait_for_event(DOWNLOADER_EVT_DONE, K_SECONDS(3)); + + downloader_deinit(&dlc); + dlc_wait_for_event(DOWNLOADER_EVT_DEINITIALIZED, K_SECONDS(1)); +} + +void test_downloader_get_hdr_and_payload(void) +{ + int err; + struct downloader_evt evt; + + err = downloader_init(&dlc, &dlc_config); + TEST_ASSERT_EQUAL(0, err); + + zsock_getaddrinfo_fake.custom_fake = zsock_getaddrinfo_server_ok; + zsock_freeaddrinfo_fake.custom_fake = zsock_freeaddrinfo_server_ipv6; + z_impl_zsock_socket_fake.custom_fake = z_impl_zsock_socket_http_ipv6_ok; + z_impl_zsock_connect_fake.custom_fake = z_impl_zsock_connect_ipv6_ok; + z_impl_zsock_setsockopt_fake.custom_fake = z_impl_zsock_setsockopt_http_ok; + z_impl_zsock_sendto_fake.custom_fake = z_impl_zsock_sendto_ok; + z_impl_zsock_recvfrom_fake.custom_fake = z_impl_zsock_recvfrom_http_header_and_payload; + + + err = downloader_get(&dlc, &dlc_host_cfg, HTTP_URI, 0); + TEST_ASSERT_EQUAL(0, err); + + evt = dlc_wait_for_event(DOWNLOADER_EVT_FRAGMENT, K_SECONDS(3)); + TEST_ASSERT_EQUAL(20, evt.fragment.len); + TEST_ASSERT_EQUAL_MEMORY(PAYLOAD, evt.fragment.buf, evt.fragment.len); + + evt = dlc_wait_for_event(DOWNLOADER_EVT_DONE, K_SECONDS(3)); + + downloader_deinit(&dlc); + dlc_wait_for_event(DOWNLOADER_EVT_DEINITIALIZED, K_SECONDS(1)); +} + +void test_downloader_get_ipv6_fails_to_connect_ipv4_success(void) +{ + int err; + struct downloader_evt evt; + + err = downloader_init(&dlc, &dlc_config); + TEST_ASSERT_EQUAL(0, err); + + zsock_getaddrinfo_fake.custom_fake = zsock_getaddrinfo_server_ok; + zsock_freeaddrinfo_fake.custom_fake = zsock_freeaddrinfo_server_ipv6_then_ipv4; + z_impl_zsock_socket_fake.custom_fake = z_impl_zsock_socket_http_ipv6_then_ipv4; + z_impl_zsock_connect_fake.custom_fake = z_impl_zsock_connect_ipv6_fails_ipv4_ok; + z_impl_zsock_setsockopt_fake.custom_fake = z_impl_zsock_setsockopt_http_ok; + z_impl_zsock_sendto_fake.custom_fake = z_impl_zsock_sendto_ok; + z_impl_zsock_recvfrom_fake.custom_fake = z_impl_zsock_recvfrom_http_header_then_data; + + err = downloader_get(&dlc, &dlc_host_cfg, HTTP_URI, 0); + TEST_ASSERT_EQUAL(0, err); + + evt = dlc_wait_for_event(DOWNLOADER_EVT_DONE, K_SECONDS(3)); + + downloader_deinit(&dlc); + dlc_wait_for_event(DOWNLOADER_EVT_DEINITIALIZED, K_SECONDS(1)); +} + +void test_downloader_get_ipv4_specific_ok(void) +{ + int err; + struct downloader_evt evt; + static struct downloader_host_cfg dlc_host_cfg_ipv4 = { + .pdn_id = 0, + .family = AF_INET, + }; + + err = downloader_init(&dlc, &dlc_config); + TEST_ASSERT_EQUAL(0, err); + + zsock_getaddrinfo_fake.custom_fake = zsock_getaddrinfo_server_ok; + zsock_freeaddrinfo_fake.custom_fake = zsock_freeaddrinfo_server_ipv4; + z_impl_zsock_socket_fake.custom_fake = z_impl_zsock_socket_http_ipv4_ok; + z_impl_zsock_connect_fake.custom_fake = z_impl_zsock_connect_ipv4_ok; + z_impl_zsock_setsockopt_fake.custom_fake = z_impl_zsock_setsockopt_http_ok; + z_impl_zsock_sendto_fake.custom_fake = z_impl_zsock_sendto_ok; + z_impl_zsock_recvfrom_fake.custom_fake = z_impl_zsock_recvfrom_http_header_then_data; + + err = downloader_get(&dlc, &dlc_host_cfg_ipv4, HTTP_URI, 0); + TEST_ASSERT_EQUAL(0, err); + + evt = dlc_wait_for_event(DOWNLOADER_EVT_DONE, K_SECONDS(3)); + + downloader_deinit(&dlc); + dlc_wait_for_event(DOWNLOADER_EVT_DEINITIALIZED, K_SECONDS(1)); +} + +void test_downloader_file_size_get_einval(void) +{ + +} + +void setUp(void) +{ + RESET_FAKE(z_impl_zsock_setsockopt); + RESET_FAKE(z_impl_zsock_socket); + RESET_FAKE(z_impl_zsock_connect); + RESET_FAKE(z_impl_zsock_close); + RESET_FAKE(z_impl_zsock_send); + RESET_FAKE(z_impl_zsock_recv); + RESET_FAKE(zsock_getaddrinfo); + RESET_FAKE(zsock_freeaddrinfo); + RESET_FAKE(z_impl_zsock_inet_pton); + RESET_FAKE(z_impl_net_addr_ntop); + RESET_FAKE(z_impl_zsock_sendto); + RESET_FAKE(z_impl_zsock_recvfrom); + RESET_FAKE(z_impl_sys_rand_get); + + RESET_FAKE(coap_get_option_int); + RESET_FAKE(coap_block_transfer_init); + RESET_FAKE(coap_pending_clear); + RESET_FAKE(coap_pending_cycle); + RESET_FAKE(coap_update_from_block); + RESET_FAKE(coap_next_block); + RESET_FAKE(coap_packet_parse); + RESET_FAKE(coap_header_get_id); + RESET_FAKE(coap_header_get_type); + RESET_FAKE(coap_header_get_code); + RESET_FAKE(coap_packet_get_payload); + RESET_FAKE(coap_packet_init); + RESET_FAKE(coap_next_token); + RESET_FAKE(coap_packet_append_option); + RESET_FAKE(coap_append_block2_option); + RESET_FAKE(coap_append_size2_option); + RESET_FAKE(coap_get_transmission_parameters); + RESET_FAKE(coap_pending_init); + + k_pipe_flush(&event_pipe); +} + +void tearDown(void) +{ +} + +/* It is required to be added to each test. That is because unity's + * main may return nonzero, while zephyr's main currently must + * return 0 in all cases (other values are reserved). + */ +extern int unity_main(void); + +int main(void) +{ + (void)unity_main(); + + return 0; +} diff --git a/tests/subsys/net/lib/downloader/testcase.yaml b/tests/subsys/net/lib/downloader/testcase.yaml new file mode 100644 index 000000000000..47e751b62b5a --- /dev/null +++ b/tests/subsys/net/lib/downloader/testcase.yaml @@ -0,0 +1,7 @@ +tests: + net.lib.downloader: + sysbuild: true + tags: fota sysbuild ci_tests_subsys_net + platform_allow: native_sim + integration_platforms: + - native_sim diff --git a/tests/subsys/net/lib/fota_download/CMakeLists.txt b/tests/subsys/net/lib/fota_download/CMakeLists.txt index 16cf827e0597..e61aa1a1523d 100644 --- a/tests/subsys/net/lib/fota_download/CMakeLists.txt +++ b/tests/subsys/net/lib/fota_download/CMakeLists.txt @@ -25,15 +25,16 @@ target_include_directories(app ${ZEPHYR_NRF_MODULE_DIR}/subsys/net/lib/fota_download/include ${ZEPHYR_NRF_MODULE_DIR}/include/net/ ${ZEPHYR_NRF_MODULE_DIR}/subsys/dfu/include - ${ZEPHYR_NRF_MODULE_DIR}/subsys/net/lib/download_client/include + ${ZEPHYR_NRF_MODULE_DIR}/subsys/net/lib/downloader/include . # To get 'pm_config.h' ) target_compile_options(app PRIVATE - -DCONFIG_DOWNLOAD_CLIENT_BUF_SIZE=500 - -DCONFIG_DOWNLOAD_CLIENT_STACK_SIZE=500 - -DCONFIG_DOWNLOAD_CLIENT_MAX_FILENAME_SIZE=192 + -DCONFIG_DOWNLOADER_STACK_SIZE=500 + -DCONFIG_DOWNLOADER_MAX_FILENAME_SIZE=192 + -DCONFIG_DOWNLOADER_MAX_HOSTNAME_SIZE=128 + -DCONFIG_DOWNLOADER_TRANSPORT_PARAMS_SIZE=128 -DCONFIG_FW_MAGIC_LEN=32 -DABI_INFO_MAGIC=0xdededede -DCONFIG_FW_FIRMWARE_INFO_OFFSET=0x200 @@ -44,4 +45,5 @@ target_compile_options(app -DCONFIG_FOTA_DOWNLOAD_FILE_NAME_LENGTH=128 -DCONFIG_FOTA_DOWNLOAD_HOST_NAME_LENGTH=128 -DCONFIG_FOTA_DOWNLOAD_SEC_TAG_LIST_SIZE_MAX=5 + -DCONFIG_FOTA_DOWNLOAD_BUF_SZ=2048 ) 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..3764f59f799c 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 @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include #include #include @@ -24,7 +24,7 @@ static char buf[1024]; #define ARBITRARY_IMAGE_OFFSET 512 /* Stubs and mocks */ -static const char *download_client_start_file; +static const char *downloader_get_file; static bool spm_s0_active_retval; K_SEM_DEFINE(download_with_offset_sem, 0, 1); @@ -33,7 +33,7 @@ static bool fail_on_offset_get; static bool fail_on_connect; static bool fail_on_start; static bool download_with_offset_success; -static download_client_callback_t download_client_event_handler; +static downloader_callback_t downloader_event_handler; K_SEM_DEFINE(stop_sem, 0, 1); int dfu_target_init(int img_type, int img_num, size_t file_size, dfu_target_callback_t cb) @@ -81,16 +81,14 @@ int dfu_target_schedule_update(int img_num) return 0; } -int download_client_file_size_get(struct download_client *client, size_t *size) +int downloader_file_size_get(struct downloader *client, size_t *size) { return 0; } -int download_client_init(struct download_client *client, - download_client_callback_t callback) +int downloader_init(struct downloader *client, struct downloader_cfg *config) { - download_client_event_handler = callback; - client->fd = -1; + downloader_event_handler = config->callback; return 0; } @@ -99,16 +97,15 @@ enum dfu_target_image_type dfu_target_smp_img_type_check(const void *const buf, return DFU_TARGET_IMAGE_TYPE_SMP; } -int download_client_get(struct download_client *client, const char *host, - const struct download_client_cfg *config, const char *file, size_t from) +int downloader_get_with_host_and_path(struct downloader *dl, + const struct downloader_host_cfg *host_config, + const char *host, const char *file, size_t from) { if (fail_on_connect == true) { return -1; } - /* Mark connection */ - client->fd = 1; - download_client_start_file = file; + downloader_get_file = file; if (fail_on_start == true) { return -1; @@ -122,16 +119,13 @@ int download_client_get(struct download_client *client, const char *host, return 0; } -int download_client_disconnect(struct download_client *client) +int downloader_cancel(struct downloader *client) { - const struct download_client_evt evt = { - .id = DOWNLOAD_CLIENT_EVT_CLOSED, + const struct downloader_evt evt = { + .id = DOWNLOADER_EVT_STOPPED, }; - if (client->fd == -1) { - return -EINVAL; - } - client->fd = -1; - download_client_event_handler(&evt); + + downloader_event_handler(&evt); return 0; } @@ -259,7 +253,7 @@ static void init(void) fail_on_offset_get = false; fail_on_connect = false; fail_on_start = false; - download_client_start_file = NULL; + downloader_get_file = NULL; spm_s0_active_retval = false; k_sem_reset(&stop_sem); @@ -293,7 +287,7 @@ static void test_fota_download_any_generic(const char * const resource_locator, zassert_equal(err, 0, NULL); /* Verify that the correct resource was selected */ - zassert_true(strcmp(download_client_start_file, expected_selection) == 0, NULL); + zassert_true(strcmp(downloader_get_file, expected_selection) == 0, NULL); /* Verify that the download can be canceled with no isse */ err = fota_download_cancel(); @@ -340,8 +334,8 @@ ZTEST(fota_download_tests, test_download_with_offset) uint8_t fragment_buf[1] = {0}; size_t fragment_len = 1; - const struct download_client_evt evt = { - .id = DOWNLOAD_CLIENT_EVT_FRAGMENT, + const struct downloader_evt evt = { + .id = DOWNLOADER_EVT_FRAGMENT, .fragment = { .buf = fragment_buf, .len = fragment_len, @@ -354,7 +348,7 @@ ZTEST(fota_download_tests, test_download_with_offset) err = fota_download_any(BASE_DOMAIN, buf, NO_TLS, 0, 0, 0); zassert_ok(err, NULL); - err = download_client_event_handler(&evt); + err = downloader_event_handler(&evt); zassert_equal(err, -1, NULL); fail_on_offset_get = true; @@ -373,7 +367,7 @@ ZTEST(fota_download_tests, test_download_with_offset) err = fota_download_any(BASE_DOMAIN, buf, NO_TLS, 0, 0, 0); zassert_ok(err, NULL); - err = download_client_event_handler(&evt); + err = downloader_event_handler(&evt); zassert_equal(err, -1, NULL); fail_on_connect = true; @@ -391,7 +385,7 @@ ZTEST(fota_download_tests, test_download_with_offset) err = fota_download_any(BASE_DOMAIN, buf, NO_TLS, 0, 0, 0); zassert_ok(err, NULL); - err = download_client_event_handler(&evt); + err = downloader_event_handler(&evt); zassert_equal(err, -1, NULL); fail_on_start = true; @@ -407,7 +401,7 @@ ZTEST(fota_download_tests, test_download_with_offset) err = fota_download_any(BASE_DOMAIN, buf, NO_TLS, 0, 0, 0); zassert_ok(err, NULL); - err = download_client_event_handler(&evt); + err = downloader_event_handler(&evt); zassert_equal(err, -1, NULL); download_with_offset_success = false; diff --git a/tests/subsys/net/lib/lwm2m_client_utils/CMakeLists.txt b/tests/subsys/net/lib/lwm2m_client_utils/CMakeLists.txt index f9216633e624..57a583422203 100644 --- a/tests/subsys/net/lib/lwm2m_client_utils/CMakeLists.txt +++ b/tests/subsys/net/lib/lwm2m_client_utils/CMakeLists.txt @@ -33,8 +33,10 @@ set(options -DCONFIG_LTE_PSM_REQ_RAT="00001111" -DCONFIG_LTE_EDRX_REQ_VALUE_LTE_M="0001" -DCONFIG_LTE_PTW_VALUE_LTE_M="0000" - -DCONFIG_DOWNLOAD_CLIENT_BUF_SIZE=2048 - -DCONFIG_DOWNLOAD_CLIENT_STACK_SIZE=4096 + -DCONFIG_DOWNLOADER_STACK_SIZE=4096 + -DCONFIG_DOWNLOADER_MAX_HOSTNAME_SIZE=128 + -DCONFIG_DOWNLOADER_MAX_FILENAME_SIZE=128 + -DCONFIG_DOWNLOADER_TRANSPORT_PARAMS_SIZE=128 -DCONFIG_LWM2M_FIRMWARE_UPDATE_OBJ_SUPPORT -DCONFIG_LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT -DCONFIG_DFU_TARGET_MCUBOOT diff --git a/tests/subsys/net/lib/lwm2m_fota_utils/CMakeLists.txt b/tests/subsys/net/lib/lwm2m_fota_utils/CMakeLists.txt index faceb800e949..470fa1e5ae86 100644 --- a/tests/subsys/net/lib/lwm2m_fota_utils/CMakeLists.txt +++ b/tests/subsys/net/lib/lwm2m_fota_utils/CMakeLists.txt @@ -32,8 +32,10 @@ set(options -DCONFIG_LWM2M_CLIENT_UTILS_NEIGHBOUR_CELL_LISTENER -DCONFIG_LWM2M_CLIENT_UTILS_CONN_MON_OBJ_SUPPORT -DCONFIG_LTE_LC_TAU_PRE_WARNING_NOTIFICATIONS - -DCONFIG_DOWNLOAD_CLIENT_BUF_SIZE=2048 - -DCONFIG_DOWNLOAD_CLIENT_STACK_SIZE=4096 + -DCONFIG_DOWNLOADER_STACK_SIZE=4096 + -DCONFIG_DOWNLOADER_MAX_HOSTNAME_SIZE=128 + -DCONFIG_DOWNLOADER_MAX_FILENAME_SIZE=128 + -DCONFIG_DOWNLOADER_TRANSPORT_PARAMS_SIZE=128 -DCONFIG_LWM2M_FIRMWARE_UPDATE_OBJ_SUPPORT -DCONFIG_LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT -DCONFIG_DFU_TARGET_MCUBOOT=y diff --git a/tests/subsys/net/lib/mcumgr_smp_client/CMakeLists.txt b/tests/subsys/net/lib/mcumgr_smp_client/CMakeLists.txt index d97350fb1c75..118183771b2f 100644 --- a/tests/subsys/net/lib/mcumgr_smp_client/CMakeLists.txt +++ b/tests/subsys/net/lib/mcumgr_smp_client/CMakeLists.txt @@ -17,12 +17,12 @@ target_sources(app ${ZEPHYR_NRF_MODULE_DIR}/subsys/net/lib/mcumgr_smp_client/src/mcumgr_smp_client.c ${ZEPHYR_NRF_MODULE_DIR}/subsys/net/lib/fota_download/src/util/fota_download_util.c ${ZEPHYR_NRF_MODULE_DIR}/subsys/net/lib/fota_download/src/util/fota_download_smp.c - ${ZEPHYR_NRF_MODULE_DIR}/subsys/net/lib/download_client/src/parse.c + ${ZEPHYR_NRF_MODULE_DIR}/subsys/net/lib/downloader/src/dl_parse.c ) set(includes "src/" -${ZEPHYR_NRF_MODULE_DIR}/subsys/net/lib/download_client/include +${ZEPHYR_NRF_MODULE_DIR}/subsys/net/lib/downloader/include ) target_include_directories(app @@ -40,10 +40,12 @@ target_compile_options(app -DCONFIG_NRF_MCUMGR_SMP_CLIENT_LOG_LEVEL=2 -DCONFIG_FOTA_DOWNLOAD_LOG_LEVEL=2 -DCONFIG_FOTA_DOWNLOAD_FILE_NAME_LENGTH=128 - -DCONFIG_FOTA_DOWNLOAD_HOST_NAME_LENGTH=128 + -DCONFIG_FOTA_DOWNLOAD_HOST_NAME_LENGTH=256 -DCONFIG_FOTA_DOWNLOAD_RESOURCE_LOCATOR_LENGTH=512 - -DCONFIG_DOWNLOAD_CLIENT_BUF_SIZE=2048 - -DCONFIG_DOWNLOAD_CLIENT_STACK_SIZE=1024 + -DCONFIG_DOWNLOADER_MAX_HOSTNAME_SIZE=128 + -DCONFIG_DOWNLOADER_MAX_FILENAME_SIZE=128 + -DCONFIG_DOWNLOADER_TRANSPORT_PARAMS_SIZE=128 + -DCONFIG_DOWNLOADER_STACK_SIZE=1024 -DCONFIG_DFU_TARGET_SMP=1 ) diff --git a/tests/subsys/net/lib/nrf_cloud/cloud/CMakeLists.txt b/tests/subsys/net/lib/nrf_cloud/cloud/CMakeLists.txt index 1a62f3825f48..f002e99f74e5 100644 --- a/tests/subsys/net/lib/nrf_cloud/cloud/CMakeLists.txt +++ b/tests/subsys/net/lib/nrf_cloud/cloud/CMakeLists.txt @@ -55,8 +55,8 @@ if (CONFIG_NRF_CLOUD_MQTT OR CONFIG_NRF_CLOUD_FOTA OR CONFIG_NRF_MODEM_LIB) # NET_SOCKETS_POSIX_NAMES=y in zephyr/net/socket.h, so # it needs to be excluded when NET_SOCKETS_POSIX_NAMES=n set_source_files_properties( - ${ZEPHYR_NRF_MODULE_DIR}/subsys/net/lib/download_client/src/download_client.c - DIRECTORY ${ZEPHYR_NRF_MODULE_DIR}/subsys/net/lib/download_client/ + ${ZEPHYR_NRF_MODULE_DIR}/subsys/net/lib/downloader/src/downloader.c + DIRECTORY ${ZEPHYR_NRF_MODULE_DIR}/subsys/net/lib/downloader/ PROPERTIES HEADER_FILE_ONLY ON )