From 67e0d5a0cb83cc4c54b36da4f59251cd07677080 Mon Sep 17 00:00:00 2001 From: Mateusz Kwiatkowski Date: Mon, 9 Oct 2023 15:05:24 +0200 Subject: [PATCH] Anjay 3.6.0 --- .github/workflows/anjay-tests.yml | 10 +- CHANGELOG.md | 21 ++ CMakeLists.txt | 4 +- CONTRIBUTING.rst | 9 +- README.md | 20 +- demo/advanced_firmware_update.c | 3 +- demo/advanced_firmware_update.h | 3 + demo/advanced_firmware_update_app.c | 18 ++ demo/demo.c | 2 + demo/demo_args.c | 47 ++++ demo/demo_args.h | 2 + demo/firmware_update.c | 15 ++ demo/firmware_update.h | 2 + deps/avs_commons | 2 +- doc/sphinx/snippet_sources.md5 | 6 +- .../CF-CustomHardwareSupport.rst | 39 ++- .../FU-Introduction.rst | 4 + .../FU-PoorConnectivity.rst | 23 +- .../FU-SecureDownloads.rst | 22 ++ .../embedded_lwm2m10/anjay/anjay_config.h | 11 + .../embedded_lwm2m11/anjay/anjay_config.h | 11 + .../linux_lwm2m10/anjay/anjay_config.h | 11 + .../linux_lwm2m11/anjay/anjay_config.h | 11 + include_public/anjay/advanced_fw_update.h | 67 ++++- include_public/anjay/anjay_config.h.in | 11 + include_public/anjay/core.h | 9 + include_public/anjay/download.h | 13 + include_public/anjay/fw_update.h | 61 ++++- requirements.txt | 18 ++ src/anjay_config_log.h | 5 + src/anjay_modules/anjay_utils_core.h | 3 + src/core/anjay_core.c | 2 +- src/core/anjay_dm_core.c | 10 +- src/core/dm/anjay_dm_read.c | 10 +- src/core/dm/anjay_dm_read.h | 5 +- src/core/dm/anjay_dm_write.c | 5 +- src/core/dm/anjay_dm_write.h | 5 +- src/core/downloader/anjay_coap.c | 51 +++- src/core/downloader/anjay_http.c | 14 +- src/core/io/anjay_dynamic.c | 14 +- src/core/observe/anjay_observe_core.c | 39 ++- src/core/observe/anjay_observe_core.h | 6 +- src/core/servers/anjay_connections.c | 54 +++- src/core/servers/anjay_connections.h | 20 +- src/core/servers/anjay_connections_internal.h | 2 +- src/core/servers/anjay_security.h | 1 - src/core/servers/anjay_security_generic.c | 8 +- src/core/servers/anjay_server_connections.c | 38 +-- .../anjay_advanced_fw_update.c | 93 +++++-- src/modules/fw_update/anjay_fw_update.c | 13 + tests/core/anjay.c | 14 - tests/integration/framework/asserts.py | 4 +- tests/integration/run_tests.sh.in | 2 +- .../default/advanced_firmware_update.py | 247 +++++++++++++++--- .../suites/default/firmware_update.py | 64 ++++- .../testfest/dm/advanced_firmware_update.py | 13 +- .../suites/testfest/dm/firmware_update.py | 13 +- tools/ci-psa/Dockerfile | 10 +- tools/ci-psa/README.md | 6 + tools/ci/build-docker-images.sh | 13 +- tools/ci/rockylinux-9/Dockerfile | 6 +- tools/ci/ubuntu-18.04/Dockerfile | 13 +- tools/ci/ubuntu-20.04/Dockerfile | 6 +- tools/ci/ubuntu-22.04/Dockerfile | 6 +- 64 files changed, 1009 insertions(+), 281 deletions(-) create mode 100644 requirements.txt diff --git a/.github/workflows/anjay-tests.yml b/.github/workflows/anjay-tests.yml index 9cfffe08..1019624b 100644 --- a/.github/workflows/anjay-tests.yml +++ b/.github/workflows/anjay-tests.yml @@ -10,7 +10,7 @@ on: [push] jobs: ubuntu1804-compilers-test: runs-on: ubuntu-latest - container: avsystemembedded/anjay-travis:ubuntu-18.04-1.2 + container: avsystemembedded/anjay-travis:ubuntu-18.04-2.0 env: CC: ${{ matrix.CC }} CXX: ${{ matrix.CXX }} @@ -37,7 +37,7 @@ jobs: ubuntu2004-compilers-test: runs-on: ubuntu-latest - container: avsystemembedded/anjay-travis:ubuntu-20.04-1.2 + container: avsystemembedded/anjay-travis:ubuntu-20.04-2.0 env: CC: ${{ matrix.CC }} CXX: ${{ matrix.CXX }} @@ -68,7 +68,7 @@ jobs: ubuntu2204-compilers-test: runs-on: ubuntu-latest - container: avsystemembedded/anjay-travis:ubuntu-22.04-1.2 + container: avsystemembedded/anjay-travis:ubuntu-22.04-2.0 env: CC: ${{ matrix.CC }} CXX: ${{ matrix.CXX }} @@ -110,7 +110,7 @@ jobs: rockylinux9-compilers-test: runs-on: ubuntu-latest - container: avsystemembedded/anjay-travis:rockylinux-9-1.2 + container: avsystemembedded/anjay-travis:rockylinux-9-2.0 env: CC: ${{ matrix.CC }} CXX: ${{ matrix.CXX }} @@ -153,7 +153,7 @@ jobs: # NOTE: try the brew install command twice to work around "brew link" errors - run: INSTALL_CMD="brew install openssl mbedtls $COMPILER_VERSION"; $INSTALL_CMD || $INSTALL_CMD # NOTE: The above command may have installed a new version of Python, that's why we launch it weirdly - - run: /usr/bin/env python3 -m pip install sphinx sphinx-rtd-theme linuxdoc cbor2 aiocoap cryptography packaging requests wheel + - run: /usr/bin/env python3 -m pip install -r requirements.txt - run: env JAVA_HOME="$JAVA_HOME_17_X64" ./devconfig --with-asan --without-analysis --no-examples -DWITH_VALGRIND_TRACK_ORIGINS=OFF -DWITH_URL_CHECK=OFF -DWITH_IPV6=OFF - run: LC_ALL=en_US.UTF-8 make -j - run: LC_ALL=en_US.UTF-8 make check diff --git a/CHANGELOG.md b/CHANGELOG.md index 54fde488..df8ba36a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## 3.6.0 (October 9th, 2023) + +### Features + +- Added APIs for setting custom timeouts for downloads performed over CoAP+TCP + and HTTP, including firmware update downloads +- Added `requirements.txt` file to manage Python dependencies more efficiently + +### Improvements +- Clarified documentation on behavior of Firmware Update and Advanced Firmware + Update modules when ``anjay_fw_update_get_security_config_t`` and + ``anjay_advanced_fw_update_perform_upgrade_t`` callbacks, respectively, are + not defined. +- Added compilation flag that disables all composite operations + +### Bugfixes + +- Updated integration tests so that they pass on macOS +- Fix abort scenario in Advanced Firmware Update module + ## 3.5.0 (September 7th, 2023) ### BREAKING CHANGES @@ -32,6 +52,7 @@ require extra care to maintain thread safety - Removed ``const`` qualifier from ``MAKE_URI_PATH()`` compound literal which triggers a plausible compiler bug on IAR EWARM v9.30 +- Reading SNI from the Security Object for the FOTA download connection ### Bugfixes diff --git a/CMakeLists.txt b/CMakeLists.txt index ce37d5c6..038bbd93 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ cmake_minimum_required(VERSION 3.6.0) project(anjay C) -set(ANJAY_VERSION "3.5.0" CACHE STRING "Anjay library version") +set(ANJAY_VERSION "3.6.0" CACHE STRING "Anjay library version") set(ANJAY_BINARY_VERSION 1.0.0) set(ANJAY_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") @@ -268,6 +268,7 @@ option(WITH_LWM2M_JSON "Enable support for LwM2M 1.0 JSON (output only)" ON) option(WITHOUT_PLAINTEXT "Disable support for Plain Text content format" OFF) option(WITHOUT_DEREGISTER "Disable use of the Deregister message" OFF) option(WITHOUT_IP_STICKINESS "Disable support for IP stickiness" OFF) +option(WITHOUT_COMPOSITE_OPERATIONS "Disable composite operations" OFF) cmake_dependent_option(WITH_SENML_JSON "Enable support for SenML JSON content format" ON WITH_LWM2M11 OFF) cmake_dependent_option(WITH_CBOR "Enable support for CBOR and SenML CBOR content formats" ON WITH_LWM2M11 OFF) cmake_dependent_option(WITH_SEND "Enable support for LwM2M 1.1 Send operation" ON "WITH_CBOR OR WITH_SENML_JSON" OFF) @@ -524,6 +525,7 @@ set(ANJAY_WITHOUT_TLV "${WITHOUT_TLV}") set(ANJAY_WITHOUT_PLAINTEXT "${WITHOUT_PLAINTEXT}") set(ANJAY_WITHOUT_DEREGISTER "${WITHOUT_DEREGISTER}") set(ANJAY_WITHOUT_IP_STICKINESS "${WITHOUT_IP_STICKINESS}") +set(ANJAY_WITHOUT_COMPOSITE_OPERATIONS "${WITHOUT_COMPOSITE_OPERATIONS}") set(ANJAY_WITH_MODULE_ACCESS_CONTROL "${WITH_MODULE_access_control}") set(ANJAY_WITH_MODULE_IPSO_OBJECTS "${WITH_MODULE_ipso_objects}") set(ANJAY_WITH_MODULE_FW_UPDATE "${WITH_MODULE_fw_update}") diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 9d4c6129..2f541a49 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -102,10 +102,11 @@ Make use of the `coverage script `_ to generate a code coverage Before submitting your code, run the whole test suite (``make check``) to ensure that it does not introduce regressions. Use ``valgrind`` and Address Sanitizer to check for memory corruption errors. -Running tests on Ubuntu 16.04 or later: :: +Running tests on Ubuntu 20.04 or later: :: # Install these for tests: - sudo apt-get install libpython3-dev libssl-dev python3 python3-cryptography python3-jinja2 python3-sphinx python3-requests clang valgrind clang-tools + sudo apt-get install python3-pip git libmbedtls-dev libssl-dev zlib1g-dev python3 libpython3-dev wget valgrind curl cmake build-essential tshark + pip3 install -U -r requirements.txt # Configure and run check target ./devconfig && make check @@ -119,7 +120,7 @@ Running tests on CentOS 7 or later: :: # Use update-alternatives to create a /usr/bin/python3 symlink with priority 0 # (lowest possible) sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.5 0 - sudo python3 -m pip install cryptography jinja2 requests sphinx sphinx_rtd_theme + sudo python3 -m pip install -r requirements.txt # Configure and run check target # NOTE: clang-3.4 static analyzer (default version for CentOS) gives false @@ -130,7 +131,7 @@ Running tests on macOS Sierra or later: :: # Install these for tests: brew install python3 openssl llvm - pip3 install cryptography sphinx sphinx_rtd_theme requests + pip3 install -r requirements.txt # Configure and run check target: # if the scan-build script is located somewhere else, then you need to diff --git a/README.md b/README.md index 7f9e5e9a..8980eef2 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,10 @@ The project has been created and is actively maintained by [AVSystem](https://ww * [About OMA LwM2M](#about-oma-lwm2m) * [Quickstart guide](#quickstart-guide) * [Dependencies](#dependencies) - * [Ubuntu 18.04 LTS / Raspbian Buster or later](#ubuntu-1804-lts--raspbian-buster-or-later) + * [Ubuntu 20.04 LTS / Raspbian Buster or later](#ubuntu-2004-lts--raspbian-buster-or-later) * [CentOS 7 or later](#centos-7-or-later) * [macOS Sierra or later, with Homebrew](#macos-sierra-or-later-with-homebrew) + * [Python dependencies](#python-dependencies) * [Running the demo client](#running-the-demo-client) * [Detailed compilation guide](#detailed-compilation-guide) * [Building using CMake](#building-using-cmake) @@ -145,7 +146,7 @@ More details about OMA LwM2M: [Brief introduction to LwM2M](https://AVSystem.git - [Doxygen](http://www.doxygen.nl/), - [Sphinx](https://www.sphinx-doc.org/en/master/). -#### Ubuntu 18.04 LTS / Raspbian Buster or later +#### Ubuntu 20.04 LTS / Raspbian Buster or later ``` sh @@ -167,6 +168,15 @@ sudo yum install -y which git make cmake3 mbedtls-devel gcc gcc-c++ zlib-devel brew install cmake mbedtls openssl ``` +#### Python dependencies + +In order to run integration tests or `nsh_lwm2m` server, some additional Python +modules are required. To install them, you can use `requirements.txt` file by +running the following command: +```sh +pip3 install -U -r requirements.txt +``` + ### Running the demo client For initial development and testing of LwM2M clients, we recommend using the [Coiote IoT Device Management](https://www.avsystem.com/products/coiote-iot-device-management-platform/) where you can use the basic LwM2M server functionality for free. @@ -287,12 +297,14 @@ docker run -it anjay ## Embedded operating systems ports -If you want to use Anjay on Mbed OS, Zephyr OS, FreeRTOS or ESP-IDF check our demo +If you want to use Anjay on Mbed OS, Zephyr OS, FreeRTOS, Azure RTOS or ESP-IDF check our demo applications available in other repositories: - [Anjay-mbedos-client](https://github.com/AVSystem/Anjay-mbedos-client) (uses [Anjay-mbedos](https://github.com/AVSystem/Anjay-mbedos) integration layer) - [Anjay-zephyr-client](https://github.com/AVSystem/Anjay-zephyr-client) (uses [Anjay-zephyr](https://github.com/AVSystem/Anjay-zephyr) integration layer) - [Anjay-freertos-client](https://github.com/AVSystem/Anjay-freertos-client) -- [Anjay-esp32-client](https://github.com/AVSystem/Anjay-esp32-client) +- [Anjay-stm32-azurertos-client](https://github.com/AVSystem/Anjay-stm32-azurertos-client) +- [Anjay-esp32-client](https://github.com/AVSystem/Anjay-esp32-client) (uses [Anjay-esp-idf](https://github.com/AVSystem/Anjay-esp-idf) integration layer) +- [Anjay-pico-client](https://github.com/AVSystem/Anjay-pico-client) (uses FreeRTOS Kernel) ## Raspberry Pi client diff --git a/demo/advanced_firmware_update.c b/demo/advanced_firmware_update.c index 7b8d9f46..1c1db95e 100644 --- a/demo/advanced_firmware_update.c +++ b/demo/advanced_firmware_update.c @@ -1060,6 +1060,7 @@ int advanced_firmware_update_install( const char *persistence_file, const avs_net_security_info_t *security_info, const avs_coap_udp_tx_params_t *tx_params, + avs_time_duration_t tcp_request_timeout, anjay_advanced_fw_update_result_t delayed_result, bool prefer_same_socket_downloads, const char *original_img_file_path, @@ -1173,7 +1174,7 @@ int advanced_firmware_update_install( } result = advanced_firmware_update_application_install( anjay, fw_table, &state, security_info, tx_params, - auto_suspend); + tcp_request_timeout, auto_suspend); if (result) { demo_log(ERROR, "AFU instance %u install failed", FW_UPDATE_IID_APP); diff --git a/demo/advanced_firmware_update.h b/demo/advanced_firmware_update.h index 2eddc012..a39147f2 100644 --- a/demo/advanced_firmware_update.h +++ b/demo/advanced_firmware_update.h @@ -75,6 +75,7 @@ struct advanced_fw_update_logic { FILE *stream; avs_net_security_info_t security_info; avs_coap_udp_tx_params_t coap_tx_params; + avs_time_duration_t tcp_request_timeout; bool auto_suspend; int (*check_yourself)(struct advanced_fw_update_logic *); int (*update_yourself)(struct advanced_fw_update_logic *); @@ -88,6 +89,7 @@ int advanced_firmware_update_application_install( anjay_advanced_fw_update_initial_state_t *init_state, const avs_net_security_info_t *security_info, const avs_coap_udp_tx_params_t *tx_params, + avs_time_duration_t tcp_request_timeout, bool auto_suspend); int advanced_firmware_update_app_perform(advanced_fw_update_logic_t *fw); const char *advanced_firmware_update_app_get_pkg_version(anjay_iid_t iid, @@ -115,6 +117,7 @@ int advanced_firmware_update_install( const char *persistence_file, const avs_net_security_info_t *security_info, const avs_coap_udp_tx_params_t *tx_params, + avs_time_duration_t tcp_request_timeout, anjay_advanced_fw_update_result_t delayed_result, bool prefer_same_socket_downloads, const char *original_img_file_path, diff --git a/demo/advanced_firmware_update_app.c b/demo/advanced_firmware_update_app.c index a5b5e6bb..296d98af 100644 --- a/demo/advanced_firmware_update_app.c +++ b/demo/advanced_firmware_update_app.c @@ -169,6 +169,16 @@ static avs_coap_udp_tx_params_t fw_get_coap_tx_params( return fw->coap_tx_params; } +static avs_time_duration_t fw_get_tcp_request_timeout( + anjay_iid_t iid, void *user_ptr, const char *download_uri) { + (void) iid; + (void) download_uri; + advanced_fw_update_logic_t *fw_table = + (advanced_fw_update_logic_t *) user_ptr; + advanced_fw_update_logic_t *fw = &fw_table[FW_UPDATE_IID_APP]; + return fw->tcp_request_timeout; +} + static anjay_advanced_fw_update_handlers_t handlers = { .stream_open = fw_stream_open, .stream_write = fw_update_common_write, @@ -198,6 +208,7 @@ int advanced_firmware_update_application_install( anjay_advanced_fw_update_initial_state_t *init_state, const avs_net_security_info_t *security_info, const avs_coap_udp_tx_params_t *tx_params, + avs_time_duration_t tcp_request_timeout, bool auto_suspend) { advanced_fw_update_logic_t *fw_logic = &fw_table[FW_UPDATE_IID_APP]; @@ -221,6 +232,13 @@ int advanced_firmware_update_application_install( handlers.get_coap_tx_params = NULL; } + if (avs_time_duration_valid(tcp_request_timeout)) { + fw_logic->tcp_request_timeout = tcp_request_timeout; + handlers.get_tcp_request_timeout = fw_get_tcp_request_timeout; + } else { + handlers.get_tcp_request_timeout = NULL; + } + fw_global = fw_logic; int result = anjay_advanced_fw_update_instance_add(anjay, fw_logic->iid, diff --git a/demo/demo.c b/demo/demo.c index dbea89f4..59aee5d5 100644 --- a/demo/demo.c +++ b/demo/demo.c @@ -743,6 +743,7 @@ static int demo_init(anjay_demo_t *demo, cmdline_args_t *cmdline_args) { cmdline_args->fwu_tx_params_modified ? &cmdline_args->fwu_tx_params : NULL, + cmdline_args->fwu_tcp_request_timeout, cmdline_args->fw_update_delayed_result, cmdline_args->prefer_same_socket_downloads, # ifdef ANJAY_WITH_SEND @@ -764,6 +765,7 @@ static int demo_init(anjay_demo_t *demo, cmdline_args_t *cmdline_args) { cmdline_args->advanced_fwu_tx_params_modified ? &cmdline_args->advanced_fwu_tx_params : NULL, + cmdline_args->advanced_fwu_tcp_request_timeout, cmdline_args->advanced_fw_update_delayed_result, cmdline_args->prefer_same_socket_downloads, cmdline_args->original_img_file_path, diff --git a/demo/demo_args.c b/demo/demo_args.c index 57789090..5da38b7c 100644 --- a/demo/demo_args.c +++ b/demo/demo_args.c @@ -93,10 +93,13 @@ static const cmdline_args_t DEFAULT_CMDLINE_ARGS = { #ifdef ANJAY_WITH_MODULE_FW_UPDATE .fwu_tx_params_modified = false, .fwu_tx_params = ANJAY_COAP_DEFAULT_UDP_TX_PARAMS, + .fwu_tcp_request_timeout = { 0, -1 }, +// AVS_TIME_DURATION_INVALID #endif // ANJAY_WITH_MODULE_FW_UPDATE #ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE .advanced_fwu_tx_params_modified = false, .advanced_fwu_tx_params = ANJAY_COAP_DEFAULT_UDP_TX_PARAMS, + .advanced_fwu_tcp_request_timeout = { 0, -1 }, #endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE #ifdef ANJAY_WITH_LWM2M11 .lwm2m_version_config = { @@ -504,6 +507,16 @@ static void print_help(const struct option *options) { "with the specified key (provided as hexlified string); this " "argument is used by Advanced Firmware Update and must be used " "together with --afu-psk-identity" }, +#endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE +#ifdef ANJAY_WITH_MODULE_FW_UPDATE + { 333, "TIMEOUT", NULL, + "Request timeout (in seconds) to use for firmware updates performed " + "over CoAP+TCP and HTTP" }, +#endif // ANJAY_WITH_MODULE_FW_UPDATE +#ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE + { 334, "TIMEOUT", NULL, + "Request timeout (in seconds) to use for Advanced Firmware Update " + "downloads performed over CoAP+TCP and HTTP" }, #endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE }; @@ -879,6 +892,12 @@ int demo_parse_argv(cmdline_args_t *parsed_args, int argc, char *argv[]) { { "afu-auto-suspend", no_argument, 0, 330 }, { "afu-psk-identity", required_argument, 0, 331 }, { "afu-psk-key", required_argument, 0, 332 }, +#endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE +#ifdef ANJAY_WITH_MODULE_FW_UPDATE + { "fwu-tcp-request-timeout", required_argument, 0, 333 }, +#endif // ANJAY_WITH_MODULE_FW_UPDATE +#ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE + { "afu-tcp-request-timeout", required_argument, 0, 334 }, #endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE { 0, 0, 0, 0 } // clang-format on @@ -1689,6 +1708,34 @@ int demo_parse_argv(cmdline_args_t *parsed_args, int argc, char *argv[]) { avs_crypto_psk_key_info_from_buffer(psk_buf, psk_size); break; } +#endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE +#ifdef ANJAY_WITH_MODULE_FW_UPDATE + case 333: { + double timeout_s; + if (parse_double(optarg, &timeout_s) || !isfinite(timeout_s) + || timeout_s <= 0.0) { + demo_log(ERROR, "Expected TCP request timeout to be a positive " + "floating point number"); + goto finish; + } + parsed_args->fwu_tcp_request_timeout = + avs_time_duration_from_fscalar(timeout_s, AVS_TIME_S); + break; + } +#endif // ANJAY_WITH_MODULE_FW_UPDATE +#ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE + case 334: { + double timeout_s; + if (parse_double(optarg, &timeout_s) || !isfinite(timeout_s) + || timeout_s <= 0.0) { + demo_log(ERROR, "Expected TCP request timeout to be a positive " + "floating point number"); + goto finish; + } + parsed_args->advanced_fwu_tcp_request_timeout = + avs_time_duration_from_fscalar(timeout_s, AVS_TIME_S); + break; + } #endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE case 0: goto process; diff --git a/demo/demo_args.h b/demo/demo_args.h index 49d62122..ce051b4c 100644 --- a/demo/demo_args.h +++ b/demo/demo_args.h @@ -113,6 +113,7 @@ typedef struct cmdline_args { */ bool fwu_tx_params_modified; avs_coap_udp_tx_params_t fwu_tx_params; + avs_time_duration_t fwu_tcp_request_timeout; #endif // ANJAY_WITH_MODULE_FW_UPDATE #ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE /** @@ -123,6 +124,7 @@ typedef struct cmdline_args { */ bool advanced_fwu_tx_params_modified; avs_coap_udp_tx_params_t advanced_fwu_tx_params; + avs_time_duration_t advanced_fwu_tcp_request_timeout; #endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE #ifdef ANJAY_WITH_LWM2M11 anjay_lwm2m_version_config_t lwm2m_version_config; diff --git a/demo/firmware_update.c b/demo/firmware_update.c index 35fca8d9..ea27fad4 100644 --- a/demo/firmware_update.c +++ b/demo/firmware_update.c @@ -478,6 +478,13 @@ fw_get_coap_tx_params(void *fw_, const char *download_uri) { return fw->coap_tx_params; } +static avs_time_duration_t +fw_get_tcp_request_timeout(void *fw_, const char *download_uri) { + fw_update_logic_t *fw = (fw_update_logic_t *) fw_; + (void) download_uri; + return fw->tcp_request_timeout; +} + static anjay_fw_update_handlers_t FW_UPDATE_HANDLERS = { .stream_open = fw_stream_open, .stream_write = fw_stream_write, @@ -573,6 +580,7 @@ int firmware_update_install(anjay_t *anjay, const char *persistence_file, const avs_net_security_info_t *security_info, const avs_coap_udp_tx_params_t *tx_params, + avs_time_duration_t tcp_request_timeout, anjay_fw_update_result_t delayed_result, bool prefer_same_socket_downloads, #ifdef ANJAY_WITH_SEND @@ -602,6 +610,13 @@ int firmware_update_install(anjay_t *anjay, FW_UPDATE_HANDLERS.get_coap_tx_params = NULL; } + if (avs_time_duration_valid(tcp_request_timeout)) { + fw->tcp_request_timeout = tcp_request_timeout; + FW_UPDATE_HANDLERS.get_tcp_request_timeout = fw_get_tcp_request_timeout; + } else { + FW_UPDATE_HANDLERS.get_tcp_request_timeout = NULL; + } + persistence_file_data_t data = read_persistence_file(persistence_file); delete_persistence_file(fw); demo_log(INFO, "Initial firmware upgrade state result: %d", diff --git a/demo/firmware_update.h b/demo/firmware_update.h index 2c29f9e3..8b7d4293 100644 --- a/demo/firmware_update.h +++ b/demo/firmware_update.h @@ -33,6 +33,7 @@ typedef struct { FILE *stream; avs_net_security_info_t security_info; avs_coap_udp_tx_params_t coap_tx_params; + avs_time_duration_t tcp_request_timeout; bool auto_suspend; } fw_update_logic_t; @@ -41,6 +42,7 @@ int firmware_update_install(anjay_t *anjay, const char *persistence_file, const avs_net_security_info_t *security_info, const avs_coap_udp_tx_params_t *tx_params, + avs_time_duration_t tcp_request_timeout, anjay_fw_update_result_t delayed_result, bool prefer_same_socket_downloads, #ifdef ANJAY_WITH_SEND diff --git a/deps/avs_commons b/deps/avs_commons index 5d578fc0..1a1a5112 160000 --- a/deps/avs_commons +++ b/deps/avs_commons @@ -1 +1 @@ -Subproject commit 5d578fc0a70616ebb589e3c98e30afdb1a730e0d +Subproject commit 1a1a5112bd41b33c139e0e69d6a413d508c6659f diff --git a/doc/sphinx/snippet_sources.md5 b/doc/sphinx/snippet_sources.md5 index b303b158..dad3df16 100644 --- a/doc/sphinx/snippet_sources.md5 +++ b/doc/sphinx/snippet_sources.md5 @@ -83,11 +83,11 @@ f971df7872a8f052bb72ae64610a8031 examples/tutorial/firmware-update/basic-implem 1d85bb08f511fcc7b321f0be56ddb119 examples/tutorial/firmware-update/basic-implementation/src/main.c 844cd7d9d0ebe80007dc5ae8d6a719b0 examples/tutorial/firmware-update/download-resumption/src/firmware_update.c 077f6b89dad59ef3b9b58e2abe93aff6 examples/tutorial/firmware-update/secure-downloads/src/firmware_update.c -b8af5f803c445a4b1f7824407c979037 include_public/anjay/advanced_fw_update.h +5838371e4081a02d1b91133088262fd5 include_public/anjay/advanced_fw_update.h af2f0e0ac0de25dc04713f05a378d469 include_public/anjay/attr_storage.h -0223e485b0ea245f2b0e7982a502528b include_public/anjay/core.h +cb32a2854acf33cce16d04b6bdcec940 include_public/anjay/core.h c88697977b9c8e8a34d0229137ae793a include_public/anjay/dm.h -638664942f4503dec39d1fbe9d4d7cbc include_public/anjay/fw_update.h +774ecfbac5a3a686790469b90092c3f6 include_public/anjay/fw_update.h 7ef9abee6d8d8e689ddf467cce116347 include_public/anjay/io.h 33c77736e874a89e7dec6a4654eab3c9 include_public/anjay/ipso_objects.h e449359975f3fb404a50078630be399b include_public/anjay/security.h diff --git a/doc/sphinx/source/CommercialFeatures/CF-CustomHardwareSupport.rst b/doc/sphinx/source/CommercialFeatures/CF-CustomHardwareSupport.rst index 5028cd18..36b1d74d 100644 --- a/doc/sphinx/source/CommercialFeatures/CF-CustomHardwareSupport.rst +++ b/doc/sphinx/source/CommercialFeatures/CF-CustomHardwareSupport.rst @@ -9,7 +9,7 @@ Custom Hardware Support ======================= -Anjay can be used on a number of different platforms. By default, the library +Anjay can be used on several different platforms. By default, the library targets POSIX-like operating systems and their standard interfaces for networking, multithreading, etc., which means that Anjay can be easily compiled to run on many OSs, including: @@ -43,22 +43,49 @@ example applications for many popular SDKs and prototyping kits: - *contained in the app* - `Anjay-freertos-client `_ - * - `ESP-IDF `_ + * - `STM32Cube + `_ + w/ `Azure RTOS `_ and `X-CUBE-CELLULAR + `_ - *contained in the app* + - `Anjay-stm32-azurertos-client + `_ + * - `ESP-IDF `_ + - `Anjay-esp-idf `_ - `Anjay-esp32-client `_ + * - `Raspberry Pi Pico SDK `_ + - *contained in the app* + - `Anjay-pico-client `_ -If desired platform isn't listed above, it means that custom implementation for +If the desired platform isn't listed above, it means that custom implementation for time, threading, networking and (D)TLS APIs **must be provided** as described in :doc:`../PortingGuideForNonPOSIXPlatforms`. Integrating with networking -peripherals and APIs (e.g. cellular modems) is often **nontrivial**, thus -AVSystem is open to provide help with developing code tailored for specific +peripherals and APIs (e.g. cellular modems) is often **non-trivial**, thus +AVSystem is open to providing help with developing code tailored for a specific platform. In such case, please visit our `contact page `_. +Examples of application aspects that need custom integration with a software platform: + + * Device Object that gathers various hardware and software parameters, + * FOTA integration with platform NVM memory (Flash, EEPROM) drivers and bootloader, + * Connectivity-related LwM2M Objects implementations that rely on data + exchanged with the modem (e.g. Connectivity Monitoring or APN Connection Profile), + * GPS Location data acquired by GNSS modem, + * Factory provisioning with PC-based tools that communicate with the device via + serial UART port and pre-configure the device with connectivity credentials, + * Security based on a Hardware Security Module that's not PKCS11 or PSA standards + compliant (see: :doc:`HSM Commercial Feature `). + +During the development process, AVSystem can also include your custom hardware in +CI/CD pipeline to ensure proper performance, configuration and interoperability. +Such continuous integration testing is an extension of Custom Hardware Support +included in `Anjay Support Packages `_. + We can also assist with making many **improvements and optimizations**, even if the basic functionality works out of the box. For example, Anjay-zephyr contains code specific to the nRF9160 SoC which is capable of encrypting network traffic -directly on the modem instead of using additional (D)TLS library, which normally +directly on the modem instead of using an additional (D)TLS library, which normally is a part of the application. Thanks to that we were able to completely remove mbedTLS library, saving 80 kB of flash memory, and use nRF9160's ability to cache (D)TLS sessions over power cycles, greatly reducing the overhead of secure diff --git a/doc/sphinx/source/FirmwareUpdateTutorial/FU-Introduction.rst b/doc/sphinx/source/FirmwareUpdateTutorial/FU-Introduction.rst index 084d6da5..08ca97a7 100644 --- a/doc/sphinx/source/FirmwareUpdateTutorial/FU-Introduction.rst +++ b/doc/sphinx/source/FirmwareUpdateTutorial/FU-Introduction.rst @@ -120,6 +120,10 @@ give an idea on what the implementation of FOTA would take: /** Queries CoAP transmission parameters to be used during firmware * update; @ref anjay_fw_update_get_coap_tx_params_t */ anjay_fw_update_get_coap_tx_params_t *get_coap_tx_params; + + /** Queries request timeout to be used during firmware update over CoAP+TCP + * or HTTP; @ref anjay_advanced_fw_update_get_tcp_request_timeout */ + anjay_fw_update_get_tcp_request_timeout_t *get_tcp_request_timeout; } anjay_fw_update_handlers_t; diff --git a/doc/sphinx/source/FirmwareUpdateTutorial/FU-PoorConnectivity.rst b/doc/sphinx/source/FirmwareUpdateTutorial/FU-PoorConnectivity.rst index 6ab26c89..795f6b8b 100644 --- a/doc/sphinx/source/FirmwareUpdateTutorial/FU-PoorConnectivity.rst +++ b/doc/sphinx/source/FirmwareUpdateTutorial/FU-PoorConnectivity.rst @@ -37,15 +37,15 @@ CoAP(s)/TCP The download fails if either the connection could not be established (e.g. TLS handshake failure, remote host is down) or the TCP stack declares the connection as broken, or when there is no response to the request for -**5 minutes**. +the configured time; by default that time is 30 seconds. HTTP(s) """"""" The download fails due to similar reasons as above. Since HTTP(s) operates over TCP and the TCP stack maintains retransmissions and other things in a -way outside of our control. The difference is that here the response timeout -is fixed to **30 seconds**. +way outside of our control. The reponse timeout is configured the same way as +for CoAP(s)/TCP, and the default timeout is also 30 seconds. So what happens when the download fails? @@ -64,13 +64,12 @@ problems as it doesn't seem to be supported by the LwM2M protocol. How can we ensure higher success rate? ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -As described in previous sections, for any TCP based transport you can't do -much in terms of when the firmware download is considered as failed. The -timeouts are, at the time of writing this tutorial, fixed and cannot be -changed during runtime. +For CoAP/UDP, you can provide the :ref:`CoAP transmission parameters +` by implementing the ``get_coap_tx_params`` +handler, which is a part of ``anjay_fw_update_handlers_t``. If not provided, +default CoAP transmission parameters (or passed as part of +``anjay_configuration_t``) will be used. -However, CoAP/UDP provides much more control. :ref:`CoAP transmission -parameters ` can be provided by -the user, who implements ``get_coap_tx_params``, which is a part of -``anjay_fw_update_handlers_t``. If not provided, default CoAP transmission -parameters (or passed as part of ``anjay_configuration_t``) will be used. +In a similar manner, for TCP-based transports (i.e., CoAP(s)/TCP and HTTP(s)) +you can implement the ``get_tcp_request_timeout`` to set a custom request +timeout. This is the time of stream inactivity that will be treated as an error. diff --git a/doc/sphinx/source/FirmwareUpdateTutorial/FU-SecureDownloads.rst b/doc/sphinx/source/FirmwareUpdateTutorial/FU-SecureDownloads.rst index 17202e27..e12b80f0 100644 --- a/doc/sphinx/source/FirmwareUpdateTutorial/FU-SecureDownloads.rst +++ b/doc/sphinx/source/FirmwareUpdateTutorial/FU-SecureDownloads.rst @@ -74,6 +74,15 @@ Security information is configured in Anjay through a structure: * to be used. */ avs_net_socket_tls_ciphersuites_t tls_ciphersuites; + + /* + * Server Name Indicator to use for authenticating with the peer during + * secure TLS connection. The value is passed to the underlying TLS library + * that need to take this variable into account for it make any effect. This + * field is optional and can be left zero-initialized. If not set the + * integration layer should use the Server URI instead. + */ + const char *server_name_indication; } anjay_security_config_t; And specifically, it's the ``security_info`` field that is of interest to @@ -294,6 +303,10 @@ by the user: /** Queries CoAP transmission parameters to be used during firmware * update; @ref anjay_fw_update_get_coap_tx_params_t */ anjay_fw_update_get_coap_tx_params_t *get_coap_tx_params; + + /** Queries request timeout to be used during firmware update over CoAP+TCP + * or HTTP; @ref anjay_advanced_fw_update_get_tcp_request_timeout */ + anjay_fw_update_get_tcp_request_timeout_t *get_tcp_request_timeout; } anjay_fw_update_handlers_t; Now, the ``anjay_fw_update_get_security_config_t`` job is to fill @@ -322,6 +335,15 @@ Now, the ``anjay_fw_update_get_security_config_t`` job is to fill * to be used. */ avs_net_socket_tls_ciphersuites_t tls_ciphersuites; + + /* + * Server Name Indicator to use for authenticating with the peer during + * secure TLS connection. The value is passed to the underlying TLS library + * that need to take this variable into account for it make any effect. This + * field is optional and can be left zero-initialized. If not set the + * integration layer should use the Server URI instead. + */ + const char *server_name_indication; } anjay_security_config_t; We've already seen in previous sections how to configure diff --git a/example_configs/embedded_lwm2m10/anjay/anjay_config.h b/example_configs/embedded_lwm2m10/anjay/anjay_config.h index 919b3837..ad940c98 100644 --- a/example_configs/embedded_lwm2m10/anjay/anjay_config.h +++ b/example_configs/embedded_lwm2m10/anjay/anjay_config.h @@ -634,6 +634,17 @@ * in the open source version. */ /* #undef ANJAY_WITH_MODULE_OSCORE */ + +/** + * If enable Anjay doesn't handle composite operation (read, observe and write). + * Its use makes sense for LWM2M v1.1 upwards. + * + * This flag can be used to reduce the size of the resulting code. + * + * If active, anjay will respond with message code 5.01 Not Implemented to any + * composite type request. + */ +/* #undef ANJAY_WITHOUT_COMPOSITE_OPERATIONS */ /**@}*/ #endif // ANJAY_CONFIG_H diff --git a/example_configs/embedded_lwm2m11/anjay/anjay_config.h b/example_configs/embedded_lwm2m11/anjay/anjay_config.h index dabb5e8d..0c8d2b2f 100644 --- a/example_configs/embedded_lwm2m11/anjay/anjay_config.h +++ b/example_configs/embedded_lwm2m11/anjay/anjay_config.h @@ -634,6 +634,17 @@ * in the open source version. */ /* #undef ANJAY_WITH_MODULE_OSCORE */ + +/** + * If enable Anjay doesn't handle composite operation (read, observe and write). + * Its use makes sense for LWM2M v1.1 upwards. + * + * This flag can be used to reduce the size of the resulting code. + * + * If active, anjay will respond with message code 5.01 Not Implemented to any + * composite type request. + */ +/* #undef ANJAY_WITHOUT_COMPOSITE_OPERATIONS */ /**@}*/ #endif // ANJAY_CONFIG_H diff --git a/example_configs/linux_lwm2m10/anjay/anjay_config.h b/example_configs/linux_lwm2m10/anjay/anjay_config.h index 0812b22e..2a0f6d0c 100644 --- a/example_configs/linux_lwm2m10/anjay/anjay_config.h +++ b/example_configs/linux_lwm2m10/anjay/anjay_config.h @@ -634,6 +634,17 @@ * in the open source version. */ /* #undef ANJAY_WITH_MODULE_OSCORE */ + +/** + * If enable Anjay doesn't handle composite operation (read, observe and write). + * Its use makes sense for LWM2M v1.1 upwards. + * + * This flag can be used to reduce the size of the resulting code. + * + * If active, anjay will respond with message code 5.01 Not Implemented to any + * composite type request. + */ +/* #undef ANJAY_WITHOUT_COMPOSITE_OPERATIONS */ /**@}*/ #endif // ANJAY_CONFIG_H diff --git a/example_configs/linux_lwm2m11/anjay/anjay_config.h b/example_configs/linux_lwm2m11/anjay/anjay_config.h index 7fb7c704..e8170994 100644 --- a/example_configs/linux_lwm2m11/anjay/anjay_config.h +++ b/example_configs/linux_lwm2m11/anjay/anjay_config.h @@ -634,6 +634,17 @@ * in the open source version. */ /* #undef ANJAY_WITH_MODULE_OSCORE */ + +/** + * If enable Anjay doesn't handle composite operation (read, observe and write). + * Its use makes sense for LWM2M v1.1 upwards. + * + * This flag can be used to reduce the size of the resulting code. + * + * If active, anjay will respond with message code 5.01 Not Implemented to any + * composite type request. + */ +/* #undef ANJAY_WITHOUT_COMPOSITE_OPERATIONS */ /**@}*/ #endif // ANJAY_CONFIG_H diff --git a/include_public/anjay/advanced_fw_update.h b/include_public/anjay/advanced_fw_update.h index c3643c98..ece8a0f6 100644 --- a/include_public/anjay/advanced_fw_update.h +++ b/include_public/anjay/advanced_fw_update.h @@ -417,17 +417,29 @@ typedef int anjay_advanced_fw_update_perform_upgrade_t( * to NULL), @ref anjay_security_config_from_dm will be used as a default * way to get security information. * - * In that (no user-defined handler) case, anjay_security_config_pkix() - * will be used as an additional fallback if ANJAY_WITH_LWM2M11 is - * enabled and a valid trust store is available (either specified through - * use_system_trust_store, trust_store_certs or - * trust_store_crls fields in anjay_configuration_t, or obtained - * via /est/crts request if est_cacerts_policy is set to + * WARNING: If the aforementioned @ref + * anjay_security_config_from_dm function won't find any server + * connection that matches the download_uri by protocol, + * hostname and port triple, it'll attempt to match a configuration just by the + * hostname. This may cause Anjay to use wrong security configuration, e.g. in + * case when both CoAPS LwM2M server and HTTPS firmware package server have the + * same hostname, but require different security configs. + * + * If no user-defined handler is provided and the call to + * @ref anjay_security_config_from_dm fails (including case when no matching + * LwM2M Security Object instance is found, even just by the hostname), + * @ref anjay_security_config_pkix will be used as an additional fallback + * if ANJAY_WITH_LWM2M11 is enabled and a valid trust store is available + * (either specified through use_system_trust_store, + * trust_store_certs or trust_store_crls fields in + * anjay_configuration_t, or obtained via /est/crts request if + * est_cacerts_policy is set to * ANJAY_EST_CACERTS_IF_EST_CONFIGURED or * ANJAY_EST_CACERTS_ALWAYS). * - * You may also use these functions yourself, for example as a fallback - * mechanism. + * You may also use those aforementioned functions + * (@ref anjay_security_config_from_dm, @ref anjay_security_config_pkix) in + * your callback, for example as a fallback mechanism. * * @param iid Instance ID of an Advanced Firmware Object which * tries to get security config. @@ -486,6 +498,32 @@ typedef int anjay_advanced_fw_update_get_security_config_t( typedef avs_coap_udp_tx_params_t anjay_advanced_fw_update_get_coap_tx_params_t( anjay_iid_t iid, void *user_ptr, const char *download_uri); +/** + * Returns request timeout to be used during firmware update over CoAP+TCP or + * HTTP. + * + * If this handler is not implemented at all (with the corresponding field set + * to NULL), coap_tcp_request_timeout from anjay_t object + * will be used for CoAP+TCP, and AVS_NET_SOCKET_DEFAULT_RECV_TIMEOUT + * (i.e., 30 seconds) will be used for HTTP. + * + * NOTE: This callback is called even for non-TCP downloads, + * but the returned transmission parameters are ignored in that case. + * + * @param iid Instance ID of an Advanced Firmware Object which query + * tx_params. + * + * @param user_ptr Opaque pointer to user data, as passed to + * @ref anjay_advanced_fw_update_instance_add . + * + * @param download_uri Target firmware URI. + * + * @returns The desired request timeout. If the value returned is non-positive + * (including zero and invalid value), the default will be used. + */ +typedef avs_time_duration_t anjay_advanced_fw_update_get_tcp_request_timeout_t( + anjay_iid_t iid, void *user_ptr, const char *download_uri); + /** * Handler callbacks that shall implement the platform-specific part of firmware * update process. @@ -566,6 +604,10 @@ typedef struct { /** Queries CoAP transmission parameters to be used during firmware * update; @ref anjay_advanced_fw_update_get_coap_tx_params_t */ anjay_advanced_fw_update_get_coap_tx_params_t *get_coap_tx_params; + + /** Queries request timeout to be used during firmware update over CoAP+TCP + * or HTTP; @ref anjay_advanced_fw_update_get_tcp_request_timeout */ + anjay_advanced_fw_update_get_tcp_request_timeout_t *get_tcp_request_timeout; } anjay_advanced_fw_update_handlers_t; /** @@ -882,10 +924,11 @@ anjay_advanced_fw_update_get_last_state_change_time(anjay_t *anjay, * When PULL-mode downloads are suspended, * @ref anjay_advanced_fw_update_stream_open_t will NOT be * called when a download request is issued. However, - * @ref anjay_advanced_fw_update_get_security_config_t and - * @ref anjay_advanced_fw_update_get_coap_tx_params_t will be called. You may - * call @ref anjay_advanced_fw_update_pull_reconnect from one of these functions - * if you decide to accept the download immediately after all. + * @ref anjay_advanced_fw_update_get_security_config_t, + * @ref anjay_advanced_fw_update_get_coap_tx_params_t and + * @ref anjay_advanced_fw_update_get_tcp_request_timeout_t will be called. You + * may call @ref anjay_advanced_fw_update_pull_reconnect from one of these + * functions if you decide to accept the download immediately after all. * * @param anjay Anjay object to operate on. */ diff --git a/include_public/anjay/anjay_config.h.in b/include_public/anjay/anjay_config.h.in index fd4ac707..7f1bbdc6 100644 --- a/include_public/anjay/anjay_config.h.in +++ b/include_public/anjay/anjay_config.h.in @@ -634,6 +634,17 @@ * in the open source version. */ #cmakedefine ANJAY_WITH_MODULE_OSCORE + +/** + * If enable Anjay doesn't handle composite operation (read, observe and write). + * Its use makes sense for LWM2M v1.1 upwards. + * + * This flag can be used to reduce the size of the resulting code. + * + * If active, anjay will respond with message code 5.01 Not Implemented to any + * composite type request. + */ +#cmakedefine ANJAY_WITHOUT_COMPOSITE_OPERATIONS /**@}*/ #endif // ANJAY_CONFIG_H diff --git a/include_public/anjay/core.h b/include_public/anjay/core.h index 9aab4269..6413c41f 100644 --- a/include_public/anjay/core.h +++ b/include_public/anjay/core.h @@ -1309,6 +1309,15 @@ typedef struct { * to be used. */ avs_net_socket_tls_ciphersuites_t tls_ciphersuites; + + /* + * Server Name Indicator to use for authenticating with the peer during + * secure TLS connection. The value is passed to the underlying TLS library + * that need to take this variable into account for it make any effect. This + * field is optional and can be left zero-initialized. If not set the + * integration layer should use the Server URI instead. + */ + const char *server_name_indication; } anjay_security_config_t; /** diff --git a/include_public/anjay/download.h b/include_public/anjay/download.h index 407efec1..c5d6f0f2 100644 --- a/include_public/anjay/download.h +++ b/include_public/anjay/download.h @@ -184,6 +184,19 @@ typedef struct anjay_download_config { */ avs_coap_udp_tx_params_t *coap_tx_params; + /** + * Time of inactivity that will cause the download to time out when using + * TCP-based transports (i.e., CoAP+TCP or HTTP). + * + * If uninitialized or otherwise non-positive (including zero and invalid + * value), the value passed as + * anjay_configuration_t::coap_tcp_request_timeout (or its default, + * which is 30 seconds) will be used for CoAP+TCP, and + * AVS_NET_SOCKET_DEFAULT_RECV_TIMEOUT (i.e., 30 seconds) will be + * used for HTTP. + */ + avs_time_duration_t tcp_request_timeout; + /** * If set to true, the downloader module will attempt performing downloads * over the same sockets as existing LwM2M Servers (if the download URI is diff --git a/include_public/anjay/fw_update.h b/include_public/anjay/fw_update.h index 1c6ed012..e9149a19 100644 --- a/include_public/anjay/fw_update.h +++ b/include_public/anjay/fw_update.h @@ -405,17 +405,29 @@ typedef int anjay_fw_update_perform_upgrade_t(void *user_ptr); * to NULL), @ref anjay_security_config_from_dm will be used as a default * way to get security information. * - * In that (no user-defined handler) case, anjay_security_config_pkix() - * will be used as an additional fallback if ANJAY_WITH_LWM2M11 is - * enabled and a valid trust store is available (either specified through - * use_system_trust_store, trust_store_certs or - * trust_store_crls fields in anjay_configuration_t, or obtained - * via /est/crts request if est_cacerts_policy is set to + * WARNING: If the aforementioned @ref + * anjay_security_config_from_dm function won't find any server + * connection that matches the download_uri by protocol, + * hostname and port triple, it'll attempt to match a configuration just by the + * hostname. This may cause Anjay to use wrong security configuration, e.g. in + * case when both CoAPS LwM2M server and HTTPS firmware package server have the + * same hostname, but require different security configs. + * + * If no user-defined handler is provided and the call to + * @ref anjay_security_config_from_dm fails (including case when no matching + * LwM2M Security Object instance is found, even just by the hostname), + * @ref anjay_security_config_pkix will be used as an additional fallback + * if ANJAY_WITH_LWM2M11 is enabled and a valid trust store is available + * (either specified through use_system_trust_store, + * trust_store_certs or trust_store_crls fields in + * anjay_configuration_t, or obtained via /est/crts request if + * est_cacerts_policy is set to * ANJAY_EST_CACERTS_IF_EST_CONFIGURED or * ANJAY_EST_CACERTS_ALWAYS). * - * You may also use these functions yourself, for example as a fallback - * mechanism. + * You may also use those aforementioned functions + * (@ref anjay_security_config_from_dm, @ref anjay_security_config_pkix) in + * your callback, for example as a fallback mechanism. * * @param user_ptr Opaque pointer to user data, as passed to * @ref anjay_fw_update_install @@ -466,6 +478,30 @@ typedef int anjay_fw_update_get_security_config_t( typedef avs_coap_udp_tx_params_t anjay_fw_update_get_coap_tx_params_t(void *user_ptr, const char *download_uri); +/** + * Returns request timeout to be used during firmware update over CoAP+TCP or + * HTTP. + * + * If this handler is not implemented at all (with the corresponding field set + * to NULL), coap_tcp_request_timeout from anjay_t object + * will be used for CoAP+TCP, and AVS_NET_SOCKET_DEFAULT_RECV_TIMEOUT + * (i.e., 30 seconds) will be used for HTTP. + * + * NOTE: This callback is called even for non-TCP downloads, + * but the returned transmission parameters are ignored in that case. + * + * @param user_ptr Opaque pointer to user data, as passed to + * @ref anjay_fw_update_install . + * + * @param download_uri Target firmware URI. + * + * @returns The desired request timeout. If the value returned is non-positive + * (including zero and invalid value), the default will be used. + */ +typedef avs_time_duration_t +anjay_fw_update_get_tcp_request_timeout_t(void *user_ptr, + const char *download_uri); + /** * Handler callbacks that shall implement the platform-specific part of firmware * update process. @@ -543,6 +579,10 @@ typedef struct { /** Queries CoAP transmission parameters to be used during firmware * update; @ref anjay_fw_update_get_coap_tx_params_t */ anjay_fw_update_get_coap_tx_params_t *get_coap_tx_params; + + /** Queries request timeout to be used during firmware update over CoAP+TCP + * or HTTP; @ref anjay_advanced_fw_update_get_tcp_request_timeout */ + anjay_fw_update_get_tcp_request_timeout_t *get_tcp_request_timeout; } anjay_fw_update_handlers_t; /** @@ -623,8 +663,9 @@ int anjay_fw_update_set_result(anjay_t *anjay, anjay_fw_update_result_t result); * * When PULL-mode downloads are suspended, @ref anjay_fw_update_stream_open_t * will NOT be called when a download request is issued. - * However, @ref anjay_fw_update_get_security_config_t and - * @ref anjay_fw_update_get_coap_tx_params_t will be called. You may call + * However, @ref anjay_fw_update_get_security_config_t, + * @ref anjay_fw_update_get_coap_tx_params_t and + * @ref anjay_fw_update_get_tcp_request_timeout_t will be called. You may call * @ref anjay_fw_update_pull_reconnect from one of these functions if you decide * to accept the download immediately after all. * diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..2175da4e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,18 @@ +# Copyright 2017-2023 AVSystem +# AVSystem Anjay LwM2M SDK +# All rights reserved. +# +# Licensed under the AVSystem-5-clause License. +# See the attached LICENSE file for details. + +aiocoap>=0.4 +cbor2>=5.4.2 +cryptography>=40.0.2 +GitPython>=3.1.18 +linuxdoc==20230827 +openpyxl>=3.1.2 +packaging>=21.3 +powercmd>=0.3.6 +pyparsing>=3.1.1 +Sphinx>=5.3.0 +sphinx-rtd-theme>=1.3.0 diff --git a/src/anjay_config_log.h b/src/anjay_config_log.h index b907acb7..6f993089 100644 --- a/src/anjay_config_log.h +++ b/src/anjay_config_log.h @@ -34,6 +34,11 @@ static inline void _anjay_log_feature_list(void) { #else // ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID _anjay_log(anjay, TRACE, "ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID = OFF"); #endif // ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID +#ifdef ANJAY_WITHOUT_COMPOSITE_OPERATIONS + _anjay_log(anjay, TRACE, "ANJAY_WITHOUT_COMPOSITE_OPERATIONS = ON"); +#else // ANJAY_WITHOUT_COMPOSITE_OPERATIONS + _anjay_log(anjay, TRACE, "ANJAY_WITHOUT_COMPOSITE_OPERATIONS = OFF"); +#endif // ANJAY_WITHOUT_COMPOSITE_OPERATIONS #ifdef ANJAY_WITHOUT_DEREGISTER _anjay_log(anjay, TRACE, "ANJAY_WITHOUT_DEREGISTER = ON"); #else // ANJAY_WITHOUT_DEREGISTER diff --git a/src/anjay_modules/anjay_utils_core.h b/src/anjay_modules/anjay_utils_core.h index 26dc8ae3..867134ef 100644 --- a/src/anjay_modules/anjay_utils_core.h +++ b/src/anjay_modules/anjay_utils_core.h @@ -398,6 +398,9 @@ typedef struct { avs_crypto_private_key_info_t *client_key; avs_net_socket_dane_tlsa_record_t *dane_tlsa_record; avs_net_socket_tls_ciphersuites_t ciphersuites; +#ifdef ANJAY_WITH_LWM2M11 + char server_name_indication[256]; +#endif } anjay_security_config_cache_t; void _anjay_security_config_cache_cleanup(anjay_security_config_cache_t *cache); diff --git a/src/core/anjay_core.c b/src/core/anjay_core.c index 2f9f2680..2931776f 100644 --- a/src/core/anjay_core.c +++ b/src/core/anjay_core.c @@ -47,7 +47,7 @@ VISIBILITY_SOURCE_BEGIN #ifndef ANJAY_VERSION -# define ANJAY_VERSION "3.5.0" +# define ANJAY_VERSION "3.6.0" #endif // ANJAY_VERSION #ifdef ANJAY_WITH_LWM2M11 diff --git a/src/core/anjay_dm_core.c b/src/core/anjay_dm_core.c index e5d278b4..ef74ec32 100644 --- a/src/core/anjay_dm_core.c +++ b/src/core/anjay_dm_core.c @@ -627,8 +627,12 @@ static int invoke_transactional_action(anjay_unlocked_t *anjay, break; #ifdef ANJAY_WITH_LWM2M11 case ANJAY_ACTION_WRITE_COMPOSITE: +# ifdef ANJAY_WITHOUT_COMPOSITE_OPERATIONS + retval = ANJAY_ERR_NOT_IMPLEMENTED; +# else // ANJAY_WITHOUT_COMPOSITE_OPERATIONS assert(in_ctx); retval = _anjay_dm_write_composite(anjay, request, ssid, in_ctx); +# endif // ANJAY_WITHOUT_COMPOSITE_OPERATIONS break; #endif // ANJAY_WITH_LWM2M11 case ANJAY_ACTION_CREATE: @@ -656,8 +660,12 @@ static int invoke_action(anjay_connection_ref_t connection, return _anjay_dm_read_or_observe(connection, obj, request); #ifdef ANJAY_WITH_LWM2M11 case ANJAY_ACTION_READ_COMPOSITE: +# ifdef ANJAY_WITHOUT_COMPOSITE_OPERATIONS + return ANJAY_ERR_NOT_IMPLEMENTED; +# else // ANJAY_WITHOUT_COMPOSITE_OPERATIONS return _anjay_dm_read_or_observe_composite(connection, request, in_ctx); -#endif // ANJAY_WITH_LWM2M11 +# endif // ANJAY_WITHOUT_COMPOSITE_OPERATIONS +#endif // ANJAY_WITH_LWM2M11 case ANJAY_ACTION_DISCOVER: return dm_discover(connection, obj, request); case ANJAY_ACTION_WRITE: diff --git a/src/core/dm/anjay_dm_read.c b/src/core/dm/anjay_dm_read.c index 3b358507..c74039aa 100644 --- a/src/core/dm/anjay_dm_read.c +++ b/src/core/dm/anjay_dm_read.c @@ -527,6 +527,7 @@ int _anjay_dm_read_resource_u32_array(anjay_unlocked_t *anjay, return result; } +# ifndef ANJAY_WITHOUT_COMPOSITE_OPERATIONS static int cache_all_paths(anjay_unlocked_input_ctx_t *in_ctx, AVS_LIST(anjay_uri_path_t) *out_paths) { AVS_LIST(anjay_uri_path_t) *endptr = out_paths; @@ -571,13 +572,13 @@ int _anjay_dm_read_or_observe_composite(anjay_connection_ref_t connection, } if (request->observe) { dm_log(DEBUG, _("Observe Composite")); -# ifdef ANJAY_WITH_OBSERVE +# ifdef ANJAY_WITH_OBSERVE result = _anjay_observe_composite_handle(connection, cached_paths, request); -# else // ANJAY_WITH_OBSERVE +# else // ANJAY_WITH_OBSERVE dm_log(ERROR, _("Observe support disabled")); return ANJAY_ERR_BAD_OPTION; -# endif // ANJAY_WITH_OBSERVE +# endif // ANJAY_WITH_OBSERVE } else { anjay_unlocked_t *anjay = _anjay_from_server(connection.server); const anjay_msg_details_t details = _anjay_dm_response_details_for_read( @@ -641,4 +642,5 @@ int _anjay_dm_read_or_observe_composite(anjay_connection_ref_t connection, AVS_LIST_CLEAR(&cached_paths); return result; } -#endif // ANJAY_WITH_LWM2M11 +# endif // ANJAY_WITHOUT_COMPOSITE_OPERATIONS +#endif // ANJAY_WITH_LWM2M11 diff --git a/src/core/dm/anjay_dm_read.h b/src/core/dm/anjay_dm_read.h index 2e689e25..b027bc4b 100644 --- a/src/core/dm/anjay_dm_read.h +++ b/src/core/dm/anjay_dm_read.h @@ -39,11 +39,12 @@ int _anjay_dm_read_and_destroy_ctx(anjay_unlocked_t *anjay, anjay_ssid_t requesting_ssid, anjay_unlocked_output_ctx_t **out_ctx_ptr); -#ifdef ANJAY_WITH_LWM2M11 +#if defined(ANJAY_WITH_LWM2M11) && !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS) int _anjay_dm_read_or_observe_composite(anjay_connection_ref_t connection, const anjay_request_t *request, anjay_unlocked_input_ctx_t *in_ctx); -#endif // ANJAY_WITH_LWM2M11 +#endif // defined(ANJAY_WITH_LWM2M11) && + // !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS) VISIBILITY_PRIVATE_HEADER_END diff --git a/src/core/dm/anjay_dm_write.c b/src/core/dm/anjay_dm_write.c index 9102ad07..652f7a8a 100644 --- a/src/core/dm/anjay_dm_write.c +++ b/src/core/dm/anjay_dm_write.c @@ -405,7 +405,7 @@ int _anjay_dm_write(anjay_unlocked_t *anjay, return result; } -#ifdef ANJAY_WITH_LWM2M11 +#if defined(ANJAY_WITH_LWM2M11) && !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS) int _anjay_dm_write_composite(anjay_unlocked_t *anjay, const anjay_request_t *request, anjay_ssid_t ssid, @@ -476,7 +476,8 @@ int _anjay_dm_write_composite(anjay_unlocked_t *anjay, _anjay_notify_clear_queue(¬ify_queue); return result; } -#endif // ANJAY_WITH_LWM2M11 +#endif // defined(ANJAY_WITH_LWM2M11) && + // !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS) int _anjay_dm_write_created_instance_and_move_to_next_entry( anjay_unlocked_t *anjay, diff --git a/src/core/dm/anjay_dm_write.h b/src/core/dm/anjay_dm_write.h index 7ad5bf69..83416ffa 100644 --- a/src/core/dm/anjay_dm_write.h +++ b/src/core/dm/anjay_dm_write.h @@ -23,12 +23,13 @@ int _anjay_dm_write(anjay_unlocked_t *anjay, anjay_ssid_t ssid, anjay_unlocked_input_ctx_t *in_ctx); -#ifdef ANJAY_WITH_LWM2M11 +#if defined(ANJAY_WITH_LWM2M11) && !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS) int _anjay_dm_write_composite(anjay_unlocked_t *anjay, const anjay_request_t *request, anjay_ssid_t ssid, anjay_unlocked_input_ctx_t *in_ctx); -#endif // ANJAY_WITH_LWM2M11 +#endif // defined(ANJAY_WITH_LWM2M11) && + // !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS) /** * NOTE: This function is used in one situation, that is: after LwM2M Create to diff --git a/src/core/downloader/anjay_coap.c b/src/core/downloader/anjay_coap.c index e8e942a6..dd18f00c 100644 --- a/src/core/downloader/anjay_coap.c +++ b/src/core/downloader/anjay_coap.c @@ -51,9 +51,18 @@ typedef struct { char dtls_session_buffer[ANJAY_DTLS_SESSION_BUFFER_SIZE]; avs_coap_exchange_id_t exchange_id; + union { # ifdef WITH_AVS_COAP_UDP - avs_coap_udp_tx_params_t tx_params; + struct { + avs_coap_udp_tx_params_t tx_params; + } udp; # endif // WITH_AVS_COAP_UDP +# ifdef WITH_AVS_COAP_TCP + struct { + avs_time_duration_t request_timeout; + } tcp; +# endif // WITH_AVS_COAP_TCP + } protocol; avs_coap_ctx_t *coap; avs_sched_handle_t job_start; @@ -365,7 +374,7 @@ static avs_error_t reset_coap_ctx(anjay_coap_download_ctx_t *ctx) { // handle an incoming request, and contexts used for downloads don't // expect receiving any requests that would need handling. if ((ctx->coap = avs_coap_udp_ctx_create( - _anjay_get_coap_sched(anjay), &ctx->tx_params, + _anjay_get_coap_sched(anjay), &ctx->protocol.udp.tx_params, anjay->in_shared_buffer, anjay->out_shared_buffer, NULL, anjay->prng_ctx.ctx))) { avs_coap_set_exchange_max_time(ctx->coap, @@ -379,7 +388,7 @@ static avs_error_t reset_coap_ctx(anjay_coap_download_ctx_t *ctx) { if ((ctx->coap = avs_coap_tcp_ctx_create( _anjay_get_coap_sched(anjay), anjay->in_shared_buffer, anjay->out_shared_buffer, anjay->coap_tcp_max_options_size, - anjay->coap_tcp_request_timeout, anjay->prng_ctx.ctx))) { + ctx->protocol.tcp.request_timeout, anjay->prng_ctx.ctx))) { avs_coap_set_exchange_max_time(ctx->coap, anjay->tcp_exchange_timeout); } @@ -563,7 +572,11 @@ _anjay_downloader_coap_ctx_new(anjay_downloader_t *dl, ? cfg->security_config.tls_ciphersuites : anjay->default_tls_ciphersuites, .backend_configuration = anjay->socket_config, - .prng_ctx = anjay->prng_ctx.ctx + .prng_ctx = anjay->prng_ctx.ctx, +# ifdef ANJAY_WITH_LWM2M11 + .server_name_indication = + cfg->security_config.server_name_indication, +# endif // ANJAY_WITH_LWM2M11 }; ssl_config.backend_configuration.reuse_addr = 1; ssl_config.backend_configuration.preferred_endpoint = @@ -647,19 +660,33 @@ _anjay_downloader_coap_ctx_new(anjay_downloader_t *dl, } # ifdef WITH_AVS_COAP_UDP - if (!cfg->coap_tx_params) { - ctx->tx_params = anjay->udp_tx_params; - } else { - const char *error_string = NULL; - if (avs_coap_udp_tx_params_valid(cfg->coap_tx_params, &error_string)) { - ctx->tx_params = *cfg->coap_tx_params; + if (ctx->transport == ANJAY_SOCKET_TRANSPORT_UDP) { + if (!cfg->coap_tx_params) { + ctx->protocol.udp.tx_params = anjay->udp_tx_params; } else { - dl_log(ERROR, _("invalid tx_params: ") "%s", error_string); - goto error; + const char *error_string = NULL; + if (avs_coap_udp_tx_params_valid(cfg->coap_tx_params, + &error_string)) { + ctx->protocol.udp.tx_params = *cfg->coap_tx_params; + } else { + dl_log(ERROR, _("invalid tx_params: ") "%s", error_string); + goto error; + } } } # endif // WITH_AVS_COAP_UDP +# ifdef WITH_AVS_COAP_TCP + if (ctx->transport == ANJAY_SOCKET_TRANSPORT_TCP) { + if (avs_time_duration_less(AVS_TIME_DURATION_ZERO, + cfg->tcp_request_timeout)) { + ctx->protocol.tcp.request_timeout = cfg->tcp_request_timeout; + } else { + ctx->protocol.tcp.request_timeout = anjay->coap_tcp_request_timeout; + } + } +# endif // WITH_AVS_COAP_TCP + if (_anjay_downloader_sched_reconnect_ctx((anjay_download_ctx_t *) ctx)) { dl_log(ERROR, _("could not schedule download connect job")); err = avs_errno(AVS_ENOMEM); diff --git a/src/core/downloader/anjay_http.c b/src/core/downloader/anjay_http.c index f3432c1d..a68e7a09 100644 --- a/src/core/downloader/anjay_http.c +++ b/src/core/downloader/anjay_http.c @@ -35,6 +35,7 @@ typedef struct { avs_net_ssl_configuration_t ssl_configuration; anjay_security_config_cache_t security_config_cache; avs_net_resolved_endpoint_t preferred_endpoint; + avs_time_duration_t request_timeout; avs_http_t *client; avs_url_t *parsed_url; avs_stream_t *stream; @@ -158,7 +159,7 @@ handle_http_packet_with_locked_buffer(AVS_LIST(anjay_download_ctx_t) *ctx_ptr, // if anjay_download_suspend() was called if (ctx->next_action_job) { int result = AVS_RESCHED_DELAYED(&ctx->next_action_job, - AVS_NET_SOCKET_DEFAULT_RECV_TIMEOUT); + ctx->request_timeout); assert(!result); (void) result; } @@ -302,8 +303,8 @@ static void send_request_unlocked(anjay_unlocked_t *anjay, uintptr_t id) { avs_http_set_header_storage(ctx->stream, NULL); if (AVS_SCHED_DELAYED(anjay->sched, &ctx->next_action_job, - AVS_NET_SOCKET_DEFAULT_RECV_TIMEOUT, timeout_job, - &ctx->common.id, sizeof(ctx->common.id))) { + ctx->request_timeout, timeout_job, &ctx->common.id, + sizeof(ctx->common.id))) { dl_log(ERROR, _("could not schedule timeout job")); _anjay_downloader_abort_transfer( ctx_ptr, _anjay_download_status_failed(avs_errno(AVS_ENOMEM))); @@ -605,6 +606,13 @@ _anjay_downloader_http_ctx_new(anjay_downloader_t *dl, avs_http_ssl_configuration(ctx->client, &ctx->ssl_configuration); avs_http_ssl_pre_connect_cb(ctx->client, http_ssl_pre_connect_cb, ctx); + if (avs_time_duration_less(AVS_TIME_DURATION_ZERO, + cfg->tcp_request_timeout)) { + ctx->request_timeout = cfg->tcp_request_timeout; + } else { + ctx->request_timeout = AVS_NET_SOCKET_DEFAULT_RECV_TIMEOUT; + } + if (!(ctx->parsed_url = avs_url_parse(cfg->url))) { err = avs_errno(AVS_EINVAL); goto error; diff --git a/src/core/io/anjay_dynamic.c b/src/core/io/anjay_dynamic.c index fc1a928d..9af6f9ee 100644 --- a/src/core/io/anjay_dynamic.c +++ b/src/core/io/anjay_dynamic.c @@ -116,7 +116,7 @@ static const dynamic_format_def_t SUPPORTED_HIERARCHICAL_FORMATS[] = { AVS_STATIC_ASSERT(AVS_ARRAY_SIZE(SUPPORTED_HIERARCHICAL_FORMATS) > 1, at_least_one_hierarchical_format_must_be_enabled); -#ifdef ANJAY_WITH_LWM2M11 +#if defined(ANJAY_WITH_LWM2M11) && !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS) static const dynamic_format_def_t SUPPORTED_COMPOSITE_READ_FORMATS[] = { # ifdef ANJAY_WITH_CBOR { AVS_COAP_FORMAT_SENML_CBOR, _anjay_input_senml_cbor_composite_read_create, @@ -128,7 +128,10 @@ static const dynamic_format_def_t SUPPORTED_COMPOSITE_READ_FORMATS[] = { # endif // ANJAY_WITH_SENML_JSON { AVS_COAP_FORMAT_NONE, NULL, NULL } }; +#endif // defined(ANJAY_WITH_LWM2M11) && + // !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS) +#ifdef ANJAY_WITH_LWM2M11 static const dynamic_format_def_t SUPPORTED_COMPOSITE_WRITE_FORMATS[] = { # ifdef ANJAY_WITH_CBOR { AVS_COAP_FORMAT_SENML_CBOR, _anjay_input_senml_cbor_create, NULL }, @@ -241,11 +244,12 @@ int _anjay_output_dynamic_construct(anjay_unlocked_output_ctx_t **out_ctx, (void) ((def = find_format(SUPPORTED_SIMPLE_FORMATS, format)) || (def = find_format(SUPPORTED_HIERARCHICAL_FORMATS, format))); break; -#ifdef ANJAY_WITH_LWM2M11 +#if defined(ANJAY_WITH_LWM2M11) && !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS) case ANJAY_ACTION_READ_COMPOSITE: def = find_format(SUPPORTED_COMPOSITE_READ_FORMATS, format); break; -#endif // ANJAY_WITH_LWM2M11 +#endif // defined(ANJAY_WITH_LWM2M11) && + // !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS) default: break; } @@ -284,12 +288,14 @@ int _anjay_input_dynamic_construct_raw(anjay_unlocked_input_ctx_t **out, constructor = def->input_ctx_constructor; } break; +# ifndef ANJAY_WITHOUT_COMPOSITE_OPERATIONS case ANJAY_ACTION_READ_COMPOSITE: if ((def = find_format(SUPPORTED_COMPOSITE_READ_FORMATS, format))) { constructor = def->input_ctx_constructor; } break; -#endif // ANJAY_WITH_LWM2M11 +# endif // ANJAY_WITHOUT_COMPOSITE_OPERATIONS +#endif // ANJAY_WITH_LWM2M11 default: // Nothing to prepare - the action does not need an input context. return 0; diff --git a/src/core/observe/anjay_observe_core.c b/src/core/observe/anjay_observe_core.c index 26fe0d05..519865d5 100644 --- a/src/core/observe/anjay_observe_core.c +++ b/src/core/observe/anjay_observe_core.c @@ -940,15 +940,18 @@ static int read_as_batch(anjay_unlocked_t *anjay, int result = _anjay_dm_read_into_batch(builder, anjay, obj_ptr, path_info, connection_ssid, timestamp); -# ifdef ANJAY_WITH_LWM2M11 +# if defined(ANJAY_WITH_LWM2M11) \ + && !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS) if (action == ANJAY_ACTION_READ_COMPOSITE && (result == ANJAY_ERR_UNAUTHORIZED || result == ANJAY_ERR_NOT_FOUND)) { result = 0; } -# else // ANJAY_WITH_LWM2M11 +# else // defined(ANJAY_WITH_LWM2M11) && + // !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS) (void) action; -# endif // ANJAY_WITH_LWM2M11 +# endif // defined(ANJAY_WITH_LWM2M11) && + // !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS) if (!result && !(*out_batch = _anjay_batch_builder_compile(&builder))) { anjay_log(ERROR, _("out of memory")); result = -1; @@ -962,7 +965,8 @@ cast_to_const_batch_array(anjay_batch_t **batch_array) { return (const anjay_batch_t *const *) batch_array; } -# ifdef ANJAY_WITH_LWM2M11 +# if defined(ANJAY_WITH_LWM2M11) \ + && !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS) static anjay_uri_path_t get_composite_root_path(const anjay_batch_t *const *values, size_t values_count) { @@ -974,16 +978,19 @@ get_composite_root_path(const anjay_batch_t *const *values, } return prefix_buf; } -# endif // ANJAY_WITH_LWM2M11 +# endif // defined(ANJAY_WITH_LWM2M11) && + // !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS) static anjay_uri_path_t get_response_path(anjay_observation_value_t *value) { anjay_observation_t *observation = value->ref; -# ifdef ANJAY_WITH_LWM2M11 +# if defined(ANJAY_WITH_LWM2M11) \ + && !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS) if (observation->action == ANJAY_ACTION_READ_COMPOSITE) { return get_composite_root_path(cast_to_const_batch_array(value->values), observation->paths_count); } -# endif // ANJAY_WITH_LWM2M11 +# endif // defined(ANJAY_WITH_LWM2M11) && + // !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS) return observation->paths[0]; } @@ -1052,11 +1059,13 @@ initial_response_details(anjay_unlocked_t *anjay, anjay_lwm2m_version_t lwm2m_version, const anjay_batch_t *const *values) { bool requires_hierarchical_format; -# ifdef ANJAY_WITH_LWM2M11 +# if defined(ANJAY_WITH_LWM2M11) \ + && !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS) if (request->action == ANJAY_ACTION_READ_COMPOSITE) { requires_hierarchical_format = true; } else -# endif // ANJAY_WITH_LWM2M11 +# endif // defined(ANJAY_WITH_LWM2M11) && + // !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS) { assert(request->action == ANJAY_ACTION_READ); assert(values); @@ -1080,11 +1089,13 @@ static int send_initial_response(anjay_unlocked_t *anjay, } anjay_uri_path_t root_path = request->uri; -# ifdef ANJAY_WITH_LWM2M11 +# if defined(ANJAY_WITH_LWM2M11) \ + && !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS) if (request->action == ANJAY_ACTION_READ_COMPOSITE) { root_path = get_composite_root_path(values, values_count); } -# endif // ANJAY_WITH_LWM2M11 +# endif // defined(ANJAY_WITH_LWM2M11) && + // !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS) anjay_unlocked_output_ctx_t *out_ctx = NULL; int result = @@ -1258,7 +1269,8 @@ int _anjay_observe_handle(anjay_connection_ref_t ref, request); } -# ifdef ANJAY_WITH_LWM2M11 +# if defined(ANJAY_WITH_LWM2M11) \ + && !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS) int _anjay_observe_composite_handle(anjay_connection_ref_t ref, AVS_LIST(anjay_uri_path_t) paths, const anjay_request_t *request) { @@ -1271,7 +1283,8 @@ int _anjay_observe_composite_handle(anjay_connection_ref_t ref, }, request); } -# endif // ANJAY_WITH_LWM2M11 +# endif // defined(ANJAY_WITH_LWM2M11) && + // !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS) static int observe_gc_ssid_iterate(anjay_unlocked_t *anjay, anjay_ssid_t ssid, diff --git a/src/core/observe/anjay_observe_core.h b/src/core/observe/anjay_observe_core.h index d39fd50e..5e417c63 100644 --- a/src/core/observe/anjay_observe_core.h +++ b/src/core/observe/anjay_observe_core.h @@ -63,11 +63,13 @@ void _anjay_observe_gc(anjay_unlocked_t *anjay); int _anjay_observe_handle(anjay_connection_ref_t ref, const anjay_request_t *request); -# ifdef ANJAY_WITH_LWM2M11 +# if defined(ANJAY_WITH_LWM2M11) \ + && !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS) int _anjay_observe_composite_handle(anjay_connection_ref_t ref, AVS_LIST(anjay_uri_path_t) paths, const anjay_request_t *request); -# endif // ANJAY_WITH_LWM2M11 +# endif // defined(ANJAY_WITH_LWM2M11) && + // !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS) void _anjay_observe_interrupt(anjay_connection_ref_t ref); diff --git a/src/core/servers/anjay_connections.c b/src/core/servers/anjay_connections.c index 323dbdd8..735d025f 100644 --- a/src/core/servers/anjay_connections.c +++ b/src/core/servers/anjay_connections.c @@ -249,7 +249,7 @@ _anjay_connection_init_psk_security(anjay_unlocked_t *anjay, anjay_iid_t security_iid, anjay_rid_t identity_rid, anjay_rid_t secret_key_rid, - avs_net_security_info_t *security, + anjay_security_config_t *security, anjay_security_config_cache_t *cache) { assert(anjay); avs_error_t err; @@ -286,7 +286,13 @@ _anjay_connection_init_psk_security(anjay_unlocked_t *anjay, assert(element_count == 1); psk_info.identity = *cache->psk_identity; - *security = avs_net_security_info_from_psk(psk_info); + security->security_info = avs_net_security_info_from_psk(psk_info); +#ifdef ANJAY_WITH_LWM2M11 + if (avs_is_err((err = _anjay_server_read_sni(anjay, security_iid, security, + cache)))) { + return err; + } +#endif // ANJAY_WITH_LWM2M11 return AVS_OK; } @@ -472,7 +478,6 @@ recreate_socket(anjay_unlocked_t *anjay, def->get_dtls_handshake_timeouts(anjay); socket_config.additional_configuration_clb = anjay->additional_tls_config_clb; - socket_config.server_name_indication = inout_info->sni.sni; socket_config.use_connection_id = anjay->use_connection_id; socket_config.prng_ctx = anjay->prng_ctx.ctx; @@ -494,6 +499,8 @@ recreate_socket(anjay_unlocked_t *anjay, } else { socket_config.security = security_config.security_info; socket_config.ciphersuites = security_config.tls_ciphersuites; + socket_config.server_name_indication = + security_config.server_name_indication; if (avs_is_err((err = def->prepare_connection( anjay, connection, &socket_config, security_config.dane_tlsa_record, inout_info))) @@ -594,11 +601,9 @@ static avs_error_t refresh_connection(anjay_server_info_t *server, } } -void _anjay_server_connections_refresh( - anjay_server_info_t *server, - anjay_iid_t security_iid, - avs_url_t **move_uri, - const anjay_server_name_indication_t *sni) { +void _anjay_server_connections_refresh(anjay_server_info_t *server, + anjay_iid_t security_iid, + avs_url_t **move_uri) { anjay_connection_info_t server_info = { .ssid = server->ssid, .security_iid = security_iid, @@ -609,7 +614,6 @@ void _anjay_server_connections_refresh( avs_url_protocol(*move_uri)); *move_uri = NULL; } - memcpy(&server_info.sni, sni, sizeof(*sni)); if (security_iid != ANJAY_ID_INVALID) { server->last_used_security_iid = security_iid; @@ -730,3 +734,35 @@ bool _anjay_socket_transport_supported(anjay_unlocked_t *anjay, (void) anjay; return true; } + +#ifdef ANJAY_WITH_LWM2M11 +avs_error_t _anjay_server_read_sni(anjay_unlocked_t *anjay, + anjay_iid_t security_iid, + anjay_security_config_t *security, + anjay_security_config_cache_t *cache) { + avs_error_t err = AVS_OK; + + security->server_name_indication = NULL; + const anjay_uri_path_t server_sni = + MAKE_RESOURCE_PATH(ANJAY_DM_OID_SECURITY, security_iid, + ANJAY_DM_RID_SECURITY_SNI); + int result = _anjay_dm_read_resource_string( + anjay, &server_sni, cache->server_name_indication, + sizeof(cache->server_name_indication)); + if (!result) { + anjay_log(INFO, _("using SNI ") "%s" _(" for /0/") "%u", + cache->server_name_indication, (unsigned) security_iid); + security->server_name_indication = cache->server_name_indication; + } else if (result == ANJAY_ERR_NOT_FOUND + || result == ANJAY_ERR_METHOD_NOT_ALLOWED) { + anjay_log(INFO, _("no SNI for /0/") "%u" _(", using defaults"), + (unsigned) security_iid); + } else { + anjay_log(WARNING, _("reading ") "%s" _(" failed"), + ANJAY_DEBUG_MAKE_PATH(&server_sni)); + err = avs_errno(AVS_EPROTO); + } + + return err; +} +#endif // ANJAY_WITH_LWM2M11 diff --git a/src/core/servers/anjay_connections.h b/src/core/servers/anjay_connections.h index ed24c7b4..3fb588bc 100644 --- a/src/core/servers/anjay_connections.h +++ b/src/core/servers/anjay_connections.h @@ -242,10 +242,6 @@ avs_error_t _anjay_server_connection_internal_bring_online( void _anjay_connections_close(anjay_unlocked_t *anjay, anjay_connections_t *connections); -typedef struct { - char sni[256]; -} anjay_server_name_indication_t; - /** * Makes sure that socket connections for a given server are up-to-date with the * current configuration; (re)connects any sockets and schedules Register/Update @@ -261,20 +257,20 @@ typedef struct { * @param move_uri Pointer to a server URL to connect to. This function * will take ownership of data allocated inside that * object. - * - * @param sni Server Name Identification value to be used for - * certificate validation during TLS handshake. */ -void _anjay_server_connections_refresh( - anjay_server_info_t *server, - anjay_iid_t security_iid, - avs_url_t **move_uri, - const anjay_server_name_indication_t *sni); +void _anjay_server_connections_refresh(anjay_server_info_t *server, + anjay_iid_t security_iid, + avs_url_t **move_uri); #ifdef ANJAY_WITH_LWM2M11 void _anjay_server_update_last_ssl_alert_code(const anjay_server_info_t *info, uint8_t level, uint8_t description); + +avs_error_t _anjay_server_read_sni(anjay_unlocked_t *anjay, + anjay_iid_t security_iid, + anjay_security_config_t *security, + anjay_security_config_cache_t *cache); #endif // ANJAY_WITH_LWM2M11 bool _anjay_socket_transport_supported(anjay_unlocked_t *anjay, diff --git a/src/core/servers/anjay_connections_internal.h b/src/core/servers/anjay_connections_internal.h index 2bd52495..e31ccb6e 100644 --- a/src/core/servers/anjay_connections_internal.h +++ b/src/core/servers/anjay_connections_internal.h @@ -74,7 +74,7 @@ _anjay_connection_init_psk_security(anjay_unlocked_t *anjay, anjay_iid_t security_iid, anjay_rid_t identity_rid, anjay_rid_t secret_key_rid, - avs_net_security_info_t *security, + anjay_security_config_t *security, anjay_security_config_cache_t *cache); VISIBILITY_PRIVATE_HEADER_END diff --git a/src/core/servers/anjay_security.h b/src/core/servers/anjay_security.h index da2b4c5b..247a7c39 100644 --- a/src/core/servers/anjay_security.h +++ b/src/core/servers/anjay_security.h @@ -24,7 +24,6 @@ typedef struct { avs_url_t *uri; const anjay_transport_info_t *transport_info; bool is_encrypted; - anjay_server_name_indication_t sni; } anjay_connection_info_t; int _anjay_connection_security_generic_get_uri( diff --git a/src/core/servers/anjay_security_generic.c b/src/core/servers/anjay_security_generic.c index 7e2a05c6..00ac2cb0 100644 --- a/src/core/servers/anjay_security_generic.c +++ b/src/core/servers/anjay_security_generic.c @@ -290,6 +290,11 @@ static avs_error_t init_cert_security(anjay_unlocked_t *anjay, } } avs_stream_cleanup(&server_pk_membuf); +#ifdef ANJAY_WITH_LWM2M11 + if (avs_is_ok(err)) { + err = _anjay_server_read_sni(anjay, security_iid, security, cache); + } +#endif // ANJAY_WITH_LWM2M11 if (avs_is_err(err)) { return err; } @@ -348,8 +353,7 @@ static avs_error_t init_security(anjay_unlocked_t *anjay, case ANJAY_SECURITY_PSK: return _anjay_connection_init_psk_security( anjay, security_iid, ANJAY_DM_RID_SECURITY_PK_OR_IDENTITY, - ANJAY_DM_RID_SECURITY_SECRET_KEY, &security->security_info, - cache); + ANJAY_DM_RID_SECURITY_SECRET_KEY, security, cache); case ANJAY_SECURITY_CERTIFICATE: case ANJAY_SECURITY_EST: return init_cert_security(anjay, ssid, security_iid, security, diff --git a/src/core/servers/anjay_server_connections.c b/src/core/servers/anjay_server_connections.c index ae033cf5..851db567 100644 --- a/src/core/servers/anjay_server_connections.c +++ b/src/core/servers/anjay_server_connections.c @@ -228,31 +228,6 @@ static int select_security_instance(anjay_unlocked_t *anjay, return 0; } -#ifdef ANJAY_WITH_LWM2M11 -static int read_server_sni(anjay_unlocked_t *anjay, - anjay_iid_t security_iid, - anjay_server_name_indication_t *out_sni) { - out_sni->sni[0] = '\0'; - - const anjay_uri_path_t path = - MAKE_RESOURCE_PATH(ANJAY_DM_OID_SECURITY, security_iid, - ANJAY_DM_RID_SECURITY_SNI); - int result = _anjay_dm_read_resource_string(anjay, &path, out_sni->sni, - sizeof(out_sni->sni)); - if (result == ANJAY_ERR_NOT_FOUND - || result == ANJAY_ERR_METHOD_NOT_ALLOWED) { - anjay_log(TRACE, _("no SNI for /0/") "%u" _(", using defaults"), - (unsigned) security_iid); - return 0; - } - if (!result) { - anjay_log(TRACE, _("using SNI ") "%s" _(" for /0/") "%u", out_sni->sni, - (unsigned) security_iid); - } - return result; -} -#endif // ANJAY_WITH_LWM2M11 - void _anjay_active_server_refresh(anjay_server_info_t *server) { anjay_log(TRACE, _("refreshing SSID ") "%u", server->ssid); @@ -262,7 +237,6 @@ void _anjay_active_server_refresh(anjay_server_info_t *server) { int result = 0; anjay_iid_t security_iid = ANJAY_ID_INVALID; avs_url_t *uri = NULL; - anjay_server_name_indication_t sni = { "" }; if (server->ssid == ANJAY_SSID_BOOTSTRAP) { const anjay_transport_info_t *transport_info = NULL; if ((security_iid = _anjay_find_bootstrap_security_iid(server->anjay)) @@ -281,11 +255,7 @@ void _anjay_active_server_refresh(anjay_server_info_t *server) { transport_info->transport) ->letter)) >= 0) { -#ifdef ANJAY_WITH_LWM2M11 - result = read_server_sni(server->anjay, security_iid, &sni); -#else // ANJAY_WITH_LWM2M11 result = 0; -#endif // ANJAY_WITH_LWM2M11 } } } else { @@ -295,14 +265,10 @@ void _anjay_active_server_refresh(anjay_server_info_t *server) { &preferred_transport)) || (result = select_security_instance( server->anjay, server->ssid, &server->binding_mode, - preferred_transport, &security_iid, &uri)) -#ifdef ANJAY_WITH_LWM2M11 - || (result = read_server_sni(server->anjay, security_iid, &sni)) -#endif // ANJAY_WITH_LWM2M11 - ); + preferred_transport, &security_iid, &uri))); } if (!result) { - _anjay_server_connections_refresh(server, security_iid, &uri, &sni); + _anjay_server_connections_refresh(server, security_iid, &uri); } avs_url_free(uri); if (result) { diff --git a/src/modules/advanced_fw_update/anjay_advanced_fw_update.c b/src/modules/advanced_fw_update/anjay_advanced_fw_update.c index afa96307..4f18199c 100644 --- a/src/modules/advanced_fw_update/anjay_advanced_fw_update.c +++ b/src/modules/advanced_fw_update/anjay_advanced_fw_update.c @@ -92,6 +92,11 @@ typedef struct { size_t conflicting_instances_count; } advanced_fw_instance_t; +typedef struct { + anjay_iid_t iid; + anjay_download_handle_t download_handle; +} current_download_t; + typedef struct { anjay_dm_installed_object_t def_ptr; const anjay_unlocked_dm_object_def_t *def; @@ -107,7 +112,7 @@ typedef struct { size_t supplemental_iid_cache_count; # ifdef ANJAY_WITH_DOWNLOADER - anjay_download_handle_t download_handle; + current_download_t current_download; bool downloads_suspended; AVS_LIST(anjay_download_config_t) download_queue; # endif // ANJAY_WITH_DOWNLOADER @@ -452,6 +457,18 @@ static int get_coap_tx_params(anjay_unlocked_t *anjay, } return -1; } + +static avs_time_duration_t +get_tcp_request_timeout(anjay_unlocked_t *anjay, advanced_fw_instance_t *inst) { + avs_time_duration_t result = AVS_TIME_DURATION_INVALID; + if (inst->user_state.handlers->get_tcp_request_timeout) { + ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay); + result = inst->user_state.handlers->get_tcp_request_timeout( + inst->iid, inst->user_state.arg, inst->package_uri); + ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked); + } + return result; +} # endif // ANJAY_WITH_DOWNLOADER static void @@ -781,7 +798,8 @@ static int schedule_download_now(anjay_unlocked_t *anjay, } } avs_error_t err = - _anjay_download_unlocked(anjay, cfg, &fw->download_handle); + _anjay_download_unlocked(anjay, cfg, + &fw->current_download.download_handle); if (avs_is_err(err)) { anjay_advanced_fw_update_result_t update_result = ANJAY_ADVANCED_FW_UPDATE_RESULT_CONNECTION_LOST; @@ -807,8 +825,10 @@ static int schedule_download_now(anjay_unlocked_t *anjay, # endif // ANJAY_WITH_SEND return -1; } + fw->current_download.iid = inst->iid; if (fw->downloads_suspended) { - _anjay_download_suspend_unlocked(anjay, fw->download_handle); + _anjay_download_suspend_unlocked(anjay, + fw->current_download.download_handle); } inst->retry_download_on_expired = (false); update_state_and_update_result(anjay, fw, inst, @@ -822,12 +842,12 @@ static int schedule_download_now(anjay_unlocked_t *anjay, static void start_next_download_if_waiting(anjay_unlocked_t *anjay, advanced_fw_repr_t *fw) { if (fw->download_queue != NULL) { - if (schedule_download_now( - anjay, fw, - (advanced_fw_instance_t *) fw->download_queue->user_data, - fw->download_queue)) { + advanced_fw_instance_t *inst = + (advanced_fw_instance_t *) fw->download_queue->user_data; + if (schedule_download_now(anjay, fw, inst, fw->download_queue)) { fw_log(WARNING, _("Scheduling next waiting download failed")); } + fw_log(TRACE, _("Scheduled download for instance %") PRIu16, inst->iid); avs_free((void *) (intptr_t) fw->download_queue->url); avs_free((void *) fw->download_queue->coap_tx_params); AVS_LIST_DELETE(&fw->download_queue); @@ -845,7 +865,8 @@ static void download_finished(anjay_t *anjay_locked, } else { advanced_fw_repr_t *fw = get_fw(*obj); advanced_fw_instance_t *inst = (advanced_fw_instance_t *) inst_; - fw->download_handle = NULL; + fw->current_download.download_handle = NULL; + fw->current_download.iid = ANJAY_ID_INVALID; if (inst->state != ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADING) { // something already failed in download_write_block() reset_user_state(anjay, inst); @@ -919,7 +940,7 @@ static void download_finished(anjay_t *anjay_locked, } static bool is_any_download_in_progress(advanced_fw_repr_t *fw) { - return fw->download_handle || fw->download_queue; + return fw->current_download.download_handle || fw->download_queue; } static int enqueue_download(anjay_unlocked_t *anjay, @@ -986,6 +1007,7 @@ static int schedule_download(anjay_unlocked_t *anjay, if (!get_coap_tx_params(anjay, inst, &tx_params)) { cfg.coap_tx_params = &tx_params; } + cfg.tcp_request_timeout = get_tcp_request_timeout(anjay, inst); if (is_any_download_in_progress(fw)) { return enqueue_download(anjay, fw, inst, &cfg); } @@ -1102,21 +1124,42 @@ static int write_firmware(anjay_unlocked_t *anjay, } # ifdef ANJAY_WITH_DOWNLOADER +static void download_queue_entry_cleanup(anjay_download_config_t *cfg) { + avs_free((void *) (intptr_t) cfg->url); + avs_free((void *) cfg->coap_tx_params); +} + static void cancel_existing_download_if_in_progress(anjay_unlocked_t *anjay, advanced_fw_repr_t *fw, advanced_fw_instance_t *inst) { if (inst->state == ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADING) { - if (inst->resume_download_job) { - assert(!fw->download_handle); - avs_sched_del(&inst->resume_download_job); + if (fw->current_download.download_handle + && fw->current_download.iid == inst->iid) { + _anjay_download_abort_unlocked( + anjay, fw->current_download.download_handle); + assert(!fw->current_download.download_handle); + fw->current_download.iid = ANJAY_ID_INVALID; + fw_log(TRACE, + _("Aborted ongoing download for instance ") "%" PRIu16, + inst->iid); + start_next_download_if_waiting(anjay, fw); return; } - AVS_ASSERT(fw->download_handle, - "download_handle is NULL - another Write handler called " - "during a PUSH-mode download?!"); - _anjay_download_abort_unlocked(anjay, fw->download_handle); - assert(!fw->download_handle); + AVS_LIST(anjay_download_config_t) *queued_cfg; + AVS_LIST_FOREACH_PTR(queued_cfg, &fw->download_queue) { + advanced_fw_instance_t *queued_inst = + (advanced_fw_instance_t *) (*queued_cfg)->user_data; + if (queued_inst->iid == inst->iid) { + download_queue_entry_cleanup(*queued_cfg); + AVS_LIST_DELETE(queued_cfg); + fw_log(TRACE, + _("Removed instance ") "%" PRIu16 _( + " from download queue"), + inst->iid); + return; + } + } } } # endif // ANJAY_WITH_DOWNLOADER @@ -1601,6 +1644,7 @@ static int fw_execute(anjay_unlocked_t *anjay, # ifdef ANJAY_WITH_DOWNLOADER cancel_existing_download_if_in_progress(anjay, fw, inst); # endif // ANJAY_WITH_DOWNLOADER + reset_user_state(anjay, inst); update_state_and_update_result( anjay, fw, inst, ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE, @@ -1734,8 +1778,7 @@ static void fw_delete(void *fw_) { } # ifdef ANJAY_WITH_DOWNLOADER AVS_LIST_CLEAR(&fw->download_queue) { - avs_free((void *) (intptr_t) fw->download_queue->url); - avs_free((void *) fw->download_queue->coap_tx_params); + download_queue_entry_cleanup(fw->download_queue); } # endif // ANJAY_WITH_DOWNLOADER // NOTE: fw itself will be freed when cleaning the objects list @@ -1753,6 +1796,7 @@ int anjay_advanced_fw_update_install( fw_log(ERROR, _("out of memory")); } else { repr->def = &FIRMWARE_UPDATE; + repr->current_download.iid = ANJAY_ID_INVALID; if (config) { # ifdef ANJAY_WITH_DOWNLOADER repr->prefer_same_socket_downloads = @@ -2277,8 +2321,9 @@ void anjay_advanced_fw_update_pull_suspend(anjay_t *anjay_locked) { } else { advanced_fw_repr_t *fw = get_fw(*obj); assert(fw); - if (fw->download_handle) { - _anjay_download_suspend_unlocked(anjay, fw->download_handle); + if (fw->current_download.download_handle) { + _anjay_download_suspend_unlocked( + anjay, fw->current_download.download_handle); } fw->downloads_suspended = true; } @@ -2297,9 +2342,9 @@ int anjay_advanced_fw_update_pull_reconnect(anjay_t *anjay_locked) { advanced_fw_repr_t *fw = get_fw(*obj); assert(fw); fw->downloads_suspended = false; - if (fw->download_handle) { - result = _anjay_download_reconnect_unlocked(anjay, - fw->download_handle); + if (fw->current_download.download_handle) { + result = _anjay_download_reconnect_unlocked( + anjay, fw->current_download.download_handle); } else { result = 0; } diff --git a/src/modules/fw_update/anjay_fw_update.c b/src/modules/fw_update/anjay_fw_update.c index e24de78c..e34c0e3f 100644 --- a/src/modules/fw_update/anjay_fw_update.c +++ b/src/modules/fw_update/anjay_fw_update.c @@ -368,6 +368,18 @@ static int get_coap_tx_params(anjay_unlocked_t *anjay, } return -1; } + +static avs_time_duration_t get_tcp_request_timeout(anjay_unlocked_t *anjay, + fw_repr_t *fw) { + avs_time_duration_t result = AVS_TIME_DURATION_INVALID; + if (fw->user_state.handlers->get_tcp_request_timeout) { + ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay); + result = fw->user_state.handlers->get_tcp_request_timeout( + fw->user_state.arg, fw->package_uri); + ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked); + } + return result; +} # endif // ANJAY_WITH_DOWNLOADER static void handle_err_result(anjay_unlocked_t *anjay, @@ -680,6 +692,7 @@ static int schedule_download(anjay_unlocked_t *anjay, if (!get_coap_tx_params(anjay, fw, &tx_params)) { cfg.coap_tx_params = &tx_params; } + cfg.tcp_request_timeout = get_tcp_request_timeout(anjay, fw); assert(!fw->download_handle); avs_error_t err = diff --git a/tests/core/anjay.c b/tests/core/anjay.c index 5d4a0a99..9a1afaa8 100644 --- a/tests/core/anjay.c +++ b/tests/core/anjay.c @@ -747,15 +747,6 @@ AVS_UNIT_TEST(queue_mode, change) { _anjay_mock_dm_expect_resource_read( anjay, &FAKE_SECURITY2, 1, ANJAY_DM_RID_SECURITY_SERVER_URI, ANJAY_ID_INVALID, 0, ANJAY_MOCK_DM_STRING(0, "coap://127.0.0.1")); -#ifdef ANJAY_WITH_LWM2M11 - // get SNI - _anjay_mock_dm_expect_list_resources( - anjay, &FAKE_SECURITY2, 1, 0, - (const anjay_mock_dm_res_entry_t[]) { { ANJAY_DM_RID_SECURITY_SNI, - ANJAY_DM_RES_R, - ANJAY_DM_RES_ABSENT }, - ANJAY_MOCK_DM_RES_END }); -#endif // ANJAY_WITH_LWM2M11 // data model for the Update message - just fake an empty one _anjay_mock_dm_expect_list_instances( @@ -935,11 +926,6 @@ expect_refresh_server__(anjay_t *anjay, _anjay_mock_dm_expect_resource_read( anjay, &FAKE_SECURITY2, 1, ANJAY_DM_RID_SECURITY_SERVER_URI, ANJAY_ID_INVALID, 0, ANJAY_MOCK_DM_STRING(0, "coap://127.0.0.1")); -#ifdef ANJAY_WITH_LWM2M11 - // Attempt to read SNI - _anjay_mock_dm_expect_list_resources(anjay, &FAKE_SECURITY2, 1, 0, - FAKE_SECURITY_RESOURCES); -#endif // ANJAY_WITH_LWM2M11 if (args->with_reconnect == RECONNECT_NONE) { return; } diff --git a/tests/integration/framework/asserts.py b/tests/integration/framework/asserts.py index ae56cd95..09d2380b 100644 --- a/tests/integration/framework/asserts.py +++ b/tests/integration/framework/asserts.py @@ -214,7 +214,9 @@ def assertDtlsReconnect(self, server=None, timeout_s=-1, deadline=None, expected with self.assertRaises(RuntimeError) as raised: serv.recv(timeout_s=timeout_s, deadline=deadline) # -0x6780 == MBEDTLS_ERR_SSL_CLIENT_RECONNECT - self.assertIn(expected_error, raised.exception.args[0]) + if isinstance(expected_error, str): + expected_error = [expected_error] + self.assertTrue(any(err in raised.exception.args[0] for err in expected_error)) def assertPktIsDtlsClientHello(self, pkt, seq_number=ANY): if seq_number is not ANY and seq_number >= 2 ** 48: diff --git a/tests/integration/run_tests.sh.in b/tests/integration/run_tests.sh.in index 79e25ffb..614c13f3 100755 --- a/tests/integration/run_tests.sh.in +++ b/tests/integration/run_tests.sh.in @@ -12,7 +12,7 @@ COMMAND="@CMAKE_CTEST_COMMAND@"; RERUNS=@TEST_RERUNS@; CREATE_XLSX_REPORTS="python3 @CMAKE_SOURCE_DIR@/tests/integration/framework/create_xlsx_test_report.py \ - -d @CMAKE_SOURCE_DIR@/output/test/integration/log/test/" + -d @CMAKE_BINARY_DIR@/output/test/integration/log/test/" if [ "$1" == "-h" ]; then COMMAND="@CMAKE_CTEST_COMMAND@ -R hsm"; diff --git a/tests/integration/suites/default/advanced_firmware_update.py b/tests/integration/suites/default/advanced_firmware_update.py index c6e193b9..fbb34238 100644 --- a/tests/integration/suites/default/advanced_firmware_update.py +++ b/tests/integration/suites/default/advanced_firmware_update.py @@ -1273,7 +1273,8 @@ def runTest(self): with self.file_server as file_server: file_server._server.reset() self.communicate('afu-reconnect') - self.assertDtlsReconnect(file_server._server, timeout_s=10, expected_error='0x7900') + self.assertDtlsReconnect(file_server._server, timeout_s=10, + expected_error=['0x7700', '0x7900']) deadline = time.time() + 20 while time.time() < deadline: @@ -2940,7 +2941,7 @@ def runTest(self): self.FW_PKG_OPTS = {'magic': b'AJAY_APP', 'version': 2} self.provide_response_app_img() - # Write /33629/0/0 (Firmware) + # Write /33629/0/1 (Package URI) self.write_firmware_and_wait_for_download(Instances.APP, self.get_firmware_uri()) @@ -2952,7 +2953,7 @@ def runTest(self): self.FW_PKG_OPTS = {'magic': b'AJAY_TEE', 'version': 2} self.provide_response_additional_img(content=DUMMY_FILE) - # Write /33629/1/0 (Firmware) + # Write /33629/1/1 (Package URI) self.write_firmware_and_wait_for_download(Instances.TEE, self.get_firmware_uri()) @@ -3079,7 +3080,7 @@ def runTest(self): self.FW_PKG_OPTS = {'magic': b'AJAY_APP', 'version': 2} self.provide_response_app_img() - # Write /33629/0/0 (Firmware) + # Write /33629/0/1 (Package URI) self.write_firmware_and_wait_for_download(Instances.APP, self.get_firmware_uri()) @@ -3091,7 +3092,7 @@ def runTest(self): self.FW_PKG_OPTS = {'magic': b'AJAY_TEE', 'version': 2} self.provide_response_additional_img(content=DUMMY_FILE) - # Write /33629/0/0 (Firmware) + # Write /33629/0/1 (Package URI) self.write_firmware_and_wait_for_download(Instances.TEE, self.get_firmware_uri()) @@ -3103,7 +3104,7 @@ def runTest(self): self.FW_PKG_OPTS = {'magic': b'AJAYBOOT', 'version': 2} self.provide_response_additional_img(content=DUMMY_FILE) - # Write /33629/0/0 (Firmware) + # Write /33629/0/1 (Package URI) self.write_firmware_and_wait_for_download(Instances.BOOT, self.get_firmware_uri()) @@ -3115,7 +3116,7 @@ def runTest(self): self.FW_PKG_OPTS = {'magic': b'AJAYMODE', 'version': 2} self.provide_response_additional_img(content=DUMMY_FILE) - # Write /33629/0/0 (Firmware) + # Write /33629/0/1 (Package URI) self.write_firmware_and_wait_for_download(Instances.MODEM, self.get_firmware_uri()) @@ -3150,7 +3151,7 @@ def runTest(self): self.FW_PKG_OPTS = {'magic': b'AJAY_APP', 'version': 2} self.provide_response_app_img(use_real_app=True) - # Write /33629/0/0 (Firmware) + # Write /33629/0/1 (Package URI) self.write_firmware_and_wait_for_download(Instances.APP, self.get_firmware_uri()) @@ -3162,7 +3163,7 @@ def runTest(self): self.FW_PKG_OPTS = {'magic': b'AJAY_TEE', 'version': 2} self.provide_response_additional_img(content=DUMMY_FILE) - # Write /33629/0/0 (Firmware) + # Write /33629/0/1 (Package URI) self.write_firmware_and_wait_for_download(Instances.TEE, self.get_firmware_uri()) @@ -3174,7 +3175,7 @@ def runTest(self): self.FW_PKG_OPTS = {'magic': b'AJAYBOOT', 'version': 2} self.provide_response_additional_img(content=DUMMY_FILE) - # Write /33629/0/0 (Firmware) + # Write /33629/0/1 (Package URI) self.write_firmware_and_wait_for_download(Instances.BOOT, self.get_firmware_uri()) @@ -3186,7 +3187,7 @@ def runTest(self): self.FW_PKG_OPTS = {'magic': b'AJAYMODE', 'version': 2} self.provide_response_additional_img(content=DUMMY_FILE) - # Write /33629/0/0 (Firmware) + # Write /33629/0/1 (Package URI) self.write_firmware_and_wait_for_download(Instances.MODEM, self.get_firmware_uri()) @@ -3224,7 +3225,7 @@ def runTest(self): # Check /33629/0/16 (LinkedInstances), there should not be any linked instances self.read_linked_and_check(Instances.APP, []) - # Write /33629/0/0 (Firmware) + # Write /33629/0/1 (Package URI) self.write_firmware_and_wait_for_download(Instances.APP, self.get_firmware_uri()) @@ -3253,7 +3254,7 @@ def runTest(self): # Check /33629/0/16 (LinkedInstances), there should not be any linked instances self.read_linked_and_check(Instances.APP, []) - # Write /33629/0/0 (Firmware) + # Write /33629/0/1 (Package URI) self.write_firmware_and_wait_for_download(Instances.APP, self.get_firmware_uri()) @@ -3298,7 +3299,7 @@ def runTest(self): # Check /33629/0/17, there should not be any conflicting instances self.read_conflicting_and_check(Instances.APP, []) - # Write /33629/0/0 (Firmware) + # Write /33629/0/1 (Package URI) self.write_firmware_and_wait_for_download(Instances.APP, self.get_firmware_uri()) @@ -3325,7 +3326,7 @@ def runTest(self): # Check /33629/0/17, there should not be any conflicting instances self.read_conflicting_and_check(Instances.APP, []) - # Write /33629/0/0 (Firmware) + # Write /33629/0/1 (Package URI) self.write_firmware_and_wait_for_download(Instances.APP, self.get_firmware_uri()) @@ -3338,7 +3339,7 @@ def runTest(self): self.FW_PKG_OPTS = {'magic': b'AJAY_TEE', 'version': 2} self.provide_response_additional_img(content=DUMMY_FILE) - # Write /33629/1/0 (Firmware) + # Write /33629/1/1 (Package URI) self.write_firmware_and_wait_for_download(Instances.TEE, self.get_firmware_uri()) @@ -3368,7 +3369,7 @@ def runTest(self): # Check /33629/1/17, there should not be any conflicting instances self.read_conflicting_and_check(Instances.TEE, []) - # Write /33629/1/0 (Firmware) + # Write /33629/1/1 (Package URI) self.write_firmware_and_wait_for_download(Instances.TEE, self.get_firmware_uri()) @@ -3382,7 +3383,7 @@ def runTest(self): 'linked': [Instances.TEE]} self.provide_response_additional_img(content=DUMMY_FILE) - # Write /33629/2/0 (Firmware) + # Write /33629/2/1 (Package URI) self.write_firmware_and_wait_for_download(Instances.BOOT, self.get_firmware_uri()) @@ -3422,7 +3423,7 @@ def runTest(self): self.FW_PKG_OPTS = {'magic': b'AJAY_TEE', 'version': 2} self.provide_response_additional_img(content=DUMMY_FILE) - # Write /33629/0/0 (Firmware) + # Write /33629/0/1 (Package URI) self.write_firmware_and_wait_for_download(Instances.TEE, self.get_firmware_uri()) @@ -3439,7 +3440,7 @@ def runTest(self): # Check /33629/0/17, there should not be any conflicting instances self.read_conflicting_and_check(Instances.APP, []) - # Write /33629/0/0 (Firmware) + # Write /33629/0/1 (Package URI) self.write_firmware_and_wait_for_download(Instances.APP, self.get_firmware_uri()) @@ -3468,7 +3469,7 @@ def runTest(self): # Check /33629/1/17, there should not be any conflicting instances self.read_conflicting_and_check(Instances.TEE, []) - # Write /33629/1/0 (Firmware) + # Write /33629/1/1 (Package URI) self.write_firmware_and_wait_for_download(Instances.TEE, self.get_firmware_uri()) @@ -3482,7 +3483,7 @@ def runTest(self): 'linked': [Instances.TEE]} self.provide_response_additional_img(content=DUMMY_FILE, overwrite_original_img=False) - # Write /33629/2/0 (Firmware) + # Write /33629/2/1 (Package URI) self.write_firmware_and_wait_for_download(Instances.BOOT, self.get_firmware_uri()) @@ -3533,7 +3534,7 @@ def runTest(self): # Check /33629/2/17, there should not be any conflicting instances self.read_conflicting_and_check(Instances.BOOT, []) - # Write /33629/2/0 (Firmware) + # Write /33629/2/1 (Package URI) self.write_firmware_and_wait_for_download(Instances.BOOT, self.get_firmware_uri()) @@ -3547,7 +3548,7 @@ def runTest(self): self.FW_PKG_OPTS = {'magic': b'AJAY_TEE', 'version': 2} self.provide_response_additional_img(content=DUMMY_FILE) - # Write /33629/0/0 (Firmware) + # Write /33629/0/1 (Package URI) self.write_firmware_and_wait_for_download(Instances.TEE, self.get_firmware_uri()) @@ -3604,7 +3605,7 @@ def runTest(self): # Check /33629/0/17, there should not be any conflicting instances self.read_conflicting_and_check(Instances.APP, []) - # Write /33629/0/0 (Firmware) + # Write /33629/0/1 (Package URI) self.write_firmware_and_wait_for_download(Instances.APP, self.get_firmware_uri()) @@ -3617,7 +3618,7 @@ def runTest(self): self.FW_PKG_OPTS = {'magic': b'AJAY_TEE', 'version': 2} self.provide_response_additional_img(content=DUMMY_FILE) - # Write /33629/0/0 (Firmware) + # Write /33629/0/1 (Package URI) self.write_firmware_and_wait_for_download(Instances.TEE, self.get_firmware_uri()) @@ -3861,7 +3862,7 @@ def runTest(self): self.FW_PKG_OPTS = {'magic': b'AJAY_APP', 'version': 2, 'linked': [1]} self.provide_response_app_img(use_real_app=True) - # Write /33629/0/0 (Firmware) + # Write /33629/0/1 (Package URI) self.write_firmware_and_wait_for_download(Instances.APP, self.get_firmware_uri()) @@ -3874,7 +3875,7 @@ def runTest(self): self.FW_PKG_OPTS = {'magic': b'AJAY_TEE', 'version': 2, 'linked': [0]} self.provide_response_additional_img(content=DUMMY_FILE) - # Write /33629/1/0 (Firmware) + # Write /33629/1/1 (Package URI) self.write_firmware_and_wait_for_download(Instances.TEE, self.get_firmware_uri()) @@ -3969,7 +3970,7 @@ def runTest(self): self.FW_PKG_OPTS = {'magic': b'AJAY_APP', 'version': 2, 'linked': [1]} self.provide_response_app_img(use_real_app=True) - # Write /33629/0/0 (Firmware) + # Write /33629/0/1 (Package URI) self.write_firmware_and_wait_for_download(Instances.APP, self.get_firmware_uri()) @@ -3982,7 +3983,7 @@ def runTest(self): self.FW_PKG_OPTS = {'magic': b'AJAY_TEE', 'version': 2, 'linked': [0]} self.provide_response_additional_img(content=DUMMY_FILE) - # Write /33629/1/0 (Firmware) + # Write /33629/1/1 (Package URI) self.write_firmware_and_wait_for_download(Instances.TEE, self.get_firmware_uri()) @@ -4066,7 +4067,7 @@ def runTest(self): 'pkg_version': b'2.0.1'} self.provide_response_app_img() - # Write /33629/0/0 (Firmware) + # Write /33629/0/1 (Package URI) self.write_firmware_and_wait_for_download(Instances.APP, self.get_firmware_uri()) @@ -4098,7 +4099,7 @@ def runTest(self): 'pkg_version': b'2.0.1'} self.provide_response_app_img() - # Write /33629/0/0 (Firmware) + # Write /33629/0/1 (Package URI) self.write_firmware_and_wait_for_download(Instances.APP, self.get_firmware_uri()) @@ -4127,7 +4128,7 @@ def runTest(self): 'pkg_version': b'2.0.1'} self.provide_response_additional_img(content=DUMMY_FILE) - # Write /33629/1/0 (Firmware) + # Write /33629/1/1 (Package URI) self.write_firmware_and_wait_for_download(Instances.TEE, self.get_firmware_uri()) @@ -4349,7 +4350,7 @@ def runTest(self): self.FW_PKG_OPTS = {'magic': b'AJAY_APP', 'version': 2} self.provide_response_app_img(use_real_app=True) - # Write /33629/0/0 (Firmware) + # Write /33629/0/1 (Package URI) self.write_firmware_and_wait_for_download(Instances.APP, self.get_firmware_uri()) @@ -4395,3 +4396,181 @@ def runTest(self): self.read_update_result(Instances.APP)) self.assertEqual(UpdateResult.SUCCESS, self.read_update_result(Instances.TEE)) + + +class AdvancedFirmwareUpdateCancelWhileDownloadQueued(AdvancedFirmwareUpdate.TestWithCoapServer): + def setUp(self, coap_server=None, *args, **kwargs): + super().setUp(coap_server=[None, None], *args, **kwargs) + + def runTest(self): + # Prepare package for /33629/0 + self.FW_PKG_OPTS = {'magic': b'AJAY_APP', 'version': 2} + self.prepare_package_app_img(use_real_app=True) + with self.get_file_server(serv=0) as file_server: + file_server.set_resource('/firmwareAPP', + self.PACKAGE) + fw_uri1 = file_server.get_resource_uri('/firmwareAPP') + + # Prepare package for /33629/1 + self.FW_PKG_OPTS = {'magic': b'AJAY_TEE', 'version': 2} + self.prepare_package_additional_img(content=DUMMY_FILE) + with self.get_file_server(serv=1) as file_server: + file_server.set_resource('/firmwareTEE', + self.PACKAGE) + fw_uri2 = file_server.get_resource_uri('/firmwareTEE') + + # Write /33629/0/1 (Firmware URI) + req1 = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI, + fw_uri1) + self.serv.send(req1) + + # Write /33629/1/1 (Firmware URI) + req2 = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.TEE].PackageURI, + fw_uri2) + self.serv.send(req2) + + # There should be two request already received + self.assertMsgEqual(Lwm2mChanged.matching(req1)(), + self.serv.recv()) + self.assertMsgEqual(Lwm2mChanged.matching(req2)(), + self.serv.recv()) + + # Wait for download to start + self.wait_until_state_is(Instances.APP, UpdateState.DOWNLOADING) + # TEE URI is going to be queued but its state should be DOWNLOADING as well + self.wait_until_state_is(Instances.TEE, UpdateState.DOWNLOADING) + + # Execute /33629/0/10 (Cancel) + req1 = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Cancel) + self.serv.send(req1) + + # Execute /33629/1/10 (Cancel) + req2 = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.TEE].Cancel) + self.serv.send(req2) + + # Check APP instance abort + if self.read_log_until_match(regex=re.escape(b'Aborted ongoing download for instance 0'), + timeout_s=5) is None: + raise self.failureException( + 'string not found') + + # Download for instance TEE should start then + if self.read_log_until_match(regex=re.escape(b'Scheduled download for instance 1'), + timeout_s=5) is None: + raise self.failureException( + 'string not found') + + # Check TEE instance abort + if self.read_log_until_match(regex=re.escape(b'Aborted ongoing download for instance 1'), + timeout_s=5) is None: + raise self.failureException( + 'string not found') + + # There should be two request already received + self.assertMsgEqual(Lwm2mChanged.matching(req1)(), + self.serv.recv()) + self.assertMsgEqual(Lwm2mChanged.matching(req2)(), + self.serv.recv()) + + + # Check states and results + self.assertEqual(UpdateState.IDLE, self.read_state(Instances.APP)) + self.assertEqual(UpdateResult.CANCELLED, + self.read_update_result(Instances.APP)) + self.assertEqual(UpdateState.IDLE, self.read_state(Instances.TEE)) + self.assertEqual(UpdateResult.CANCELLED, + self.read_update_result(Instances.TEE)) + + +class AdvancedFirmwareUpdateCancelCurrentDownloadAndLeaveSecondOne(AdvancedFirmwareUpdate.TestWithCoapServer): + def setUp(self, coap_server=None, *args, **kwargs): + super().setUp(coap_server=[None, None], *args, **kwargs) + + def runTest(self): + # Prepare package for /33629/0 + self.FW_PKG_OPTS = {'magic': b'AJAY_APP', 'version': 2} + self.prepare_package_app_img(use_real_app=True) + with self.get_file_server(serv=0) as file_server: + file_server.set_resource('/firmwareAPP', + self.PACKAGE) + fw_uri1 = file_server.get_resource_uri('/firmwareAPP') + + # Prepare package for /33629/1 + self.FW_PKG_OPTS = {'magic': b'AJAY_TEE', 'version': 2} + self.prepare_package_additional_img(content=DUMMY_FILE) + with self.get_file_server(serv=1) as file_server: + file_server.set_resource('/firmwareTEE', + self.PACKAGE) + fw_uri2 = file_server.get_resource_uri('/firmwareTEE') + + # Write /33629/0/1 (Firmware URI) + req1 = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI, + fw_uri1) + self.serv.send(req1) + + # Write /33629/1/1 (Firmware URI) + req2 = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.TEE].PackageURI, + fw_uri2) + self.serv.send(req2) + + # There should be two request already received + self.assertMsgEqual(Lwm2mChanged.matching(req1)(), + self.serv.recv()) + self.assertMsgEqual(Lwm2mChanged.matching(req2)(), + self.serv.recv()) + + # Wait for download to start + self.wait_until_state_is(Instances.APP, UpdateState.DOWNLOADING) + # TEE URI is going to be queued but its state should be DOWNLOADING as well + self.wait_until_state_is(Instances.TEE, UpdateState.DOWNLOADING) + + # Execute /33629/0/10 (Cancel) + req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Cancel) + self.serv.send(req) + self.assertMsgEqual(Lwm2mChanged.matching(req)(), + self.serv.recv()) + + # Check states and results + self.assertEqual(UpdateState.IDLE, self.read_state(Instances.APP)) + self.assertEqual(UpdateResult.CANCELLED, + self.read_update_result(Instances.APP)) + self.wait_until_state_is(Instances.TEE, UpdateState.DOWNLOADED) + self.assertEqual(UpdateResult.INITIAL, + self.read_update_result(Instances.TEE)) + + +class AdvancedFirmwareUpdateHttpRequestTimeoutTest(AdvancedFirmwareUpdate.TestWithPartialDownload, + AdvancedFirmwareUpdate.TestWithHttpServer): + CHUNK_SIZE = 500 + RESPONSE_DELAY = 0.5 + TCP_REQUEST_TIMEOUT = 5 + + def setUp(self): + super().setUp( + extra_cmdline_args=['--afu-tcp-request-timeout', str(self.TCP_REQUEST_TIMEOUT)]) + + def runTest(self): + self.provide_response() + + # Write /33629/0/1 (Firmware URI) + req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI, + self.get_firmware_uri()) + self.serv.send(req) + self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv()) + + self.wait_for_half_download() + # Change RESPONSE_DELAY so that the server stops responding + self.RESPONSE_DELAY = self.TCP_REQUEST_TIMEOUT + 5 + + half_download_time = time.time() + self.wait_until_state_is(Instances.APP, UpdateState.IDLE, + timeout_s=self.TCP_REQUEST_TIMEOUT + 5) + fail_time = time.time() + self.assertEqual(self.read_update_result(Instances.APP), UpdateResult.CONNECTION_LOST) + + self.assertAlmostEqual(fail_time, half_download_time + self.TCP_REQUEST_TIMEOUT, delta=1.5) + + +class AdvancedFirmwareUpdateHttpRequestTimeoutTest20sec( + AdvancedFirmwareUpdateHttpRequestTimeoutTest): + TCP_REQUEST_TIMEOUT = 20 diff --git a/tests/integration/suites/default/firmware_update.py b/tests/integration/suites/default/firmware_update.py index 1aa4159f..5f44a9f3 100644 --- a/tests/integration/suites/default/firmware_update.py +++ b/tests/integration/suites/default/firmware_update.py @@ -150,6 +150,15 @@ def write_firmware_and_wait_for_download(self, firmware_uri: str, self.fail('firmware still not downloaded') + def wait_until_state_is(self, state, timeout_s=10): + deadline = time.time() + timeout_s + while time.time() < deadline: + time.sleep(0.1) + if self.read_state() == state: + return + + self.fail(f'state still is not {state}') + class TestWithHttpServerMixin: RESPONSE_DELAY = 0 CHUNK_SIZE = sys.maxsize @@ -203,17 +212,20 @@ def chunks(data): for chunk in chunks(response_content): time.sleep(test_case.RESPONSE_DELAY) - try: - self.wfile.write(chunk) - except BrokenPipeError: - pass + self.wfile.write(chunk) self.wfile.flush() def log_request(self, code='-', size='-'): # don't display logs on successful request pass - return http.server.HTTPServer(('', 0), FirmwareRequestHandler) + class SilentServer(http.server.HTTPServer): + def handle_error(self, *args, **kwargs): + # don't log BrokenPipeErrors + if not isinstance(sys.exc_info()[1], BrokenPipeError): + super().handle_error(*args, **kwargs) + + return SilentServer(('', 0), FirmwareRequestHandler) def write_firmware_and_wait_for_download(self, *args, **kwargs): requests = list(self.requests) @@ -537,10 +549,7 @@ def do_GET(self): while offset < len(response_content): chunk = response_content[offset:offset + 1024] - try: - self.wfile.write(chunk) - except BrokenPipeError: - pass + self.wfile.write(chunk) offset += len(chunk) time.sleep(0.5) @@ -1366,7 +1375,8 @@ def runTest(self): with self.file_server as file_server: file_server._server.reset() self.communicate('fw-update-reconnect') - self.assertDtlsReconnect(file_server._server, timeout_s=10, expected_error='0x7900') + self.assertDtlsReconnect(file_server._server, timeout_s=10, + expected_error=['0x7700', '0x7900']) deadline = time.time() + 20 while time.time() < deadline: @@ -2861,3 +2871,37 @@ def runTest(self): self.servers[1]), UpdateResult.INITIAL) self.assertEqual(self.read_state(self.servers[1]), UpdateState.IDLE) self.assertDemoRegisters(self.servers[0]) + + +class FirmwareUpdateHttpRequestTimeoutTest(FirmwareUpdate.TestWithPartialDownload, + FirmwareUpdate.TestWithHttpServer): + CHUNK_SIZE = 500 + RESPONSE_DELAY = 0.5 + TCP_REQUEST_TIMEOUT = 5 + + def setUp(self): + super().setUp(extra_cmdline_args=['--fwu-tcp-request-timeout', + str(self.TCP_REQUEST_TIMEOUT)]) + + def runTest(self): + self.provide_response() + + # Write /5/0/1 (Firmware URI) + req = Lwm2mWrite(ResPath.FirmwareUpdate.PackageURI, self.get_firmware_uri()) + self.serv.send(req) + self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv()) + + self.wait_for_half_download() + # Change RESPONSE_DELAY so that the server stops responding + self.RESPONSE_DELAY = self.TCP_REQUEST_TIMEOUT + 5 + + half_download_time = time.time() + self.wait_until_state_is(UpdateState.IDLE, timeout_s=self.TCP_REQUEST_TIMEOUT + 5) + fail_time = time.time() + self.assertEqual(self.read_update_result(), UpdateResult.CONNECTION_LOST) + + self.assertAlmostEqual(fail_time, half_download_time + self.TCP_REQUEST_TIMEOUT, delta=1.5) + + +class FirmwareUpdateHttpRequestTimeoutTest20sec(FirmwareUpdateHttpRequestTimeoutTest): + TCP_REQUEST_TIMEOUT = 20 diff --git a/tests/integration/suites/testfest/dm/advanced_firmware_update.py b/tests/integration/suites/testfest/dm/advanced_firmware_update.py index fbe531b9..d73f7509 100644 --- a/tests/integration/suites/testfest/dm/advanced_firmware_update.py +++ b/tests/integration/suites/testfest/dm/advanced_firmware_update.py @@ -125,17 +125,20 @@ def do_GET(self): time.sleep(1) test_case.during_download(self) - try: - self.wfile.write(firmware_package) - except BrokenPipeError: - pass + self.wfile.write(firmware_package) def log_request(code='-', size='-'): # don't display logs on successful request pass + class SilentServer(self.HTTP_SERVER_CLASS): + def handle_error(self, *args, **kwargs): + # don't log BrokenPipeErrors + if not isinstance(sys.exc_info()[1], BrokenPipeError): + super().handle_error(*args, **kwargs) + self.requests = [] - self.http_server = self.HTTP_SERVER_CLASS(('', 0), FirmwareRequestHandler) + self.http_server = SilentServer(('', 0), FirmwareRequestHandler) self.server_thread = threading.Thread(target=lambda: self.http_server.serve_forever()) self.server_thread.start() diff --git a/tests/integration/suites/testfest/dm/firmware_update.py b/tests/integration/suites/testfest/dm/firmware_update.py index 8ff4f27a..70b5328d 100644 --- a/tests/integration/suites/testfest/dm/firmware_update.py +++ b/tests/integration/suites/testfest/dm/firmware_update.py @@ -105,17 +105,20 @@ def do_GET(self): time.sleep(1) test_case.during_download(self) - try: - self.wfile.write(firmware_package) - except BrokenPipeError: - pass + self.wfile.write(firmware_package) def log_request(code='-', size='-'): # don't display logs on successful request pass + class SilentServer(self.HTTP_SERVER_CLASS): + def handle_error(self, *args, **kwargs): + # don't log BrokenPipeErrors + if not isinstance(sys.exc_info()[1], BrokenPipeError): + super().handle_error(*args, **kwargs) + self.requests = [] - self.http_server = self.HTTP_SERVER_CLASS(('', 0), FirmwareRequestHandler) + self.http_server = SilentServer(('', 0), FirmwareRequestHandler) self.server_thread = threading.Thread(target=lambda: self.http_server.serve_forever()) self.server_thread.start() diff --git a/tools/ci-psa/Dockerfile b/tools/ci-psa/Dockerfile index 0d33c353..6ed4af6d 100644 --- a/tools/ci-psa/Dockerfile +++ b/tools/ci-psa/Dockerfile @@ -7,9 +7,13 @@ FROM ubuntu:22.04 - -RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -yq git build-essential cmake zlib1g-dev doxygen python3 libpython3-dev libssl-dev python3-pip python3-sphinx clang-tools valgrind opensc libengine-pkcs11-openssl docker.io nodejs curl jq automake -RUN pip3 install sphinx_rtd_theme cbor2 dpkt gitpython cryptography openpyxl +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive \ + apt-get install -yq git build-essential cmake zlib1g-dev doxygen python3 \ + libpython3-dev libssl-dev python3-pip clang-tools valgrind opensc \ + libengine-pkcs11-openssl docker.io nodejs curl jq automake +COPY requirements.txt requirements.txt +RUN pip3 install -r requirements.txt RUN $(which echo) -e " \n\ \n\ diff --git a/tools/ci-psa/README.md b/tools/ci-psa/README.md index c1243848..3c35c928 100644 --- a/tools/ci-psa/README.md +++ b/tools/ci-psa/README.md @@ -2,3 +2,9 @@ This Dockerfile is developed to build PSA examples and run PSA tests. It contains the basic libs needed to build Anjay and additionaly custom build of mbed TLS with PSA enabled. +To build the image, you can run the following command from the root of the Anjay +repository: +```bash +docker build --no-cache -f tools/ci-psa/Dockerfile . +``` + diff --git a/tools/ci/build-docker-images.sh b/tools/ci/build-docker-images.sh index e617a31b..4cc405cb 100755 --- a/tools/ci/build-docker-images.sh +++ b/tools/ci/build-docker-images.sh @@ -9,15 +9,16 @@ set -e SCRIPT_DIR="$(dirname "$(readlink -f "$BASH_SOURCE")")" +ANJAY_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")" -if [[ "$#" -gt 0 ]]; then +if [[ $# -ne 2 || $1 != "--version" ]]; then cat <&2 Builds Docker images for use in CI. Intended usage: # build docker images locally - $0 + $0 --version # push built images to docker.io docker login docker.io docker push avsystemembedded/anjay-travis:IMAGE_TO_PUSH @@ -27,13 +28,17 @@ EOF exit 1 fi +IMAGE_VERSION=$2 + build-docker-image() { local NAME="$1" local DOCKERFILE="$2" - docker build --no-cache -t "$NAME" -f "$DOCKERFILE" "$(dirname "$DOCKERFILE")" + pushd "$ANJAY_ROOT" + docker build --no-cache -t "$NAME" -f "$DOCKERFILE" . + popd } for IMAGE_DOCKERFILE in "$SCRIPT_DIR/"*/Dockerfile; do - IMAGE_NAME="avsystemembedded/anjay-travis:$(basename "$(dirname "$IMAGE_DOCKERFILE")")" + IMAGE_NAME="avsystemembedded/anjay-travis:$(basename "$(dirname "$IMAGE_DOCKERFILE")")-"$IMAGE_VERSION"" build-docker-image "$IMAGE_NAME" "$IMAGE_DOCKERFILE" done diff --git a/tools/ci/rockylinux-9/Dockerfile b/tools/ci/rockylinux-9/Dockerfile index 488313b7..7ec26d87 100644 --- a/tools/ci/rockylinux-9/Dockerfile +++ b/tools/ci/rockylinux-9/Dockerfile @@ -8,10 +8,10 @@ FROM rockylinux/rockylinux:9 RUN dnf update -y && \ dnf install -y --allowerasing python3-pip git openssl-devel zlib-devel \ - python3 python3-devel wget python3-cryptography openssl \ - python3-requests python3-packaging valgrind curl cmake \ + python3 python3-devel wget openssl valgrind curl cmake \ gcc gcc-c++ wireshark-cli which 'dnf-command(config-manager)' RUN dnf config-manager --set-enabled crb && \ dnf install -y epel-release && \ dnf install -y mbedtls-devel -RUN pip3 install sphinx sphinx-rtd-theme cbor2 aiocoap wheel linuxdoc openpyxl +COPY requirements.txt requirements.txt +RUN pip3 install -r requirements.txt diff --git a/tools/ci/ubuntu-18.04/Dockerfile b/tools/ci/ubuntu-18.04/Dockerfile index d23b9587..5ebe2698 100644 --- a/tools/ci/ubuntu-18.04/Dockerfile +++ b/tools/ci/ubuntu-18.04/Dockerfile @@ -9,10 +9,13 @@ FROM ubuntu:18.04 RUN apt-get update && \ env DEBIAN_FRONTEND=noninteractive \ apt-get install -y python3-pip git build-essential libmbedtls-dev \ - libssl-dev zlib1g-dev python3 libpython3-dev wget python3-cryptography \ - python3-requests python3-packaging valgrind curl cmake tshark -RUN pip3 install sphinx sphinx-rtd-theme linuxdoc openpyxl -# NOTE: Newer versions don't install cleanly on Python 3.6 -RUN pip3 install aiocoap==0.4b3 cbor2==4.1.2 + libssl-dev zlib1g-dev python3 libpython3-dev wget valgrind curl cmake \ + tshark +COPY requirements.txt requirements.txt +RUN pip3 install --upgrade pip +RUN env LANG=C.UTF-8 LC_ALL=C.UTF-8 pip3 install -r requirements.txt +# NOTE: The versions in requirements.txt install cleanly but don't work properly +# on Python 3.6 +RUN pip3 install cryptography==2.1.4 cbor2==4.1.2 aiocoap==0.4b3 # Solve issues with EPERM when running dumpcap RUN setcap '' $(which dumpcap) diff --git a/tools/ci/ubuntu-20.04/Dockerfile b/tools/ci/ubuntu-20.04/Dockerfile index 025c1377..f12045ff 100644 --- a/tools/ci/ubuntu-20.04/Dockerfile +++ b/tools/ci/ubuntu-20.04/Dockerfile @@ -9,8 +9,8 @@ FROM ubuntu:20.04 RUN apt-get update && \ env DEBIAN_FRONTEND=noninteractive \ apt-get install -y python3-pip git libmbedtls-dev libssl-dev zlib1g-dev \ - python3 libpython3-dev wget python3-cryptography python3-requests \ - python3-packaging valgrind curl cmake build-essential tshark -RUN pip3 install sphinx sphinx-rtd-theme cbor2 aiocoap linuxdoc openpyxl + python3 libpython3-dev wget valgrind curl cmake build-essential tshark +COPY requirements.txt requirements.txt +RUN pip3 install -r requirements.txt # Solve issues with EPERM when running dumpcap RUN setcap '' $(which dumpcap) diff --git a/tools/ci/ubuntu-22.04/Dockerfile b/tools/ci/ubuntu-22.04/Dockerfile index d8d3f50e..e141d492 100644 --- a/tools/ci/ubuntu-22.04/Dockerfile +++ b/tools/ci/ubuntu-22.04/Dockerfile @@ -9,8 +9,8 @@ FROM ubuntu:22.04 RUN apt-get update && \ env DEBIAN_FRONTEND=noninteractive \ apt-get install -y python3-pip git libmbedtls-dev libssl-dev zlib1g-dev \ - python3 libpython3-dev wget python3-cryptography python3-requests \ - python3-packaging valgrind curl cmake build-essential tshark -RUN pip3 install sphinx sphinx-rtd-theme cbor2 aiocoap linuxdoc openpyxl + python3 libpython3-dev wget valgrind curl cmake build-essential tshark +COPY requirements.txt requirements.txt +RUN pip3 install -r requirements.txt # Solve issues with EPERM when running dumpcap RUN setcap '' $(which dumpcap)