From 0acafc41f108527853416f541ea7a1363127d15c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Ku=C4=87ma?= Date: Thu, 7 Sep 2023 14:40:22 +0200 Subject: [PATCH] Anjay 3.5.0 BREAKING CHANGES - Reversed the order of calling the ``delivery_handler`` callback vs. canceling the observation when sending notifications with 4.xx or 5.xx code; this change is breaking only for direct users of ``avs_coap`` API Features - Added APIs for suspending and resuming standalone downloads as well as Firmware Update and Advanced Firmware Update PULL-mode downloads - Added standalone versions of the Security and Server object implementations, that can be customized by the end users - Added definitions for common Core Object IDs in the public API - Removed potentially faulty assertion in code generated by anjay_codegen.py - (commercial feature only) added ``anjay_sim_bootstrap_calculate_md5()`` function that allows verification whether SIM Bootstrap data has been changed (e.g. as a result of SIM OTA) Improvements - Rewritten Send-based reporting in Advanced Firmware Update in such a way that it will now work with custom implementations of the Server object - Simplified the CoAP downloader implementation so that the ``get_remote_hostname`` socket operation is no longer necessary for download resumption - Made handling of initial peer CSM messages in CoAP+TCP asynchronous - Updated the documentation with more descriptive warnings about functions that 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 Bugfixes - Fixed a critical bug that caused Anjay to crash when sending notifications with 4.xx or 5.xx code over TCP - Fixed a regression introduced in 2.13.0 that prevented the Firmware Update and Advanced Firmware Update from compiling without the ``ANJAY_WITH_DOWNLOADER`` configuration option enabled - Fixed a condition where the Register or Update messages could be erroneously regenerated when refreshing server connections while already performing a Register or Update request - Fixed a condition where the connection could be erroneously retried automatically when a fatal failure was expected - Decoupled the ``WITH_AVS_COAP_TCP`` and ``ANJAY_WITH_LWM2M11`` configuration options so that they can be set independently as intended - Fixed the ``devconfig`` script and Github Actions configuration for better compatibility with building on macOS - Refactored TCP binding handling in integration tests for more reliability - Fixed the case where CoAP+TCP Abort message could erroneously be sent multiple times - Loosened some time constraints in Advanced Firmware Update tests - Fixed supplemental iid sort in Advanced Firmware Update - Fixed too early restart while performing an upgrade using Advanced Firmware Update module in Anjay demo app - Fixed too early persistence write while performing an upgrade using Advanced Firmware Update module in Anjay demo app --- .github/workflows/anjay-tests.yml | 22 +- CHANGELOG.md | 59 + CMakeLists.txt | 16 +- demo/CMakeLists.txt | 16 + demo/advanced_firmware_update.c | 19 +- demo/advanced_firmware_update.h | 12 +- demo/advanced_firmware_update_app.c | 87 +- demo/demo.c | 229 ++-- demo/demo.h | 11 +- demo/demo_args.c | 168 ++- demo/demo_args.h | 6 +- demo/demo_cmds.c | 60 +- demo/demo_utils.c | 13 - demo/demo_utils.h | 7 +- demo/firmware_update.c | 39 +- demo/firmware_update.h | 9 +- demo/objects.h | 7 +- demo/objects/binary_app_data_container.c | 8 - demo/objects/event_log.c | 4 - demo/objects/portfolio.c | 9 - deps/avs_coap/CMakeLists.txt | 2 + .../src/async/avs_coap_async_server.c | 31 +- deps/avs_coap/src/tcp/avs_coap_tcp_ctx.c | 124 +- deps/avs_coap/src/tcp/avs_coap_tcp_ctx.h | 8 + .../src/tcp/avs_coap_tcp_pending_requests.c | 63 +- .../src/tcp/avs_coap_tcp_pending_requests.h | 4 +- .../avs_coap/src/tcp/avs_coap_tcp_signaling.c | 2 +- .../avs_coap/src/tcp/avs_coap_tcp_signaling.h | 3 +- deps/avs_coap/src/udp/avs_coap_udp_ctx.c | 161 +-- deps/avs_coap/src/udp/avs_coap_udp_ctx.h | 139 +++ .../avs_coap/src/udp/avs_coap_udp_tx_params.h | 52 +- deps/avs_coap/tests/tcp/csm.c | 246 ++++ deps/avs_coap/tests/tcp/env.h | 22 +- deps/avs_coap/tests/tcp/helper_functions.h | 1 + deps/avs_coap/tests/udp/async_observe.c | 2 +- deps/avs_coap/tests/udp/udp_tx_params.c | 35 +- deps/avs_commons | 2 +- devconfig | 5 +- doc/sphinx/snippet_sources.md5 | 18 +- .../BasicClient/BC-ObjectImplementation.rst | 9 +- .../source/BasicClient/BC-ThreadSafety.rst | 10 +- .../CF-SmartCardBootstrap.rst | 2 +- doc/sphinx/source/Migrating.rst | 1 + .../Migrating/MigratingFromAnjay214.rst | 21 + .../Migrating/MigratingFromAnjay215.rst | 21 + .../Migrating/MigratingFromAnjay225.rst | 100 +- .../source/Migrating/MigratingFromAnjay24.rst | 21 + .../source/Migrating/MigratingFromAnjay26.rst | 21 + .../source/Migrating/MigratingFromAnjay27.rst | 21 + .../source/Migrating/MigratingFromAnjay28.rst | 21 + .../source/Migrating/MigratingFromAnjay30.rst | 21 + .../source/Migrating/MigratingFromAnjay32.rst | 18 + .../source/Migrating/MigratingFromAnjay33.rst | 18 + .../source/Migrating/MigratingFromAnjay34.rst | 40 + .../NetworkingAPI.rst | 4 +- doc/sphinx/source/Tools/StandaloneObjects.rst | 87 ++ doc/sphinx/source/Tools/StubGenerator.rst | 16 +- .../embedded_lwm2m10/anjay/anjay_config.h | 5 +- .../avsystem/commons/lwip-posix-compat.h | 3 + .../embedded_lwm2m11/anjay/anjay_config.h | 5 +- .../avsystem/commons/lwip-posix-compat.h | 3 + .../linux_lwm2m10/anjay/anjay_config.h | 5 +- .../linux_lwm2m11/anjay/anjay_config.h | 5 +- .../BC-Notifications/src/time_object.c | 5 - .../BC-ObjectImplementation/src/time_object.c | 5 - examples/tutorial/BC-Send/src/time_object.c | 5 - .../BC-ThreadSafety/src/time_object.c | 6 - include_public/anjay/advanced_fw_update.h | 54 + include_public/anjay/anjay_config.h.in | 5 +- include_public/anjay/core.h | 46 +- include_public/anjay/dm.h | 6 + include_public/anjay/download.h | 48 + include_public/anjay/fw_update.h | 41 + include_public/anjay/server.h | 4 +- src/anjay_modules/anjay_dm_utils.h | 11 +- src/anjay_modules/anjay_utils_core.h | 6 + src/core/anjay_core.c | 57 +- src/core/anjay_core.h | 8 +- src/core/anjay_downloader.h | 10 +- src/core/anjay_lwm2m_send.c | 13 +- src/core/anjay_servers_private.h | 11 +- src/core/downloader/anjay_coap.c | 49 +- src/core/downloader/anjay_downloader.c | 203 +-- src/core/downloader/anjay_http.c | 12 +- src/core/downloader/anjay_private.h | 5 +- src/core/observe/anjay_observe_core.c | 49 +- src/core/servers/anjay_activate.c | 7 - src/core/servers/anjay_connection_ip.c | 16 +- src/core/servers/anjay_connections.c | 10 +- src/core/servers/anjay_connections.h | 4 +- src/core/servers/anjay_connections_internal.h | 4 +- src/core/servers/anjay_register.c | 101 +- src/core/servers/anjay_reload.c | 4 +- src/core/servers/anjay_server_connections.c | 2 - src/core/servers/anjay_servers_internal.c | 1 - src/core/servers/anjay_servers_internal.h | 3 + .../anjay_advanced_fw_update.c | 193 ++- src/modules/fw_update/anjay_fw_update.c | 70 +- src/modules/ipso/anjay_ipso_3d_sensor.c | 3 - src/modules/ipso/anjay_ipso_basic_sensor.c | 4 - src/modules/ipso/anjay_ipso_button.c | 3 - src/modules/server/anjay_mod_server.c | 16 +- standalone/security/standalone_mod_security.c | 1098 +++++++++++++++++ standalone/security/standalone_mod_security.h | 160 +++ standalone/security/standalone_security.h | 447 +++++++ .../standalone_security_persistence.c | 613 +++++++++ .../standalone_security_transaction.c | 575 +++++++++ .../standalone_security_transaction.h | 14 + .../security/standalone_security_utils.c | 472 +++++++ .../security/standalone_security_utils.h | 112 ++ standalone/server/standalone_mod_server.c | 783 ++++++++++++ standalone/server/standalone_mod_server.h | 99 ++ standalone/server/standalone_server.h | 190 +++ .../server/standalone_server_persistence.c | 521 ++++++++ .../server/standalone_server_transaction.c | 179 +++ .../server/standalone_server_transaction.h | 13 + standalone/server/standalone_server_utils.c | 67 + standalone/server/standalone_server_utils.h | 24 + tests/core/downloader/downloader.c | 437 ++++--- tests/integration/framework/asserts.py | 4 +- .../framework/create_xlsx_test_report.py | 286 +++++ .../framework/lwm2m/coap/option.py | 24 +- .../framework/lwm2m/coap/packet.py | 81 +- .../framework/lwm2m/coap/server.py | 34 +- .../framework/pretty_test_runner.py | 26 +- tests/integration/run_tests.sh.in | 13 +- tests/integration/runtest.py | 8 +- .../default/advanced_firmware_update.py | 703 ++++++++++- tests/integration/suites/default/async.py | 37 + .../suites/default/firmware_update.py | 601 ++++++++- .../suites/default/notifications.py | 35 + tools/anjay_codegen.py | 29 - tools/anjay_config_log_tool.py | 5 +- tools/ci-psa/Dockerfile | 4 +- tools/ci/rockylinux-9/Dockerfile | 2 +- tools/ci/ubuntu-18.04/Dockerfile | 2 +- tools/ci/ubuntu-20.04/Dockerfile | 2 +- tools/ci/ubuntu-22.04/Dockerfile | 2 +- tools/license_headers.py | 1 + 139 files changed, 9800 insertions(+), 1202 deletions(-) create mode 100644 deps/avs_coap/src/udp/avs_coap_udp_ctx.h create mode 100644 deps/avs_coap/tests/tcp/csm.c create mode 100644 doc/sphinx/source/Migrating/MigratingFromAnjay34.rst create mode 100644 doc/sphinx/source/Tools/StandaloneObjects.rst create mode 100644 standalone/security/standalone_mod_security.c create mode 100644 standalone/security/standalone_mod_security.h create mode 100644 standalone/security/standalone_security.h create mode 100644 standalone/security/standalone_security_persistence.c create mode 100644 standalone/security/standalone_security_transaction.c create mode 100644 standalone/security/standalone_security_transaction.h create mode 100644 standalone/security/standalone_security_utils.c create mode 100644 standalone/security/standalone_security_utils.h create mode 100644 standalone/server/standalone_mod_server.c create mode 100644 standalone/server/standalone_mod_server.h create mode 100644 standalone/server/standalone_server.h create mode 100644 standalone/server/standalone_server_persistence.c create mode 100644 standalone/server/standalone_server_transaction.c create mode 100644 standalone/server/standalone_server_transaction.h create mode 100644 standalone/server/standalone_server_utils.c create mode 100644 standalone/server/standalone_server_utils.h create mode 100644 tests/integration/framework/create_xlsx_test_report.py diff --git a/.github/workflows/anjay-tests.yml b/.github/workflows/anjay-tests.yml index 25181cee..9cfffe08 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.1 + container: avsystemembedded/anjay-travis:ubuntu-18.04-1.2 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.1 + container: avsystemembedded/anjay-travis:ubuntu-20.04-1.2 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.1 + container: avsystemembedded/anjay-travis:ubuntu-22.04-1.2 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.1 + container: avsystemembedded/anjay-travis:rockylinux-9-1.2 env: CC: ${{ matrix.CC }} CXX: ${{ matrix.CXX }} @@ -138,7 +138,7 @@ jobs: CXX: clang++ macOS-compilers-test: - runs-on: macos-11 + runs-on: macos-12 env: CC: ${{ matrix.CC }} CXX: ${{ matrix.CXX }} @@ -149,12 +149,14 @@ jobs: with: submodules: recursive - run: brew update - # NOTE: latest known compatible versions are openssl@3--3.0.5 and mbedtls--3.2.1 - - run: brew install openssl mbedtls $COMPILER_VERSION - - run: pip3 install sphinx sphinx-rtd-theme cbor2 aiocoap cryptography packaging requests wheel + # NOTE: latest known compatible versions are openssl@3--3.1.1 and mbedtls--3.4.0 + # 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: 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=C.UTF-8 make -j - - run: LC_ALL=C.UTF-8 make check + - run: LC_ALL=en_US.UTF-8 make -j + - run: LC_ALL=en_US.UTF-8 make check strategy: fail-fast: false matrix: diff --git a/CHANGELOG.md b/CHANGELOG.md index c2366c02..54fde488 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,64 @@ # Changelog +## 3.5.0 (September 7th, 2023) + +### BREAKING CHANGES + +- Reversed the order of calling the ``delivery_handler`` callback vs. canceling + the observation when sending notifications with 4.xx or 5.xx code; this change + is breaking only for direct users of ``avs_coap`` API + +### Features + +- Added APIs for suspending and resuming standalone downloads as well as + Firmware Update and Advanced Firmware Update PULL-mode downloads +- Added standalone versions of the Security and Server object implementations, + that can be customized by the end users +- Added definitions for common Core Object IDs in the public API +- Removed potentially faulty assertion in code generated by anjay_codegen.py +- (commercial feature only) added ``anjay_sim_bootstrap_calculate_md5()`` + function that allows verification whether SIM Bootstrap data has been changed + (e.g. as a result of SIM OTA) + +### Improvements + +- Rewritten Send-based reporting in Advanced Firmware Update in such a way that + it will now work with custom implementations of the Server object +- Simplified the CoAP downloader implementation so that the + ``get_remote_hostname`` socket operation is no longer necessary for download + resumption +- Made handling of initial peer CSM messages in CoAP+TCP asynchronous +- Updated the documentation with more descriptive warnings about functions that + 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 + +### Bugfixes + +- Fixed a critical bug that caused Anjay to crash when sending notifications + with 4.xx or 5.xx code over TCP +- Fixed a regression introduced in 2.13.0 that prevented the Firmware Update + and Advanced Firmware Update from compiling without the + ``ANJAY_WITH_DOWNLOADER`` configuration option enabled +- Fixed a condition where the Register or Update messages could be erroneously + regenerated when refreshing server connections while already performing a + Register or Update request +- Fixed a condition where the connection could be erroneously retried + automatically when a fatal failure was expected +- Decoupled the ``WITH_AVS_COAP_TCP`` and ``ANJAY_WITH_LWM2M11`` configuration + options so that they can be set independently as intended +- Fixed the ``devconfig`` script and Github Actions configuration for better + compatibility with building on macOS +- Refactored TCP binding handling in integration tests for more reliability +- Fixed the case where CoAP+TCP Abort message could erroneously be sent multiple + times +- Loosened some time constraints in Advanced Firmware Update tests +- Fixed supplemental iid sort in Advanced Firmware Update +- Fixed too early restart while performing an upgrade using Advanced Firmware + Update module in Anjay demo app +- Fixed too early persistence write while performing an upgrade using Advanced + Firmware Update module in Anjay demo app + ## 3.4.1 (June 23rd, 2023) ### Features diff --git a/CMakeLists.txt b/CMakeLists.txt index a00a5f76..ce37d5c6 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.4.1" CACHE STRING "Anjay library version") +set(ANJAY_VERSION "3.5.0" CACHE STRING "Anjay library version") set(ANJAY_BINARY_VERSION 1.0.0) set(ANJAY_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") @@ -105,7 +105,10 @@ set(ANJAY_DEFAULT_SEND_FORMAT AVS_COAP_FORMAT_NONE CACHE STRING ################# FEATURES THAT REQUIRE LIBRARY CONFIGURATION ################## +option(WITH_AVS_PERSISTENCE "Enable support for persisting objects data" ON) + option(WITH_BOOTSTRAP "Enable LwM2M Bootstrap Interface support" ON) +option(WITHOUT_TLV "Disable support for TLV content format" OFF) option(WITH_DOWNLOADER "Enable support for downloader API" ON) cmake_dependent_option(WITH_HTTP_DOWNLOAD "Enable support for HTTP(S) downloads" OFF "WITH_DOWNLOADER" OFF) option(WITH_LWM2M11 "Enable support for LwM2M 1.1" ON) @@ -128,6 +131,9 @@ endif() if(WITH_AVS_PERSISTENCE) list(APPEND AVS_COMMONS_COMPONENTS persistence) endif() +if(WITH_MODULE_sim_bootstrap) + list(APPEND AVS_COMMONS_COMPONENTS stream_md5) +endif() if(NOT DEFINED WITH_AVS_RBTREE OR WITH_AVS_RBTREE) set(WITH_AVS_RBTREE ON CACHE INTERNAL "") endif() @@ -203,18 +209,13 @@ else() message(STATUS "DTLS backend: ${_DTLS_BACKEND_LOWERCASE}") endif() -if(NOT WITH_LWM2M11) - set(WITH_AVS_COAP_TCP OFF CACHE "" INTERNAL) -endif() - -cmake_dependent_option(WITH_AVS_COAP_TCP "Enable CoAP over TCP support" ON WITH_LWM2M11 OFF) +option(WITH_AVS_COAP_TCP "Enable CoAP over TCP support" "${WITH_LWM2M11}") set(ENABLE_ADVANCED_CRYPTO OFF) option(WITH_AVS_LOG "Enable logging support" ON) -option(WITH_AVS_PERSISTENCE "Enable support for persisting objects data" ON) if(NOT WITH_LOCAL_AVS_COMMONS) set(WITH_SCHEDULER_THREAD_SAFE "${WITH_THREAD_SAFETY}" CACHE INTERNAL "") @@ -264,7 +265,6 @@ cmake_dependent_option(WITH_CON_ATTR "Enable support for the Confirmable Notific option(WITH_LEGACY_CONTENT_FORMAT_SUPPORT "Enable support for pre-LwM2M 1.0 CoAP Content-Format values (1541-1543)" OFF) option(WITH_LWM2M_JSON "Enable support for LwM2M 1.0 JSON (output only)" ON) -option(WITHOUT_TLV "Disable support for TLV content format" OFF) 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) diff --git a/demo/CMakeLists.txt b/demo/CMakeLists.txt index 2265ad32..64e072c6 100644 --- a/demo/CMakeLists.txt +++ b/demo/CMakeLists.txt @@ -8,6 +8,8 @@ cmake_minimum_required(VERSION 3.1) project(lwm2m_demo C) +option(WITH_DEMO_USE_STANDALONE_OBJECTS "Use standalone versions of built-in objects in demo" OFF) + set(SOURCES demo.c demo_args.c @@ -29,6 +31,11 @@ set(SOURCES objects/portfolio.c objects/test.c) +if(WITH_DEMO_USE_STANDALONE_OBJECTS) + file(GLOB STANDALONE_SOURCES ../standalone/*/*.c) + set(SOURCES ${SOURCES} ${STANDALONE_SOURCES}) +endif() + if (${ANJAY_WITH_MODULE_FW_UPDATE}) set(SOURCES ${SOURCES} firmware_update.c) endif() @@ -52,6 +59,11 @@ set(HEADERS demo_utils.h objects.h) +if(WITH_DEMO_USE_STANDALONE_OBJECTS) + file(GLOB STANDALONE_HEADERS ../standalone/*/*.h) + set(HEADERS ${HEADERS} ${STANDALONE_HEADERS}) +endif() + if (${ANJAY_WITH_MODULE_FW_UPDATE}) set(HEADERS ${HEADERS} firmware_update.h) endif() @@ -71,6 +83,10 @@ find_package(Threads REQUIRED) add_executable(demo ${ALL_SOURCES}) target_link_libraries(demo PRIVATE anjay m ${CMAKE_THREAD_LIBS_INIT}) +if(WITH_DEMO_USE_STANDALONE_OBJECTS) + target_compile_definitions(demo PRIVATE WITH_DEMO_USE_STANDALONE_OBJECTS) +endif() + add_custom_target(demo_firmware COMMAND python3 ${CMAKE_CURRENT_SOURCE_DIR}/../tests/integration/framework/firmware_package.py diff --git a/demo/advanced_firmware_update.c b/demo/advanced_firmware_update.c index 3853a546..7b8d9f46 100644 --- a/demo/advanced_firmware_update.c +++ b/demo/advanced_firmware_update.c @@ -732,6 +732,9 @@ int fw_update_common_write(anjay_iid_t iid, static int stream_finish(anjay_iid_t iid, void *fw_) { advanced_fw_update_logic_t *fw_table = (advanced_fw_update_logic_t *) fw_; advanced_fw_update_logic_t *fw = &fw_table[iid]; + if (fw->auto_suspend) { + anjay_advanced_fw_update_pull_suspend(fw->anjay); + } if (!fw->stream) { demo_log(ERROR, "stream not open"); return -1; @@ -937,6 +940,9 @@ void fw_update_common_reset(anjay_iid_t iid, void *fw_) { conflicting_instances_count); } } + if (fw->auto_suspend) { + anjay_advanced_fw_update_pull_suspend(fw->anjay); + } } int fw_update_common_perform_upgrade( @@ -1056,12 +1062,11 @@ int advanced_firmware_update_install( const avs_coap_udp_tx_params_t *tx_params, anjay_advanced_fw_update_result_t delayed_result, bool prefer_same_socket_downloads, - const char *original_img_file_path + const char *original_img_file_path, #ifdef ANJAY_WITH_SEND - , - bool use_lwm2m_send + bool use_lwm2m_send, #endif // ANJAY_WITH_SEND -) { + bool auto_suspend) { advanced_fw_update_logic_t *fw_logic_app = NULL; int result = -1; @@ -1167,7 +1172,8 @@ int advanced_firmware_update_install( maybe_delete_firmware_file(fw_logic_app); } result = advanced_firmware_update_application_install( - anjay, fw_table, &state, security_info, tx_params); + anjay, fw_table, &state, security_info, tx_params, + auto_suspend); if (result) { demo_log(ERROR, "AFU instance %u install failed", FW_UPDATE_IID_APP); @@ -1211,6 +1217,9 @@ int advanced_firmware_update_install( } if (!result) { + if (auto_suspend) { + anjay_advanced_fw_update_pull_suspend(anjay); + } demo_log(INFO, "AFU object install success"); } diff --git a/demo/advanced_firmware_update.h b/demo/advanced_firmware_update.h index 872225ff..2eddc012 100644 --- a/demo/advanced_firmware_update.h +++ b/demo/advanced_firmware_update.h @@ -74,6 +74,8 @@ struct advanced_fw_update_logic { const char *persistence_file; FILE *stream; avs_net_security_info_t security_info; + avs_coap_udp_tx_params_t coap_tx_params; + bool auto_suspend; int (*check_yourself)(struct advanced_fw_update_logic *); int (*update_yourself)(struct advanced_fw_update_logic *); avs_sched_handle_t update_job; @@ -85,7 +87,8 @@ int advanced_firmware_update_application_install( advanced_fw_update_logic_t *fw_logic, 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); + const avs_coap_udp_tx_params_t *tx_params, + 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, void *fw_); @@ -114,12 +117,11 @@ int advanced_firmware_update_install( const avs_coap_udp_tx_params_t *tx_params, anjay_advanced_fw_update_result_t delayed_result, bool prefer_same_socket_downloads, - const char *original_img_file_path + const char *original_img_file_path, #ifdef ANJAY_WITH_SEND - , - bool use_lwm2m_send + bool use_lwm2m_send, #endif // ANJAY_WITH_SEND -); + bool auto_suspend); void advanced_firmware_update_uninstall(advanced_fw_update_logic_t *fw_table); int fw_update_common_open(anjay_iid_t iid, void *fw_); diff --git a/demo/advanced_firmware_update_app.c b/demo/advanced_firmware_update_app.c index 0ab9fc78..a5b5e6bb 100644 --- a/demo/advanced_firmware_update_app.c +++ b/demo/advanced_firmware_update_app.c @@ -13,6 +13,8 @@ #include #include +#define RESTART_DELAY_SEC 3 + static advanced_fw_update_logic_t *fw_global; static int fw_stream_open(anjay_iid_t iid, void *fw_) { @@ -20,10 +22,6 @@ static int fw_stream_open(anjay_iid_t iid, void *fw_) { return fw_update_common_open(iid, fw_); } -struct execute_new_app_args { - advanced_fw_update_logic_t *fw; -}; - static int prepare_and_validate_update(advanced_fw_update_logic_t *fw) { demo_log(INFO, "Checking image of " AVS_QUOTE_MACRO( @@ -45,23 +43,13 @@ static int prepare_and_validate_update(advanced_fw_update_logic_t *fw) { return 0; } -static void execute_new_app(avs_sched_t *sched, const void *args_) { - (void) sched; - const struct execute_new_app_args *args = - (const struct execute_new_app_args *) args_; - demo_log(INFO, "App image going to execv from %s", - args->fw->next_target_path); - execv(args->fw->next_target_path, argv_get()); - demo_log(ERROR, "execv failed (%s)", strerror(errno)); -} - -static int update(advanced_fw_update_logic_t *fw) { - /* This function only works with APP so fw always points to fw_table: */ - advanced_fw_update_logic_t *fw_table = (advanced_fw_update_logic_t *) fw; - demo_log(INFO, "*** FIRMWARE UPDATE: %s ***", fw->next_target_path); +static int write_persistence(advanced_fw_update_logic_t *fw_table) { + advanced_fw_update_logic_t *fw = + (advanced_fw_update_logic_t *) &fw_table[FW_UPDATE_IID_APP]; states_results_paths_t states_results_paths; if (advanced_firmware_update_read_states_results_paths( fw_table, &states_results_paths)) { + demo_log(ERROR, "Can't read states/results/paths."); return -1; } states_results_paths.inst_states[FW_UPDATE_IID_APP] = @@ -79,11 +67,42 @@ static int update(advanced_fw_update_logic_t *fw) { fw->iid), fw->current_ver)) { advanced_firmware_update_delete_persistence_file(fw); + demo_log(ERROR, "Can't write persistence file."); return -1; } + return 0; +} + +struct execute_new_app_args { + advanced_fw_update_logic_t *fw_table; +}; + +static void execute_new_app(avs_sched_t *sched, const void *args_) { + (void) sched; + const struct execute_new_app_args *args = + (const struct execute_new_app_args *) args_; + advanced_fw_update_logic_t *fw = + (advanced_fw_update_logic_t *) &args->fw_table[FW_UPDATE_IID_APP]; + if (write_persistence(args->fw_table)) { + demo_log(ERROR, "Can't persist state. Execute new app failed."); + return; + } + demo_log(INFO, "App image going to execv from %s", fw->next_target_path); + execv(fw->next_target_path, argv_get()); + demo_log(ERROR, "execv failed (%s)", strerror(errno)); +} + +static int update(advanced_fw_update_logic_t *fw) { + /* This function only works with APP so fw always points to fw_table: */ + advanced_fw_update_logic_t *fw_table = (advanced_fw_update_logic_t *) fw; + demo_log(INFO, "*** FIRMWARE UPDATE: %s ***", fw->next_target_path); if (fw->metadata.force_error_case) { demo_log(INFO, "force_error_case present and set to: %d", (int) fw->metadata.force_error_case); + if (write_persistence(fw_table)) { + demo_log(ERROR, "Can't persist state. Update failed."); + return -1; + } } switch (fw->metadata.force_error_case) { case FORCE_ERROR_FAILED_UPDATE: @@ -125,23 +144,29 @@ static int update(advanced_fw_update_logic_t *fw) { break; } struct execute_new_app_args args = { - .fw = fw, + .fw_table = fw_table, }; - if (AVS_SCHED_NOW(anjay_get_scheduler(fw->anjay), &fw->update_job, - execute_new_app, &args, sizeof(args))) { + if (AVS_SCHED_DELAYED(anjay_get_scheduler(fw->anjay), &fw->update_job, + avs_time_duration_from_scalar(RESTART_DELAY_SEC, + AVS_TIME_S), + execute_new_app, &args, sizeof(args))) { demo_log(WARNING, "Could not schedule the upgrade job"); return ANJAY_ERR_INTERNAL; } return 0; } -static avs_coap_udp_tx_params_t advanced_fw_get_coap_tx_params_wrapper( +static avs_coap_udp_tx_params_t fw_get_coap_tx_params( 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_get_coap_tx_params(fw, download_uri); + if (fw->auto_suspend) { + anjay_advanced_fw_update_pull_reconnect(fw->anjay); + } + return fw->coap_tx_params; } static anjay_advanced_fw_update_handlers_t handlers = { @@ -151,8 +176,7 @@ static anjay_advanced_fw_update_handlers_t handlers = { .reset = fw_update_common_reset, .get_pkg_version = fw_update_common_get_pkg_version, .get_current_version = fw_update_common_get_current_version, - .perform_upgrade = fw_update_common_perform_upgrade, - .get_coap_tx_params = advanced_fw_get_coap_tx_params_wrapper + .perform_upgrade = fw_update_common_perform_upgrade }; static int fw_get_security_config(anjay_iid_t iid, @@ -173,7 +197,8 @@ int advanced_firmware_update_application_install( advanced_fw_update_logic_t *fw_table, 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) { + const avs_coap_udp_tx_params_t *tx_params, + bool auto_suspend) { advanced_fw_update_logic_t *fw_logic = &fw_table[FW_UPDATE_IID_APP]; if (security_info) { @@ -184,8 +209,14 @@ int advanced_firmware_update_application_install( handlers.get_security_config = NULL; } - if (tx_params) { - fw_set_coap_tx_params(tx_params); + if (tx_params || auto_suspend) { + if (tx_params) { + fw_logic->coap_tx_params = *tx_params; + } else if (auto_suspend) { + fw_logic->coap_tx_params = AVS_COAP_DEFAULT_UDP_TX_PARAMS; + } + fw_logic->auto_suspend = auto_suspend; + handlers.get_coap_tx_params = fw_get_coap_tx_params; } else { handlers.get_coap_tx_params = NULL; } diff --git a/demo/demo.c b/demo/demo.c index c4cf7b7f..dbea89f4 100644 --- a/demo/demo.c +++ b/demo/demo.c @@ -50,19 +50,33 @@ #include #include #include -#include -#include + +#ifdef WITH_DEMO_USE_STANDALONE_OBJECTS +# include "../standalone/security/standalone_security.h" +# include "../standalone/server/standalone_server.h" +#else // WITH_DEMO_USE_STANDALONE_OBJECTS +# include +# include +#endif // WITH_DEMO_USE_STANDALONE_OBJECTS #ifdef ANJAY_WITH_MODULE_FACTORY_PROVISIONING # include #endif // ANJAY_WITH_MODULE_FACTORY_PROVISIONING static int security_object_reload(anjay_demo_t *demo) { +#ifdef WITH_DEMO_USE_STANDALONE_OBJECTS + standalone_security_object_purge(demo->security_obj_ptr); +#else // WITH_DEMO_USE_STANDALONE_OBJECTS anjay_security_object_purge(demo->anjay); +#endif // WITH_DEMO_USE_STANDALONE_OBJECTS const server_connection_args_t *args = demo->connection_args; const server_entry_t *server; DEMO_FOREACH_SERVER_ENTRY(server, args) { +#ifdef WITH_DEMO_USE_STANDALONE_OBJECTS + standalone_security_instance_t instance; +#else // WITH_DEMO_USE_STANDALONE_OBJECTS anjay_security_instance_t instance; +#endif // WITH_DEMO_USE_STANDALONE_OBJECTS memset(&instance, 0, sizeof(instance)); instance.ssid = ANJAY_SSID_ANY; if ((instance.bootstrap_server = server->is_bootstrap)) { @@ -127,7 +141,15 @@ static int security_object_reload(anjay_demo_t *demo) { #endif // ANJAY_WITH_LWM2M11 anjay_iid_t iid = server->security_iid; - if (anjay_security_object_add_instance(demo->anjay, &instance, &iid)) { + if ( +#ifdef WITH_DEMO_USE_STANDALONE_OBJECTS + standalone_security_object_add_instance( + demo->security_obj_ptr, &instance, &iid) +#else // WITH_DEMO_USE_STANDALONE_OBJECTS + anjay_security_object_add_instance(demo->anjay, &instance, + &iid) +#endif // WITH_DEMO_USE_STANDALONE_OBJECTS + ) { demo_log(ERROR, "Cannot add Security Instance"); return -1; } @@ -136,31 +158,50 @@ static int security_object_reload(anjay_demo_t *demo) { } static int server_object_reload(anjay_demo_t *demo) { +#ifdef WITH_DEMO_USE_STANDALONE_OBJECTS + standalone_server_object_purge(demo->server_obj_ptr); +#else // WITH_DEMO_USE_STANDALONE_OBJECTS anjay_server_object_purge(demo->anjay); +#endif // WITH_DEMO_USE_STANDALONE_OBJECTS const server_entry_t *server; DEMO_FOREACH_SERVER_ENTRY(server, demo->connection_args) { if (server->is_bootstrap) { continue; } - const anjay_server_instance_t instance = { - .ssid = server->id, - .lifetime = demo->connection_args->lifetime, - .default_min_period = -1, - .default_max_period = -1, - .disable_timeout = -1, - .binding = server->binding_mode, - .notification_storing = true, +#ifdef WITH_DEMO_USE_STANDALONE_OBJECTS + const standalone_server_instance_t instance = +#else // WITH_DEMO_USE_STANDALONE_OBJECTS + const anjay_server_instance_t instance = +#endif // WITH_DEMO_USE_STANDALONE_OBJECTS + { + .ssid = server->id, + .lifetime = demo->connection_args->lifetime, + .default_min_period = -1, + .default_max_period = -1, + .disable_timeout = -1, + .binding = server->binding_mode, + .notification_storing = true, #ifdef ANJAY_WITH_LWM2M11 - .communication_retry_count = &server->retry_count, - .communication_retry_timer = &server->retry_timer, - .communication_sequence_retry_count = &server->sequence_retry_count, - .communication_sequence_delay_timer = &server->sequence_delay_timer, - .preferred_transport = '\0', + .communication_retry_count = &server->retry_count, + .communication_retry_timer = &server->retry_timer, + .communication_sequence_retry_count = + &server->sequence_retry_count, + .communication_sequence_delay_timer = + &server->sequence_delay_timer, + .preferred_transport = '\0', #endif // ANJAY_WITH_LWM2M11 - }; + }; anjay_iid_t iid = server->server_iid; - if (anjay_server_object_add_instance(demo->anjay, &instance, &iid)) { + if ( +#ifdef WITH_DEMO_USE_STANDALONE_OBJECTS + standalone_server_object_add_instance(demo->server_obj_ptr, + &instance, &iid) +#else // WITH_DEMO_USE_STANDALONE_OBJECTS + anjay_server_object_add_instance(demo->anjay, &instance, + &iid) +#endif // WITH_DEMO_USE_STANDALONE_OBJECTS + ) { demo_log(ERROR, "Cannot add Server Instance"); return -1; } @@ -208,8 +249,15 @@ static void demo_delete(anjay_demo_t *demo) { avs_stream_t *data = avs_stream_file_create(demo->dm_persistence_file, AVS_STREAM_FILE_WRITE); if (!data +# ifdef WITH_DEMO_USE_STANDALONE_OBJECTS + || avs_is_err(standalone_security_object_persist( + demo->security_obj_ptr, data)) + || avs_is_err(standalone_server_object_persist( + demo->server_obj_ptr, data)) +# else // WITH_DEMO_USE_STANDALONE_OBJECTS || avs_is_err(anjay_security_object_persist(demo->anjay, data)) || avs_is_err(anjay_server_object_persist(demo->anjay, data)) +# endif // WITH_DEMO_USE_STANDALONE_OBJECTS # ifdef ANJAY_WITH_MODULE_ACCESS_CONTROL || avs_is_err(anjay_access_control_persist(demo->anjay, data)) # endif // ANJAY_WITH_MODULE_ACCESS_CONTROL @@ -244,8 +292,14 @@ static void demo_delete(anjay_demo_t *demo) { #ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE advanced_firmware_update_uninstall(demo->advanced_fw_update_logic_table); #endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE - - AVS_LIST_CLEAR(&demo->allocated_strings); +#ifdef WITH_DEMO_USE_STANDALONE_OBJECTS + standalone_server_object_cleanup(demo->server_obj_ptr); + standalone_security_object_cleanup(demo->security_obj_ptr); + demo->server_obj_ptr = NULL; + demo->security_obj_ptr = NULL; +#endif // WITH_DEMO_USE_STANDALONE_OBJECTS + + AVS_LIST_CLEAR(&demo->allocated_buffers); avs_free(demo); } @@ -442,6 +496,9 @@ static void reschedule_notify_time_dependent(anjay_demo_t *demo) { } static int demo_init(anjay_demo_t *demo, cmdline_args_t *cmdline_args) { + demo->allocated_buffers = cmdline_args->allocated_buffers; + cmdline_args->allocated_buffers = NULL; + for (size_t i = 0; i < MAX_SERVERS; ++i) { server_entry_t *entry = &cmdline_args->connection_args.servers[i]; if (entry->uri == NULL) { @@ -536,6 +593,7 @@ static int demo_init(anjay_demo_t *demo, cmdline_args_t *cmdline_args) { # ifdef AVS_COMMONS_WITH_AVS_PERSISTENCE demo->dm_persistence_file = cmdline_args->dm_persistence_file; # endif // AVS_COMMONS_WITH_AVS_PERSISTENCE + // defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP) #endif // AVS_COMMONS_STREAM_WITH_FILE { demo->anjay = anjay_new(&config); } if (!demo->anjay @@ -546,56 +604,70 @@ static int demo_init(anjay_demo_t *demo, cmdline_args_t *cmdline_args) { return -1; } - if (anjay_security_object_install(demo->anjay) - || anjay_server_object_install(demo->anjay) + if ( +#ifdef WITH_DEMO_USE_STANDALONE_OBJECTS + !(demo->security_obj_ptr = + standalone_security_object_install(demo->anjay)) + || !(demo->server_obj_ptr = + standalone_server_object_install(demo->anjay)) +#else // WITH_DEMO_USE_STANDALONE_OBJECTS + anjay_security_object_install(demo->anjay) + || anjay_server_object_install(demo->anjay) +#endif // WITH_DEMO_USE_STANDALONE_OBJECTS #ifdef ANJAY_WITH_MODULE_IPSO_OBJECTS - || install_accelerometer_object(demo->anjay) - || add_installed_object_update_handler(demo, - accelerometer_update_handler) - || install_push_button_object(demo->anjay) - || install_temperature_object(demo->anjay) - || add_installed_object_update_handler(demo, - temperature_update_handler) + || install_accelerometer_object(demo->anjay) + || add_installed_object_update_handler( + demo, accelerometer_update_handler) + || install_push_button_object(demo->anjay) + || install_temperature_object(demo->anjay) + || add_installed_object_update_handler( + demo, temperature_update_handler) #endif // ANJAY_WITH_MODULE_IPSO_OBJECTS - || install_object(demo, location_object_create(), NULL, - location_notify_time_dependent, - location_object_release) - || install_object(demo, apn_conn_profile_object_create(), - apn_conn_profile_get_instances, NULL, - apn_conn_profile_object_release) - || install_object(demo, binary_app_data_container_object_create(), - binary_app_data_container_get_instances, NULL, - binary_app_data_container_object_release) - || install_object(demo, cell_connectivity_object_create(demo), NULL, - NULL, cell_connectivity_object_release) - || install_object(demo, cm_object_create(), NULL, - cm_notify_time_dependent, cm_object_release) - || install_object(demo, cs_object_create(), NULL, NULL, - cs_object_release) - || install_object(demo, download_diagnostics_object_create(), NULL, - NULL, download_diagnostics_object_release) - || install_object(demo, - device_object_create(cmdline_args->endpoint_name), - NULL, device_notify_time_dependent, - device_object_release) - || install_object(demo, ext_dev_info_object_create(), NULL, - ext_dev_info_notify_time_dependent, - ext_dev_info_object_release) - || install_object(demo, geopoints_object_create(demo), - geopoints_get_instances, - geopoints_notify_time_dependent, - geopoints_object_release) + || install_object(demo, location_object_create(), NULL, + location_notify_time_dependent, + location_object_release) + || install_object(demo, apn_conn_profile_object_create(), + apn_conn_profile_get_instances, NULL, + apn_conn_profile_object_release) + || install_object( + demo, binary_app_data_container_object_create(), + binary_app_data_container_get_instances, NULL, + binary_app_data_container_object_release) + || install_object( + demo, cell_connectivity_object_create(demo), + NULL, NULL, cell_connectivity_object_release) + || install_object(demo, cm_object_create(), NULL, + cm_notify_time_dependent, + cm_object_release) + || install_object(demo, cs_object_create(), NULL, NULL, + cs_object_release) + || install_object( + demo, download_diagnostics_object_create(), NULL, + NULL, download_diagnostics_object_release) + || install_object(demo, + device_object_create( + cmdline_args->endpoint_name), + NULL, device_notify_time_dependent, + device_object_release) + || install_object(demo, ext_dev_info_object_create(), NULL, + ext_dev_info_notify_time_dependent, + ext_dev_info_object_release) + || install_object(demo, geopoints_object_create(demo), + geopoints_get_instances, + geopoints_notify_time_dependent, + geopoints_object_release) #ifndef _WIN32 - || install_object(demo, ip_ping_object_create(), NULL, NULL, - ip_ping_object_release) + || install_object(demo, ip_ping_object_create(), NULL, NULL, + ip_ping_object_release) #endif // _WIN32 - || install_object(demo, test_object_create(), test_get_instances, - test_notify_time_dependent, test_object_release) - || install_object(demo, portfolio_object_create(), - portfolio_get_instances, NULL, - portfolio_object_release) - || install_object(demo, event_log_object_create(), NULL, NULL, - event_log_object_release)) { + || install_object( + demo, test_object_create(), test_get_instances, + test_notify_time_dependent, test_object_release) + || install_object(demo, portfolio_object_create(), + portfolio_get_instances, NULL, + portfolio_object_release) + || install_object(demo, event_log_object_create(), NULL, + NULL, event_log_object_release)) { return -1; } @@ -614,8 +686,15 @@ static int demo_init(anjay_demo_t *demo, cmdline_args_t *cmdline_args) { avs_stream_file_create(cmdline_args->dm_persistence_file, AVS_STREAM_FILE_READ); if (!data +# ifdef WITH_DEMO_USE_STANDALONE_OBJECTS + || avs_is_err(standalone_security_object_restore( + demo->security_obj_ptr, data)) + || avs_is_err(standalone_server_object_restore( + demo->server_obj_ptr, data)) +# else // WITH_DEMO_USE_STANDALONE_OBJECTS || avs_is_err(anjay_security_object_restore(demo->anjay, data)) || avs_is_err(anjay_server_object_restore(demo->anjay, data)) +# endif // WITH_DEMO_USE_STANDALONE_OBJECTS # ifdef ANJAY_WITH_MODULE_ACCESS_CONTROL || avs_is_err(anjay_access_control_restore(demo->anjay, data)) # endif // ANJAY_WITH_MODULE_ACCESS_CONTROL @@ -665,12 +744,11 @@ static int demo_init(anjay_demo_t *demo, cmdline_args_t *cmdline_args) { ? &cmdline_args->fwu_tx_params : NULL, cmdline_args->fw_update_delayed_result, - cmdline_args->prefer_same_socket_downloads + cmdline_args->prefer_same_socket_downloads, # ifdef ANJAY_WITH_SEND - , - cmdline_args->fw_update_use_send + cmdline_args->fw_update_use_send, # endif // ANJAY_WITH_SEND - )) { + cmdline_args->fw_update_auto_suspend)) { return -1; } #endif // ANJAY_WITH_MODULE_FW_UPDATE @@ -688,12 +766,11 @@ static int demo_init(anjay_demo_t *demo, cmdline_args_t *cmdline_args) { : NULL, cmdline_args->advanced_fw_update_delayed_result, cmdline_args->prefer_same_socket_downloads, - cmdline_args->original_img_file_path + cmdline_args->original_img_file_path, # ifdef ANJAY_WITH_SEND - , - cmdline_args->advanced_fw_update_use_send + cmdline_args->advanced_fw_update_use_send, # endif // ANJAY_WITH_SEND - )) { + cmdline_args->advanced_fw_update_auto_suspend)) { return -1; } #endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE @@ -804,13 +881,11 @@ log_handler(avs_log_level_t level, const char *module, const char *message) { } static void cmdline_args_cleanup(cmdline_args_t *cmdline_args) { - avs_free(cmdline_args->connection_args.public_cert_or_psk_identity); - avs_free(cmdline_args->connection_args.private_cert_or_psk_key); - avs_free(cmdline_args->connection_args.server_public_key); #ifdef ANJAY_WITH_MODULE_ACCESS_CONTROL AVS_LIST_CLEAR(&cmdline_args->access_entries); #endif // ANJAY_WITH_MODULE_ACCESS_CONTROL avs_free(cmdline_args->default_ciphersuites); + AVS_LIST_CLEAR(&cmdline_args->allocated_buffers); } int main(int argc, char *argv[]) { diff --git a/demo/demo.h b/demo/demo.h index 7d67193d..5a3acc64 100644 --- a/demo/demo.h +++ b/demo/demo.h @@ -29,10 +29,6 @@ #endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE #include "objects.h" -typedef struct { - char data[1]; // actually a VLA, but struct cannot be empty -} anjay_demo_string_t; - typedef int anjay_demo_object_get_instances_t(const anjay_dm_object_def_t **, AVS_LIST(anjay_iid_t) *); typedef void anjay_demo_object_deleter_t(const anjay_dm_object_def_t **); @@ -51,7 +47,7 @@ typedef void anjay_update_handler_t(anjay_t *anjay); struct anjay_demo_struct { anjay_t *anjay; - AVS_LIST(anjay_demo_string_t) allocated_strings; + AVS_LIST(anjay_demo_allocated_buffer_t) allocated_buffers; server_connection_args_t *connection_args; #ifdef AVS_COMMONS_STREAM_WITH_FILE # ifdef ANJAY_WITH_ATTR_STORAGE @@ -71,6 +67,11 @@ struct anjay_demo_struct { advanced_fw_update_logic_table[FW_UPDATE_IID_IMAGE_SLOTS]; #endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE +#ifdef WITH_DEMO_USE_STANDALONE_OBJECTS + const anjay_dm_object_def_t **security_obj_ptr; + const anjay_dm_object_def_t **server_obj_ptr; +#endif // WITH_DEMO_USE_STANDALONE_OBJECTS + AVS_LIST(anjay_demo_object_t) objects; AVS_LIST(anjay_update_handler_t *) installed_objects_update_handlers; diff --git a/demo/demo_args.c b/demo/demo_args.c index 06a3ffff..57789090 100644 --- a/demo/demo_args.c +++ b/demo/demo_args.c @@ -405,11 +405,11 @@ static void print_help(const struct option *options) { "Configures preference of re-using existing LwM2M CoAP contexts for " "firmware download" }, { 284, "NSTART", "1", "Configures NSTART (defined in RFC7252)" }, -#ifdef ANJAY_WITH_SEND +#if defined(ANJAY_WITH_SEND) && defined(ANJAY_WITH_MODULE_FW_UPDATE) { 287, NULL, NULL, "Enables using LwM2M Send to report state and result of firmware " "update" }, -#endif // ANJAY_WITH_SEND +#endif // defined(ANJAY_WITH_SEND) && defined(ANJAY_WITH_MODULE_FW_UPDATE) #ifdef ANJAY_WITH_LWM2M11 { 288, "TRUST_STORE_PATH", NULL, "Path (file or directory) to use as the trust store for " @@ -484,6 +484,27 @@ static void print_help(const struct option *options) { #endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE { 328, NULL, NULL, "Enter offline mode before starting the event loop." }, +#ifdef ANJAY_WITH_MODULE_FW_UPDATE + { 329, NULL, NULL, + "Start the Firmware Update downloads in suspended mode and resume " + "them just when they are requested. Useful for testing purposes " + "only." }, +#endif // ANJAY_WITH_MODULE_FW_UPDATE +#ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE + { 330, NULL, NULL, + "Start the Advanced Firmware Update downloads in suspended mode and " + "resume them just in time. Useful for testing purposes only." }, + { 331, "PSK identity", NULL, + "Download firmware over encrypted channels using PSK-mode encryption " + "with the specified identity (provided as hexlified string); this " + "argument is used by Advanced Firmware Update and must be used " + "together with --afu-psk-key" }, + { 332, "PSK key", NULL, + "Download firmware over encrypted channels using PSK-mode encryption " + "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 }; const size_t screen_width = get_screen_width(); @@ -590,7 +611,10 @@ static int parse_double(const char *str, double *out_value) { return 0; } -static int parse_hexstring(const char *str, uint8_t **out, size_t *out_size) { +static int parse_hexstring(cmdline_args_t *cmdline_args, + const char *str, + uint8_t **out, + size_t *out_size) { if (!str) { return -1; } @@ -602,23 +626,26 @@ static int parse_hexstring(const char *str, uint8_t **out, size_t *out_size) { if (*out) { return -1; } - *out = (uint8_t *) avs_malloc(length / 2); - *out_size = 0; - if (!*out) { + AVS_LIST(anjay_demo_allocated_buffer_t) buffer = (AVS_LIST( + anjay_demo_allocated_buffer_t)) AVS_LIST_NEW_BUFFER(length / 2); + if (!buffer) { return -1; } + *out = (uint8_t *) buffer; + *out_size = 0; const char *curr = str; uint8_t *data = *out; while (*curr) { unsigned value; if (sscanf(curr, "%2x", &value) != 1 || (uint8_t) value != value) { - avs_free(*out); + AVS_LIST_DELETE(&buffer); return -1; } *data++ = (uint8_t) value; curr += 2; } *out_size = length / 2; + AVS_LIST_INSERT(&cmdline_args->allocated_buffers, buffer); return 0; } @@ -663,8 +690,11 @@ static int clone_buffer(uint8_t **out, return 0; } -static int -load_buffer_from_file(uint8_t **out, size_t *out_size, const char *filename) { +static int load_buffer_from_file(cmdline_args_t *cmdline_args, + uint8_t **out, + size_t *out_size, + const char *filename) { + AVS_LIST(anjay_demo_allocated_buffer_t) buffer = NULL; FILE *f = fopen(filename, "rb"); if (!f) { return -1; @@ -681,16 +711,21 @@ load_buffer_from_file(uint8_t **out, size_t *out_size, const char *filename) { if (!(*out_size = (size_t) size)) { *out = NULL; } else { - if (!(*out = (uint8_t *) avs_malloc(*out_size))) { + if (!(buffer = (AVS_LIST(anjay_demo_allocated_buffer_t)) + AVS_LIST_NEW_BUFFER(*out_size))) { goto finish; } + *out = (uint8_t *) buffer; if (fread(*out, *out_size, 1, f) != 1) { - avs_free(*out); + AVS_LIST_DELETE(&buffer); *out = NULL; goto finish; } } result = 0; + if (buffer) { + AVS_LIST_INSERT(&cmdline_args->allocated_buffers, buffer); + } finish: fclose(f); return result; @@ -830,13 +865,21 @@ int demo_parse_argv(cmdline_args_t *parsed_args, int argc, char *argv[]) { { "afu-marker-path", required_argument, 0, 323 }, # endif // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) && defined(AVS_COMMONS_STREAM_WITH_FILE) { "afu-cert-file", required_argument, 0, 324 }, - { "delayed-afu-result", required_argument, 0, 325 }, + { "delayed-afu-result", required_argument, 0, 325 }, # if defined(ANJAY_WITH_SEND) - { "afu-use-send", no_argument, 0, 326 }, + { "afu-use-send", no_argument, 0, 326 }, # endif // defined(ANJAY_WITH_SEND) - { "afu-ack-timeout", required_argument, 0, 327 }, + { "afu-ack-timeout", required_argument, 0, 327 }, +#endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE + { "start-offline", no_argument, 0, 328 }, +#ifdef ANJAY_WITH_MODULE_FW_UPDATE + { "fw-auto-suspend", no_argument, 0, 329 }, +#endif // ANJAY_WITH_MODULE_FW_UPDATE +#ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE + { "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 - { "start-offline", no_argument, 0, 328 }, { 0, 0, 0, 0 } // clang-format on }; @@ -988,7 +1031,7 @@ int demo_parse_argv(cmdline_args_t *parsed_args, int argc, char *argv[]) { break; } case 'i': - if (parse_hexstring(optarg, + if (parse_hexstring(parsed_args, optarg, &parsed_args->connection_args .public_cert_or_psk_identity, &parsed_args->connection_args @@ -1002,7 +1045,7 @@ int demo_parse_argv(cmdline_args_t *parsed_args, int argc, char *argv[]) { break; case 'k': if (parse_hexstring( - optarg, + parsed_args, optarg, &parsed_args->connection_args.private_cert_or_psk_key, &parsed_args->connection_args .private_cert_or_psk_key_size)) { @@ -1197,7 +1240,8 @@ int demo_parse_argv(cmdline_args_t *parsed_args, int argc, char *argv[]) { } uint8_t *identity_buf = NULL; size_t identity_size = 0; - if (parse_hexstring(optarg, &identity_buf, &identity_size)) { + if (parse_hexstring(parsed_args, optarg, &identity_buf, + &identity_size)) { demo_log(ERROR, "Invalid PSK identity for firmware upgrade"); goto finish; } @@ -1223,7 +1267,7 @@ int demo_parse_argv(cmdline_args_t *parsed_args, int argc, char *argv[]) { } uint8_t *psk_buf = NULL; size_t psk_size = 0; - if (parse_hexstring(optarg, &psk_buf, &psk_size)) { + if (parse_hexstring(parsed_args, optarg, &psk_buf, &psk_size)) { demo_log(ERROR, "Invalid pre-shared key for firmware upgrade"); goto finish; } @@ -1421,11 +1465,11 @@ int demo_parse_argv(cmdline_args_t *parsed_args, int argc, char *argv[]) { goto finish; } break; -#ifdef ANJAY_WITH_SEND +#if defined(ANJAY_WITH_SEND) && defined(ANJAY_WITH_MODULE_FW_UPDATE) case 287: parsed_args->fw_update_use_send = true; break; -#endif // ANJAY_WITH_SEND +#endif // defined(ANJAY_WITH_SEND) && defined(ANJAY_WITH_MODULE_FW_UPDATE) #ifdef ANJAY_WITH_LWM2M11 case 288: parsed_args->pkix_trust_store = optarg; @@ -1527,7 +1571,12 @@ int demo_parse_argv(cmdline_args_t *parsed_args, int argc, char *argv[]) { # endif // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) && // defined(AVS_COMMONS_STREAM_WITH_FILE) case 324: { - + if (parsed_args->advanced_fw_security_info.mode + != (avs_net_security_mode_t) -1) { + demo_log(ERROR, "Multiple incompatible security information " + "specified for advanced firmware upgrade"); + goto finish; + } const avs_net_certificate_info_t cert_info = { .server_cert_validation = true, .trusted_certs = @@ -1570,6 +1619,77 @@ int demo_parse_argv(cmdline_args_t *parsed_args, int argc, char *argv[]) { case 328: parsed_args->start_offline = true; break; +#ifdef ANJAY_WITH_MODULE_FW_UPDATE + case 329: + parsed_args->fw_update_auto_suspend = true; + break; +#endif // ANJAY_WITH_MODULE_FW_UPDATE +#ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE + case 330: + parsed_args->advanced_fw_update_auto_suspend = true; + break; + case 331: { + if (parsed_args->advanced_fw_security_info.mode + != AVS_NET_SECURITY_PSK + && parsed_args->advanced_fw_security_info.mode + != (avs_net_security_mode_t) -1) { + demo_log(ERROR, "Multiple incompatible security information " + "specified for advanced firmware upgrade"); + goto finish; + } + if (parsed_args->advanced_fw_security_info.mode + == AVS_NET_SECURITY_PSK + && parsed_args->advanced_fw_security_info.data.psk.identity + .desc.source + != AVS_CRYPTO_DATA_SOURCE_EMPTY) { + demo_log(ERROR, "--afu-psk-identity specified more than once"); + goto finish; + } + uint8_t *identity_buf = NULL; + size_t identity_size = 0; + if (parse_hexstring(parsed_args, optarg, &identity_buf, + &identity_size)) { + demo_log(ERROR, + "Invalid PSK identity for advanced firmware upgrade"); + goto finish; + } + parsed_args->advanced_fw_security_info.mode = AVS_NET_SECURITY_PSK; + parsed_args->advanced_fw_security_info.data.psk.identity = + avs_crypto_psk_identity_info_from_buffer(identity_buf, + identity_size); + break; + } + case 332: { + if (parsed_args->advanced_fw_security_info.mode + != AVS_NET_SECURITY_PSK + && parsed_args->advanced_fw_security_info.mode + != (avs_net_security_mode_t) -1) { + demo_log(ERROR, "Multiple incompatible security information " + "specified for advanced firmware upgrade"); + goto finish; + } + if (parsed_args->advanced_fw_security_info.mode + == AVS_NET_SECURITY_PSK + && parsed_args->advanced_fw_security_info.data.psk.key.desc + .source + != AVS_CRYPTO_DATA_SOURCE_EMPTY) { + demo_log(ERROR, "--afu-psk-key specified more than once"); + goto finish; + } + uint8_t *psk_buf = NULL; + size_t psk_size = 0; + if (parse_hexstring(parsed_args, optarg, &psk_buf, &psk_size)) { + demo_log( + ERROR, + "Invalid pre-shared key for advanced firmware upgrade"); + goto finish; + } + parsed_args->advanced_fw_security_info.mode = AVS_NET_SECURITY_PSK; + parsed_args->advanced_fw_security_info.data.psk.key = + avs_crypto_psk_key_info_from_buffer(psk_buf, psk_size); + break; + } +#endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE case 0: goto process; } @@ -1671,6 +1791,7 @@ int demo_parse_argv(cmdline_args_t *parsed_args, int argc, char *argv[]) { #endif // ANJAY_WITH_SECURITY_STRUCTURED { if (load_buffer_from_file( + parsed_args, &parsed_args->connection_args .public_cert_or_psk_identity, &parsed_args->connection_args @@ -1681,6 +1802,7 @@ int demo_parse_argv(cmdline_args_t *parsed_args, int argc, char *argv[]) { retval = -1; } if (load_buffer_from_file( + parsed_args, &parsed_args->connection_args .private_cert_or_psk_key, &parsed_args->connection_args @@ -1711,6 +1833,7 @@ int demo_parse_argv(cmdline_args_t *parsed_args, int argc, char *argv[]) { #endif // ANJAY_WITH_SECURITY_STRUCTURED if (server_public_key_path && load_buffer_from_file( + parsed_args, &parsed_args->connection_args.server_public_key, &parsed_args->connection_args.server_public_key_size, server_public_key_path)) { @@ -1732,6 +1855,7 @@ int demo_parse_argv(cmdline_args_t *parsed_args, int argc, char *argv[]) { #endif // ANJAY_WITH_MODULE_FW_UPDATE finish: if (retval) { + AVS_LIST_CLEAR(&parsed_args->allocated_buffers); #ifdef ANJAY_WITH_MODULE_ACCESS_CONTROL AVS_LIST_CLEAR(&parsed_args->access_entries); #endif // ANJAY_WITH_MODULE_ACCESS_CONTROL diff --git a/demo/demo_args.h b/demo/demo_args.h index c4e6fc26..49d62122 100644 --- a/demo/demo_args.h +++ b/demo/demo_args.h @@ -61,7 +61,8 @@ typedef struct cmdline_args { # ifdef ANJAY_WITH_SEND bool fw_update_use_send; # endif // ANJAY_WITH_SEND -#endif // ANJAY_WITH_MODULE_FW_UPDATE + bool fw_update_auto_suspend; +#endif // ANJAY_WITH_MODULE_FW_UPDATE #ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE const char *advanced_fw_updated_marker_path; avs_net_security_info_t advanced_fw_security_info; @@ -77,6 +78,7 @@ typedef struct cmdline_args { # ifdef ANJAY_WITH_SEND bool advanced_fw_update_use_send; # endif // ANJAY_WITH_SEND + bool advanced_fw_update_auto_suspend; /** * This is a file path to file with original image. After additional * image is downloaded, update can be performed. Updating additional @@ -146,6 +148,8 @@ typedef struct cmdline_args { #if defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP) avs_time_duration_t tcp_request_timeout; #endif // defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP) + + AVS_LIST(anjay_demo_allocated_buffer_t) allocated_buffers; } cmdline_args_t; int demo_parse_argv(cmdline_args_t *parsed_args, int argc, char **argv); diff --git a/demo/demo_cmds.c b/demo/demo_cmds.c index 626451a5..d047014a 100644 --- a/demo/demo_cmds.c +++ b/demo/demo_cmds.c @@ -24,7 +24,12 @@ #include #include -#include + +#ifdef WITH_DEMO_USE_STANDALONE_OBJECTS +# include "../standalone/security/standalone_security.h" +#else // WITH_DEMO_USE_STANDALONE_OBJECTS +# include +#endif // WITH_DEMO_USE_STANDALONE_OBJECTS #ifdef ANJAY_WITH_SEND # include @@ -188,6 +193,16 @@ static void cmd_set_afu_result(anjay_demo_t *demo, const char *args_string) { "failed."); } } + +static void cmd_afu_suspend(anjay_demo_t *demo, const char *args_string) { + (void) args_string; + anjay_advanced_fw_update_pull_suspend(demo->anjay); +} + +static void cmd_afu_reconnect(anjay_demo_t *demo, const char *args_string) { + (void) args_string; + anjay_advanced_fw_update_pull_reconnect(demo->anjay); +} #endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE static void cmd_open_location_csv(anjay_demo_t *demo, const char *args_string) { @@ -228,14 +243,14 @@ static int add_server(anjay_demo_t *demo, const char *uri) { return -1; } size_t uri_size = strlen(uri) + 1; - AVS_LIST(anjay_demo_string_t) copied_uri = - (AVS_LIST(anjay_demo_string_t)) AVS_LIST_NEW_BUFFER(uri_size); + AVS_LIST(anjay_demo_allocated_buffer_t) copied_uri = (AVS_LIST( + anjay_demo_allocated_buffer_t)) AVS_LIST_NEW_BUFFER(uri_size); if (!copied_uri) { demo_log(ERROR, "Out of memory"); return -1; } memcpy(copied_uri->data, uri, uri_size); - AVS_LIST_INSERT(&demo->allocated_strings, copied_uri); + AVS_LIST_INSERT(&demo->allocated_buffers, copied_uri); server_entry_t *entry = &demo->connection_args->servers[num_servers]; *entry = demo->connection_args->servers[num_servers - 1]; @@ -643,6 +658,9 @@ static void cmd_download(anjay_demo_t *demo, const char *args_string) { if (avs_is_err(anjay_download(demo->anjay, &cfg, &user_data->handle))) { demo_log(ERROR, "could not schedule download"); demo_download_user_data_destroy(user_data); + } else { + printf("DOWNLOAD_HANDLE==%" PRIxPTR "\n", + (uintptr_t) user_data->handle); } } @@ -1021,6 +1039,17 @@ static void cmd_set_fw_update_result(anjay_demo_t *demo, anjay_fw_update_set_result(demo->anjay, (anjay_fw_update_result_t) result); } +static void cmd_fw_update_suspend(anjay_demo_t *demo, const char *args_string) { + (void) args_string; + anjay_fw_update_pull_suspend(demo->anjay); +} + +static void cmd_fw_update_reconnect(anjay_demo_t *demo, + const char *args_string) { + (void) args_string; + anjay_fw_update_pull_reconnect(demo->anjay); +} + #endif // ANJAY_WITH_MODULE_FW_UPDATE static void cmd_ongoing_registration_exists(anjay_demo_t *demo, @@ -1037,7 +1066,14 @@ static void cmd_set_lifetime(anjay_demo_t *demo, const char *args_string) { demo_log(ERROR, "The command requires both Instance ID and Lifetime"); return; } - if (anjay_server_object_set_lifetime(demo->anjay, iid, lifetime)) { + if ( +#ifdef WITH_DEMO_USE_STANDALONE_OBJECTS + standalone_server_object_set_lifetime(demo->server_obj_ptr, iid, + lifetime) +#else // WITH_DEMO_USE_STANDALONE_OBJECTS + anjay_server_object_set_lifetime(demo->anjay, iid, lifetime) +#endif // WITH_DEMO_USE_STANDALONE_OBJECTS + ) { demo_log(ERROR, "Could not set server lifetime to the desired value"); } } @@ -1376,6 +1412,13 @@ static const struct cmd_handler_def COMMAND_HANDLERS[] = { CMD_HANDLER("set-afu-result", "RESULT", cmd_set_afu_result, "Attempts to set Advanced Firmware Update Result of instance " "/" AVS_QUOTE_MACRO(ANJAY_ADVANCED_FW_UPDATE_OID) "/0 (APP) at runtime"), + CMD_HANDLER("afu-suspend", "", cmd_afu_suspend, + "Suspends the operation of PULL-mode downloads in the Advanced " + "Firmware Update module"), + CMD_HANDLER("afu-reconnect", "", cmd_afu_reconnect, + "Reconnects any ongoing PULL-mode downloads in the Advanced " + "Firmware Update module and if PULL-mode downloads are " + "suspended, resumes normal operation"), #endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE CMD_HANDLER("open-location-csv", "filename frequency=1", cmd_open_location_csv, @@ -1462,6 +1505,13 @@ static const struct cmd_handler_def COMMAND_HANDLERS[] = { #ifdef ANJAY_WITH_MODULE_FW_UPDATE CMD_HANDLER("set-fw-update-result", "RESULT", cmd_set_fw_update_result, "Attempts to set Firmware Update Result at runtime"), + CMD_HANDLER("fw-update-suspend", "", cmd_fw_update_suspend, + "Suspends the operation of PULL-mode downloads in the Firmware " + "Update module"), + CMD_HANDLER("fw-update-reconnect", "", cmd_fw_update_reconnect, + "Reconnects any ongoing PULL-mode downloads in the Firmware " + "Update module and if PULL-mode downloads are suspended, " + "resumes normal operation"), #endif // ANJAY_WITH_MODULE_FW_UPDATE CMD_HANDLER("ongoing-registration-exists", "", cmd_ongoing_registration_exists, diff --git a/demo/demo_utils.c b/demo/demo_utils.c index 834e9786..1e87a671 100644 --- a/demo/demo_utils.c +++ b/demo/demo_utils.c @@ -276,16 +276,3 @@ avs_error_t restore_etag(avs_persistence_context_t *ctx, anjay_etag_t **etag) { } #endif // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) && // defined(AVS_COMMONS_STREAM_WITH_FILE) - -avs_coap_udp_tx_params_t g_tx_params; - -void fw_set_coap_tx_params(const avs_coap_udp_tx_params_t *tx_params) { - g_tx_params = *tx_params; -} - -avs_coap_udp_tx_params_t fw_get_coap_tx_params(void *user_ptr, - const char *download_uri) { - (void) user_ptr; - (void) download_uri; - return g_tx_params; -} \ No newline at end of file diff --git a/demo/demo_utils.h b/demo/demo_utils.h index 6ae7210a..6d9488c6 100644 --- a/demo/demo_utils.h +++ b/demo/demo_utils.h @@ -79,9 +79,8 @@ avs_error_t restore_etag(avs_persistence_context_t *ctx, anjay_etag_t **etag); #endif // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) && // defined(AVS_COMMONS_STREAM_WITH_FILE) -void fw_set_coap_tx_params(const avs_coap_udp_tx_params_t *tx_params); - -avs_coap_udp_tx_params_t fw_get_coap_tx_params(void *user_ptr, - const char *download_uri); +typedef struct { + char data[1]; // actually a VLA, but struct cannot be empty +} anjay_demo_allocated_buffer_t; #endif // DEMO_UTILS_H diff --git a/demo/firmware_update.c b/demo/firmware_update.c index f4026609..35fca8d9 100644 --- a/demo/firmware_update.c +++ b/demo/firmware_update.c @@ -307,6 +307,9 @@ static void fw_reset(void *fw_) { fw->package_uri = NULL; maybe_delete_firmware_file(fw); delete_persistence_file(fw); + if (fw->auto_suspend) { + anjay_fw_update_pull_suspend(fw->anjay); + } } static int fw_stream_open(void *fw_, @@ -367,6 +370,9 @@ static int fw_stream_write(void *fw_, const void *data, size_t length) { static int fw_stream_finish(void *fw_) { fw_update_logic_t *fw = (fw_update_logic_t *) fw_; + if (fw->auto_suspend) { + anjay_fw_update_pull_suspend(fw->anjay); + } if (!fw->stream) { demo_log(ERROR, "stream not open"); return -1; @@ -462,6 +468,16 @@ static int fw_get_security_config(void *fw_, return 0; } +static avs_coap_udp_tx_params_t +fw_get_coap_tx_params(void *fw_, const char *download_uri) { + fw_update_logic_t *fw = (fw_update_logic_t *) fw_; + (void) download_uri; + if (fw->auto_suspend) { + anjay_fw_update_pull_reconnect(fw->anjay); + } + return fw->coap_tx_params; +} + static anjay_fw_update_handlers_t FW_UPDATE_HANDLERS = { .stream_open = fw_stream_open, .stream_write = fw_stream_write, @@ -469,8 +485,7 @@ static anjay_fw_update_handlers_t FW_UPDATE_HANDLERS = { .reset = fw_reset, .get_name = fw_get_name, .get_version = fw_get_version, - .perform_upgrade = fw_perform_upgrade, - .get_coap_tx_params = fw_get_coap_tx_params + .perform_upgrade = fw_perform_upgrade }; static bool is_valid_result(int8_t result) { @@ -559,12 +574,11 @@ int firmware_update_install(anjay_t *anjay, const avs_net_security_info_t *security_info, const avs_coap_udp_tx_params_t *tx_params, anjay_fw_update_result_t delayed_result, - bool prefer_same_socket_downloads + bool prefer_same_socket_downloads, #ifdef ANJAY_WITH_SEND - , - bool use_lwm2m_send + bool use_lwm2m_send, #endif // ANJAY_WITH_SEND -) { + bool auto_suspend) { int result = -1; fw->anjay = anjay; @@ -576,8 +590,14 @@ int firmware_update_install(anjay_t *anjay, FW_UPDATE_HANDLERS.get_security_config = NULL; } - if (tx_params) { - fw_set_coap_tx_params(tx_params); + if (tx_params || auto_suspend) { + if (tx_params) { + fw->coap_tx_params = *tx_params; + } else { + fw->coap_tx_params = AVS_COAP_DEFAULT_UDP_TX_PARAMS; + } + fw->auto_suspend = auto_suspend; + FW_UPDATE_HANDLERS.get_coap_tx_params = fw_get_coap_tx_params; } else { FW_UPDATE_HANDLERS.get_coap_tx_params = NULL; } @@ -643,6 +663,9 @@ int firmware_update_install(anjay_t *anjay, } result = anjay_fw_update_install(anjay, &FW_UPDATE_HANDLERS, fw, &state); + if (!result && auto_suspend) { + anjay_fw_update_pull_suspend(anjay); + } exit: avs_free(data.uri); diff --git a/demo/firmware_update.h b/demo/firmware_update.h index 44668d6c..2c29f9e3 100644 --- a/demo/firmware_update.h +++ b/demo/firmware_update.h @@ -32,6 +32,8 @@ typedef struct { const char *persistence_file; FILE *stream; avs_net_security_info_t security_info; + avs_coap_udp_tx_params_t coap_tx_params; + bool auto_suspend; } fw_update_logic_t; int firmware_update_install(anjay_t *anjay, @@ -40,12 +42,11 @@ int firmware_update_install(anjay_t *anjay, const avs_net_security_info_t *security_info, const avs_coap_udp_tx_params_t *tx_params, anjay_fw_update_result_t delayed_result, - bool prefer_same_socket_downloads + bool prefer_same_socket_downloads, #ifdef ANJAY_WITH_SEND - , - bool use_lwm2m_send + bool use_lwm2m_send, #endif // ANJAY_WITH_SEND -); + bool auto_suspend); void firmware_update_destroy(fw_update_logic_t *fw_update); diff --git a/demo/objects.h b/demo/objects.h index 3e079807..8023a67c 100644 --- a/demo/objects.h +++ b/demo/objects.h @@ -20,7 +20,12 @@ #include #include -#include + +#ifdef WITH_DEMO_USE_STANDALONE_OBJECTS +# include "../standalone/server/standalone_server.h" +#else // WITH_DEMO_USE_STANDALONE_OBJECTS +# include +#endif // WITH_DEMO_USE_STANDALONE_OBJECTS typedef struct anjay_demo_struct anjay_demo_t; diff --git a/demo/objects/binary_app_data_container.c b/demo/objects/binary_app_data_container.c index bb3f1ef3..c3e043b1 100644 --- a/demo/objects/binary_app_data_container.c +++ b/demo/objects/binary_app_data_container.c @@ -131,7 +131,6 @@ static int instance_create(anjay_t *anjay, anjay_iid_t iid) { (void) anjay; binary_app_data_container_t *obj = get_obj(obj_ptr); - assert(obj); AVS_LIST(binary_app_data_container_instance_t) created = AVS_LIST_NEW_ELEMENT(binary_app_data_container_instance_t); @@ -161,7 +160,6 @@ static int instance_remove(anjay_t *anjay, anjay_iid_t iid) { (void) anjay; binary_app_data_container_t *obj = get_obj(obj_ptr); - assert(obj); AVS_LIST(binary_app_data_container_instance_t) *it; AVS_LIST_FOREACH_PTR(it, &obj->instances) { @@ -184,7 +182,6 @@ static int instance_reset(anjay_t *anjay, (void) anjay; binary_app_data_container_t *obj = get_obj(obj_ptr); - assert(obj); binary_app_data_container_instance_t *inst = find_instance(obj, iid); assert(inst); @@ -213,7 +210,6 @@ static int resource_read(anjay_t *anjay, (void) anjay; binary_app_data_container_t *obj = get_obj(obj_ptr); - assert(obj); binary_app_data_container_instance_t *inst = find_instance(obj, iid); assert(inst); @@ -270,7 +266,6 @@ static int resource_write(anjay_t *anjay, (void) anjay; binary_app_data_container_t *obj = get_obj(obj_ptr); - assert(obj); binary_app_data_container_instance_t *inst = find_instance(obj, iid); assert(inst); @@ -320,7 +315,6 @@ static int resource_reset(anjay_t *anjay, (void) anjay; binary_app_data_container_t *obj = get_obj(obj_ptr); - assert(obj); binary_app_data_container_instance_t *inst = find_instance(obj, iid); assert(inst); @@ -343,7 +337,6 @@ static int list_resource_instances(anjay_t *anjay, (void) ctx; binary_app_data_container_t *obj = get_obj(obj_ptr); - assert(obj); binary_app_data_container_instance_t *inst = find_instance(obj, iid); assert(inst); @@ -499,7 +492,6 @@ int binary_app_data_container_write(anjay_t *anjay, } binary_app_data_container_t *obj = get_obj(def); - assert(obj); binary_app_data_container_instance_t *inst = find_instance(obj, iid); if (!inst) { demo_log(ERROR, "No such instance: %" PRIu16, iid); diff --git a/demo/objects/event_log.c b/demo/objects/event_log.c index d1f74fb7..20135a65 100644 --- a/demo/objects/event_log.c +++ b/demo/objects/event_log.c @@ -108,7 +108,6 @@ static int instance_reset(anjay_t *anjay, (void) iid; event_log_t *obj = get_obj(obj_ptr); - assert(obj); assert(iid == 0); obj->log_running = false; @@ -147,7 +146,6 @@ static int resource_read(anjay_t *anjay, (void) riid; event_log_t *obj = get_obj(obj_ptr); - assert(obj); assert(iid == 0); switch (rid) { @@ -181,7 +179,6 @@ static int resource_write(anjay_t *anjay, (void) riid; event_log_t *obj = get_obj(obj_ptr); - assert(obj); assert(iid == 0); switch (rid) { @@ -331,7 +328,6 @@ static int resource_execute(anjay_t *anjay, (void) iid; event_log_t *obj = get_obj(obj_ptr); - assert(obj); assert(iid == 0); switch (rid) { diff --git a/demo/objects/portfolio.c b/demo/objects/portfolio.c index 29202230..a97db523 100644 --- a/demo/objects/portfolio.c +++ b/demo/objects/portfolio.c @@ -133,7 +133,6 @@ static int instance_create(anjay_t *anjay, anjay_iid_t iid) { (void) anjay; portfolio_t *obj = get_obj(obj_ptr); - assert(obj); AVS_LIST(portfolio_instance_t) created = AVS_LIST_NEW_ELEMENT(portfolio_instance_t); @@ -159,7 +158,6 @@ static int instance_remove(anjay_t *anjay, anjay_iid_t iid) { (void) anjay; portfolio_t *obj = get_obj(obj_ptr); - assert(obj); AVS_LIST(portfolio_instance_t) *it; AVS_LIST_FOREACH_PTR(it, &obj->instances) { @@ -209,7 +207,6 @@ static int resource_read(anjay_t *anjay, (void) anjay; portfolio_t *obj = get_obj(obj_ptr); - assert(obj); portfolio_instance_t *inst = find_instance(obj, iid); assert(inst); @@ -233,7 +230,6 @@ static int resource_write(anjay_t *anjay, (void) anjay; portfolio_t *obj = get_obj(obj_ptr); - assert(obj); portfolio_instance_t *inst = find_instance(obj, iid); assert(inst); @@ -265,7 +261,6 @@ static int resource_reset(anjay_t *anjay, (void) rid; portfolio_t *obj = get_obj(obj_ptr); - assert(obj); portfolio_instance_t *inst = find_instance(obj, iid); assert(inst); @@ -282,7 +277,6 @@ static int list_resource_instances(anjay_t *anjay, (void) anjay; portfolio_t *obj = get_obj(obj_ptr); - assert(obj); portfolio_instance_t *inst = find_instance(obj, iid); assert(inst); @@ -306,7 +300,6 @@ static int transaction_begin(anjay_t *anjay, const anjay_dm_object_def_t *const *obj_ptr) { (void) anjay; portfolio_t *obj = get_obj(obj_ptr); - assert(obj); assert(!obj->backup); obj->backup = AVS_LIST_SIMPLE_CLONE(obj->instances); if (!obj->backup && obj->instances) { @@ -319,7 +312,6 @@ static int transaction_commit(anjay_t *anjay, const anjay_dm_object_def_t *const *obj_ptr) { (void) anjay; portfolio_t *obj = get_obj(obj_ptr); - assert(obj); AVS_LIST_CLEAR(&obj->backup); return 0; } @@ -328,7 +320,6 @@ static int transaction_rollback(anjay_t *anjay, const anjay_dm_object_def_t *const *obj_ptr) { (void) anjay; portfolio_t *obj = get_obj(obj_ptr); - assert(obj); AVS_LIST_CLEAR(&obj->instances); obj->instances = obj->backup; obj->backup = NULL; diff --git a/deps/avs_coap/CMakeLists.txt b/deps/avs_coap/CMakeLists.txt index ca5cd3c6..60b58f20 100644 --- a/deps/avs_coap/CMakeLists.txt +++ b/deps/avs_coap/CMakeLists.txt @@ -131,6 +131,7 @@ set(SOURCES src/options/avs_coap_options.h src/udp/avs_coap_udp_ctx.c + src/udp/avs_coap_udp_ctx.h src/udp/avs_coap_udp_header.h src/udp/avs_coap_udp_msg.h src/udp/avs_coap_udp_msg.c @@ -195,6 +196,7 @@ if(WITH_TEST) set(TEST_SOURCES tests/tcp/async_client.c tests/tcp/async_server.c + tests/tcp/csm.c tests/tcp/ctx.c tests/tcp/header.c tests/tcp/payload_escaper.c diff --git a/deps/avs_coap/src/async/avs_coap_async_server.c b/deps/avs_coap/src/async/avs_coap_async_server.c index e20ed583..7165b52d 100644 --- a/deps/avs_coap/src/async/avs_coap_async_server.c +++ b/deps/avs_coap/src/async/avs_coap_async_server.c @@ -273,11 +273,6 @@ send_result_handler(avs_coap_ctx_t *ctx, return AVS_COAP_RESPONSE_ACCEPTED; } - avs_coap_server_exchange_data_t *server = &exchange->by_type.server; - AVS_ASSERT(server->delivery_handler, - "send_result_handler called for an exchange without " - "user-defined delivery handler; this should not happen"); - switch (send_result) { case AVS_COAP_SEND_RESULT_PARTIAL_CONTENT: case AVS_COAP_SEND_RESULT_OK: @@ -296,12 +291,22 @@ send_result_handler(avs_coap_ctx_t *ctx, } #endif // WITH_AVS_COAP_BLOCK + avs_coap_server_exchange_data_t *server = &exchange->by_type.server; + AVS_ASSERT(server->delivery_handler, + "send_result_handler called for an exchange without " + "user-defined delivery handler; this should not happen"); + + uint8_t code = exchange->code; + avs_coap_token_t token = exchange->token; + + server->delivery_handler(ctx, fail_err, server->delivery_handler_arg); + if (avs_is_ok(fail_err)) { cancel_notification_on_error(ctx, (avs_coap_observe_id_t) { - .token = exchange->token + .token = token }, - exchange->code); + code); } #ifdef WITH_AVS_COAP_OBSERVE else if (fail_err.category == AVS_COAP_ERR_CATEGORY @@ -310,19 +315,13 @@ send_result_handler(avs_coap_ctx_t *ctx, // ends in a timeout, the client "is considered no longer interested in // the resource and is removed by the server from the list of observers" avs_coap_observe_cancel(ctx, (avs_coap_observe_id_t) { - .token = exchange->token + .token = token }); } #endif // WITH_AVS_COAP_OBSERVE - // exchange may have been canceled by user handler - if (!_avs_coap_find_server_exchange_ptr_by_id(ctx, exchange_id)) { - return AVS_COAP_RESPONSE_ACCEPTED; - } - - server->delivery_handler(ctx, fail_err, server->delivery_handler_arg); - - // delivery status handler might have canceled the exchange as well + // delivery status handler or observe cancel handler + // might have canceled the exchange AVS_LIST(avs_coap_exchange_t) *exchange_ptr = _avs_coap_find_server_exchange_ptr_by_id(ctx, exchange_id); if (!exchange_ptr) { diff --git a/deps/avs_coap/src/tcp/avs_coap_tcp_ctx.c b/deps/avs_coap/src/tcp/avs_coap_tcp_ctx.c index b9f268a9..61bad138 100644 --- a/deps/avs_coap/src/tcp/avs_coap_tcp_ctx.c +++ b/deps/avs_coap/src/tcp/avs_coap_tcp_ctx.c @@ -152,6 +152,18 @@ static avs_error_t handle_cached_msg(avs_coap_tcp_ctx_t *ctx, AVS_COAP_TOKEN_HEX(&ctx->cached_msg.content.token), (unsigned) ctx->cached_msg.content.payload_size); + if (_avs_coap_code_is_signaling_message(code) + && (!avs_time_monotonic_valid(ctx->peer_csm.recv_deadline) + || msg->content.code == AVS_COAP_CODE_CSM)) { + return _avs_coap_tcp_handle_signaling_message(ctx, &ctx->peer_csm, + &msg->content); + } + + if (avs_time_monotonic_valid(ctx->peer_csm.recv_deadline)) { + LOG(DEBUG, _("CSM not received as the first message on connection")); + return _avs_coap_err(AVS_COAP_ERR_TCP_CSM_NOT_RECEIVED); + } + if (avs_coap_code_is_request(code)) { if (out_request && !msg->ignore_request) { AVS_ASSERT(msg->content.payload_offset + msg->content.payload_size @@ -163,9 +175,6 @@ static avs_error_t handle_cached_msg(avs_coap_tcp_ctx_t *ctx, } else if (avs_coap_code_is_response(code)) { handle_response(ctx, msg); return AVS_OK; - } else if (_avs_coap_code_is_signaling_message(code)) { - return _avs_coap_tcp_handle_signaling_message(ctx, &ctx->peer_csm, - &msg->content); } else if (code == AVS_COAP_CODE_EMPTY) { // "Empty messages (Code 0.00) can always be sent and MUST be ignored by // the recipient. This provides a basic keepalive function that can @@ -248,10 +257,12 @@ static void finish_message_handling(avs_coap_tcp_ctx_t *ctx) { } static inline void send_abort(avs_coap_tcp_ctx_t *ctx) { - ctx->aborted = true; - (void) send_simple_msg(ctx, AVS_COAP_CODE_ABORT, - &ctx->cached_msg.content.token, - GET_DIAGNOSTIC_MESSAGE(ctx)); + if (!ctx->aborted) { + ctx->aborted = true; + (void) send_simple_msg(ctx, AVS_COAP_CODE_ABORT, + &ctx->cached_msg.content.token, + GET_DIAGNOSTIC_MESSAGE(ctx)); + } } static void coap_tcp_cleanup(avs_coap_ctx_t *ctx_) { @@ -338,13 +349,13 @@ coap_tcp_send_message(avs_coap_ctx_t *ctx_, AVS_LIST(avs_coap_tcp_pending_request_t) *req = NULL; if (send_result_handler && avs_coap_code_is_request(msg->code)) { - req = _avs_coap_tcp_create_pending_request(ctx, - &ctx->pending_requests, - &msg->token, - send_result_handler, - send_result_handler_arg); - if (!req) { - return avs_errno(AVS_ENOMEM); + avs_error_t err = + _avs_coap_tcp_create_pending_request(ctx, &req, &msg->token, + send_result_handler, + send_result_handler_arg); + assert(avs_is_ok(err) == !!req); + if (avs_is_err(err)) { + return err; } } else if (avs_coap_code_is_response(msg->code)) { // Response may be sent before receiving the entire request, don't pass @@ -791,63 +802,30 @@ coap_tcp_receive_message(avs_coap_ctx_t *ctx_, return avs_is_ok(err) ? restore_err : err; } -static avs_time_monotonic_t coap_tcp_on_timeout(avs_coap_ctx_t *ctx_) { - return _avs_coap_tcp_fail_expired_pending_requests( - (avs_coap_tcp_ctx_t *) ctx_); -} - -static avs_error_t receive_csm(avs_coap_tcp_ctx_t *ctx) { - const avs_time_monotonic_t start = avs_time_monotonic_now(); - - avs_time_duration_t timeout; - avs_error_t err = get_recv_timeout(ctx->base.socket, &timeout); - if (avs_is_err(err)) { - return err; +static void update_timeout(avs_time_monotonic_t *result_ptr, + avs_time_monotonic_t candidate) { + if (!avs_time_monotonic_valid(*result_ptr) + || avs_time_monotonic_before(candidate, *result_ptr)) { + *result_ptr = candidate; } +} - do { - const avs_time_monotonic_t now = avs_time_monotonic_now(); - const avs_time_duration_t time_passed = - avs_time_monotonic_diff(now, start); - const avs_time_duration_t new_timeout = - avs_time_duration_diff(ctx->request_timeout, time_passed); - if (avs_time_duration_less(new_timeout, AVS_TIME_DURATION_ZERO)) { - LOG(ERROR, _("timeout reached while receiving CSM")); - err = _avs_coap_err(AVS_COAP_ERR_TIMEOUT); - break; - } - - if (avs_is_err( - (err = set_recv_timeout(ctx->base.socket, new_timeout)))) { - break; +static avs_time_monotonic_t coap_tcp_on_timeout(avs_coap_ctx_t *ctx_) { + avs_coap_tcp_ctx_t *ctx = (avs_coap_tcp_ctx_t *) ctx_; + avs_time_monotonic_t result = AVS_TIME_MONOTONIC_INVALID; + if (avs_coap_ctx_has_socket(ctx_) + && avs_time_monotonic_valid(ctx->peer_csm.recv_deadline)) { + if (avs_time_monotonic_before(avs_time_monotonic_now(), + ctx->peer_csm.recv_deadline)) { + update_timeout(&result, ctx->peer_csm.recv_deadline); + } else { + LOG(ERROR, _("CSM not received within timeout")); + SET_DIAGNOSTIC_MESSAGE(ctx, "CSM not received within timeout"); + send_abort(ctx); } - - // Used to receive possible chunks of payload, which are ignored anyway. - uint8_t temp[16]; - err = receive_and_handle_message(ctx, temp, sizeof(temp), NULL); - } while (avs_is_ok(err) && ctx->cached_msg.remaining_bytes); - - if (avs_is_err(err)) { - return err; - } else if (!ctx->peer_csm.received) { - return _avs_coap_err(AVS_COAP_ERR_TCP_CSM_NOT_RECEIVED); } - - AVS_ASSERT(ctx->cached_msg.remaining_bytes == 0 - && ctx->cached_msg.remaining_header_bytes == 0, - "bug: message seems to be unfinished after handling CSM"); - finish_message_handling(ctx); - AVS_ASSERT(avs_buffer_data_size(ctx->opt_cache.buffer) == 0, - "bug: data in buffer after finishing message handling"); - AVS_ASSERT(ctx->opt_cache.state - == AVS_COAP_TCP_OPT_CACHE_STATE_RECEIVING_HEADER, - "bug: invalid state after handling CSM"); - - if (avs_is_err((err = set_recv_timeout(ctx->base.socket, timeout)))) { - return err; - } - - return AVS_OK; + update_timeout(&result, _avs_coap_tcp_fail_expired_pending_requests(ctx)); + return result; } static avs_error_t send_csm(avs_coap_tcp_ctx_t *ctx) { @@ -889,12 +867,17 @@ static avs_error_t coap_tcp_setsock(avs_coap_ctx_t *ctx_, return err; } - if (avs_is_err((err = send_csm(ctx))) - || avs_is_err((err = receive_csm(ctx)))) { - SET_DIAGNOSTIC_MESSAGE(ctx, "failed to send/receive CSM"); + ctx->peer_csm.recv_deadline = AVS_TIME_MONOTONIC_INVALID; + err = avs_errno(AVS_EOVERFLOW); + if (_avs_coap_tcp_update_recv_deadline(ctx, &ctx->peer_csm.recv_deadline) + || !avs_time_monotonic_valid(ctx->peer_csm.recv_deadline) + || avs_is_err((err = send_csm(ctx)))) { + SET_DIAGNOSTIC_MESSAGE(ctx, "failed to send CSM"); send_abort(ctx); return err; } + _avs_coap_reschedule_retry_or_request_expired_job( + ctx_, ctx->peer_csm.recv_deadline); return AVS_OK; } @@ -968,6 +951,7 @@ avs_coap_ctx_t *avs_coap_tcp_ctx_create(avs_sched_t *sched, out_buffer, sched, prng_ctx); ctx->vtable = &COAP_TCP_VTABLE; + ctx->peer_csm.recv_deadline = avs_time_monotonic_now(); ctx->peer_csm.max_message_size = CSM_MAX_MESSAGE_SIZE_BASE_VALUE; ctx->request_timeout = request_timeout; diff --git a/deps/avs_coap/src/tcp/avs_coap_tcp_ctx.h b/deps/avs_coap/src/tcp/avs_coap_tcp_ctx.h index 36c06aac..4ad2743f 100644 --- a/deps/avs_coap/src/tcp/avs_coap_tcp_ctx.h +++ b/deps/avs_coap/src/tcp/avs_coap_tcp_ctx.h @@ -61,6 +61,14 @@ typedef struct avs_coap_tcp_ctx_struct { avs_error_t _avs_coap_tcp_send_msg(avs_coap_tcp_ctx_t *ctx, const avs_coap_borrowed_msg_t *msg); +static inline int +_avs_coap_tcp_update_recv_deadline(avs_coap_tcp_ctx_t *ctx, + avs_time_monotonic_t *inout_deadline) { + *inout_deadline = avs_time_monotonic_add(avs_time_monotonic_now(), + ctx->request_timeout); + return 0; +} + VISIBILITY_PRIVATE_HEADER_END #endif // AVS_COAP_SRC_TCP_CTX_H diff --git a/deps/avs_coap/src/tcp/avs_coap_tcp_pending_requests.c b/deps/avs_coap/src/tcp/avs_coap_tcp_pending_requests.c index 1455c166..ca8883a7 100644 --- a/deps/avs_coap/src/tcp/avs_coap_tcp_pending_requests.c +++ b/deps/avs_coap/src/tcp/avs_coap_tcp_pending_requests.c @@ -60,7 +60,7 @@ is_list_ordered_by_expire_time(AVS_LIST(avs_coap_tcp_pending_request_t) list) { return true; } -static void +static AVS_LIST(avs_coap_tcp_pending_request_t) * insert_pending_request(AVS_LIST(avs_coap_tcp_pending_request_t) *list_ptr, AVS_LIST(avs_coap_tcp_pending_request_t) req) { AVS_LIST_ITERATE_PTR(list_ptr) { @@ -71,8 +71,10 @@ insert_pending_request(AVS_LIST(avs_coap_tcp_pending_request_t) *list_ptr, } AVS_LIST_INSERT(list_ptr, req); + assert(*list_ptr == req); AVS_ASSERT(is_list_ordered_by_expire_time(*list_ptr), "pending request list must be ordered by expire_time"); + return list_ptr; } static avs_coap_tcp_pending_request_t *detach_pending_request( @@ -162,12 +164,16 @@ refresh_timeout(avs_coap_tcp_ctx_t *ctx, assert(*req_ptr); assert(avs_time_monotonic_valid((*req_ptr)->expire_time)); - (*req_ptr)->expire_time = avs_time_monotonic_add(avs_time_monotonic_now(), - ctx->request_timeout); - insert_pending_request(&ctx->pending_requests, AVS_LIST_DETACH(req_ptr)); - - _avs_coap_reschedule_retry_or_request_expired_job((avs_coap_ctx_t *) ctx, - (*req_ptr)->expire_time); + if (_avs_coap_tcp_update_recv_deadline(ctx, &(*req_ptr)->expire_time)) { + finish_pending_request_with_error(ctx, req_ptr, + AVS_COAP_SEND_RESULT_FAIL, + avs_errno(AVS_UNKNOWN_ERROR)); + } else { + insert_pending_request(&ctx->pending_requests, + AVS_LIST_DETACH(req_ptr)); + _avs_coap_reschedule_retry_or_request_expired_job( + (avs_coap_ctx_t *) ctx, (*req_ptr)->expire_time); + } } void _avs_coap_tcp_handle_pending_request( @@ -218,33 +224,40 @@ void _avs_coap_tcp_handle_pending_request( AVS_UNREACHABLE("invalid enum value"); } -AVS_LIST(avs_coap_tcp_pending_request_t) *_avs_coap_tcp_create_pending_request( +avs_error_t _avs_coap_tcp_create_pending_request( avs_coap_tcp_ctx_t *ctx, - AVS_LIST(avs_coap_tcp_pending_request_t) *pending_requests, + AVS_LIST(avs_coap_tcp_pending_request_t) **out_request, const avs_coap_token_t *token, avs_coap_send_result_handler_t *handler, void *handler_arg) { + assert(out_request && !*out_request); AVS_LIST(avs_coap_tcp_pending_request_t) req = AVS_LIST_NEW_ELEMENT(avs_coap_tcp_pending_request_t); - if (req) { - *req = (avs_coap_tcp_pending_request_t) { - .handler = (avs_coap_tcp_response_handler_t) { - .handle_result = handler, - .handle_result_arg = handler_arg - }, - .token = *token, - .expire_time = avs_time_monotonic_add(avs_time_monotonic_now(), - ctx->request_timeout) - }; - insert_pending_request(pending_requests, req); - _avs_coap_reschedule_retry_or_request_expired_job( - (avs_coap_ctx_t *) ctx, req->expire_time); - } else { + if (!req) { LOG(DEBUG, _("failed to create pending request - out of memory")); - return NULL; + return avs_errno(AVS_ENOMEM); } - return pending_requests; + *req = (avs_coap_tcp_pending_request_t) { + .handler = (avs_coap_tcp_response_handler_t) { + .handle_result = handler, + .handle_result_arg = handler_arg + }, + .token = *token, + .expire_time = AVS_TIME_MONOTONIC_INVALID + }; + if (_avs_coap_tcp_update_recv_deadline(ctx, &req->expire_time)) { + AVS_LIST_DELETE(&req); + LOG(DEBUG, _("failed to create pending request - cannot calculate " + "receive deadline")); + return avs_errno(AVS_UNKNOWN_ERROR); + } + + *out_request = insert_pending_request(&ctx->pending_requests, req); + _avs_coap_reschedule_retry_or_request_expired_job((avs_coap_ctx_t *) ctx, + req->expire_time); + + return AVS_OK; } void _avs_coap_tcp_remove_pending_request( diff --git a/deps/avs_coap/src/tcp/avs_coap_tcp_pending_requests.h b/deps/avs_coap/src/tcp/avs_coap_tcp_pending_requests.h index 4d200c9a..b70fc170 100644 --- a/deps/avs_coap/src/tcp/avs_coap_tcp_pending_requests.h +++ b/deps/avs_coap/src/tcp/avs_coap_tcp_pending_requests.h @@ -33,9 +33,9 @@ typedef struct { void *handle_result_arg; } avs_coap_tcp_response_handler_t; -AVS_LIST(avs_coap_tcp_pending_request_t) *_avs_coap_tcp_create_pending_request( +avs_error_t _avs_coap_tcp_create_pending_request( struct avs_coap_tcp_ctx_struct *ctx, - AVS_LIST(avs_coap_tcp_pending_request_t) *pending_requests, + AVS_LIST(avs_coap_tcp_pending_request_t) **out_request, const avs_coap_token_t *token, avs_coap_send_result_handler_t *handler, void *handler_arg); diff --git a/deps/avs_coap/src/tcp/avs_coap_tcp_signaling.c b/deps/avs_coap/src/tcp/avs_coap_tcp_signaling.c index 3cd9914b..b0bc1d7c 100644 --- a/deps/avs_coap/src/tcp/avs_coap_tcp_signaling.c +++ b/deps/avs_coap/src/tcp/avs_coap_tcp_signaling.c @@ -29,7 +29,7 @@ VISIBILITY_SOURCE_BEGIN static avs_error_t handle_csm(avs_coap_tcp_csm_t *csm, const avs_coap_borrowed_msg_t *msg) { - csm->received = true; + csm->recv_deadline = AVS_TIME_MONOTONIC_INVALID; bool size_updated = false; bool block_updated = false; const avs_coap_options_t *opts = &msg->options; diff --git a/deps/avs_coap/src/tcp/avs_coap_tcp_signaling.h b/deps/avs_coap/src/tcp/avs_coap_tcp_signaling.h index 9dbf5103..a976d252 100644 --- a/deps/avs_coap/src/tcp/avs_coap_tcp_signaling.h +++ b/deps/avs_coap/src/tcp/avs_coap_tcp_signaling.h @@ -20,7 +20,8 @@ VISIBILITY_PRIVATE_HEADER_BEGIN struct avs_coap_tcp_ctx_struct; typedef struct { - bool received; + // if recv_deadline is invalid, it means that peer CSM has been received. + avs_time_monotonic_t recv_deadline; // max_message_size is a maximum single message size (starting from first // byte of the header and ending at the end of the message payload) which // peer can receive. diff --git a/deps/avs_coap/src/udp/avs_coap_udp_ctx.c b/deps/avs_coap/src/udp/avs_coap_udp_ctx.c index f5b19929..e9e53bbc 100644 --- a/deps/avs_coap/src/udp/avs_coap_udp_ctx.c +++ b/deps/avs_coap/src/udp/avs_coap_udp_ctx.c @@ -26,15 +26,13 @@ # include "avs_coap_ctx_vtable.h" # include "options/avs_coap_option.h" -# include "udp/avs_coap_udp_header.h" -# include "udp/avs_coap_udp_msg.h" -# include "udp/avs_coap_udp_msg_cache.h" - # define MODULE_NAME coap_udp # include +# include "udp/avs_coap_udp_ctx.h" +# include "udp/avs_coap_udp_msg_cache.h" + # include "avs_coap_common_utils.h" -# include "avs_coap_ctx.h" # include "options/avs_coap_options.h" # include "udp/avs_coap_udp_tx_params.h" @@ -50,74 +48,7 @@ void _avs_unit_mock_constructor_avs_coap_udp_initial_retry_state(void) { } # endif // AVS_UNIT_TESTING -/** - * Owning wrapper around an unconfirmed outgoing CoAP/UDP message. - * - * List of CoAP/UDP exchanges is kept sorted by (hold, next_retransmit) tuple: - * - * - up to NSTART first entries are "not held", i.e. are currently being - * retransmitted, - * - * - if more than NSTART exchanges were created, the rest is "held", - * i.e. not transmitted at all to honor NSTART defined by RFC7252. - * - * Whenever an exchange is retransmitted, next_retransmit is updated to the - * time of a next retransmission, and the exchange entry moved to appropriate - * place in the exchange list to keep described ordering. - */ -typedef struct { - /** Handler to call when context is done with the message */ - avs_coap_send_result_handler_t *send_result_handler; - /** Opaque argument to pass to send_result_handler */ - void *send_result_handler_arg; - - avs_coap_retry_state_t retry_state; - - /** If true, exchange retransmissions are disabled due to NSTART */ - bool hold; - - /** Time at which this packet has to be retransmitted next time. */ - avs_time_monotonic_t next_retransmit; - - /** CoAP message view. Points to @ref avs_coap_udp_exchange_t#packet . */ - avs_coap_udp_msg_t msg; - - /** Number of initialized bytes in @ref avs_coap_udp_exchange_t#packet . */ - size_t packet_size; - - /** Serialized packet data. */ - uint8_t packet[]; -} avs_coap_udp_unconfirmed_msg_t; - # ifdef WITH_AVS_COAP_OBSERVE -typedef struct { - uint16_t msg_id; - avs_coap_token_t token; -} avs_coap_udp_sent_notify_t; - -/** - * Fixed-size cache with queue semantics used to store (message ID, token) - * pairs of recently sent notification messages. - * - * RFC 7641 defines Reset response to sent notification to be a preferred - * method of cancelling an established observation. This cache allows us to - * match incoming Reset messages to established observations so that we can - * cancel them. - * - * Technically, entries in this cache should expire after MAX_TRANSMIT_WAIT - * since the first retransmission, but we keep them around as long as there - * is enough space and we don't try to reuse the same message ID. That means - * some Reset messages may not cancel observations if notifications are - * generated at a high rate, or that Reset messages that come later are still - * handled as valid observe cancellation. - * - * This implementation trades correctness in all cases for simplicity. - */ -typedef struct { - avs_coap_udp_sent_notify_t entries[AVS_COAP_UDP_NOTIFY_CACHE_SIZE]; - size_t size; -} avs_coap_udp_notify_cache_t; - AVS_STATIC_ASSERT(AVS_COAP_UDP_NOTIFY_CACHE_SIZE > 0, notify_cache_must_have_at_least_one_element); @@ -178,39 +109,6 @@ static inline void coap_udp_notify_cache_put(avs_coap_udp_notify_cache_t *cache, } # endif // WITH_AVS_COAP_OBSERVE -typedef struct { - const struct avs_coap_ctx_vtable *vtable; - - avs_coap_base_t base; - - AVS_LIST(avs_coap_udp_unconfirmed_msg_t) unconfirmed_messages; - - avs_net_socket_t *socket; - size_t last_mtu; - size_t forced_incoming_mtu; - avs_coap_udp_tx_params_t tx_params; - - avs_coap_stats_t stats; - - uint16_t last_msg_id; - - /** - * Any Piggybacked response we send MUST echo message ID of received - * request. Its ID/token pair is stored here to ensure that. - */ - struct { - /** true if we're currently processing a request */ - bool exists; - uint16_t msg_id; - avs_coap_token_t token; - } current_request; - - avs_coap_udp_response_cache_t *response_cache; -# ifdef WITH_AVS_COAP_OBSERVE - avs_coap_udp_notify_cache_t notify_cache; -# endif // WITH_AVS_COAP_OBSERVE -} avs_coap_udp_ctx_t; - AVS_STATIC_ASSERT(offsetof(avs_coap_udp_ctx_t, vtable) == 0, vtable_field_must_be_first_in_udp_ctx_t); @@ -411,19 +309,6 @@ static avs_error_t coap_udp_send_serialized_msg(avs_coap_udp_ctx_t *ctx, return err; } -static avs_time_monotonic_t get_first_retransmit_time(avs_coap_udp_ctx_t *ctx) { - avs_coap_retry_state_t initial_state = { - .retry_count = 0, - .recv_timeout = AVS_TIME_DURATION_ZERO - }; - if (avs_is_err(_avs_coap_udp_initial_retry_state( - &ctx->tx_params, ctx->base.prng_ctx, &initial_state))) { - return AVS_TIME_MONOTONIC_INVALID; - } - return avs_time_monotonic_add(avs_time_monotonic_now(), - initial_state.recv_timeout); -} - static AVS_LIST(avs_coap_udp_unconfirmed_msg_t) * find_unconfirmed_insert_ptr(avs_coap_udp_ctx_t *ctx, const avs_coap_udp_unconfirmed_msg_t *new_elem) { @@ -538,13 +423,14 @@ static void resume_next_unconfirmed(avs_coap_udp_ctx_t *ctx) { return; } - avs_time_monotonic_t next_retransmit = get_first_retransmit_time(ctx); + avs_time_monotonic_t next_retransmit = avs_time_monotonic_add( + avs_time_monotonic_now(), + (*unconfirmed_ptr)->retry_state.recv_timeout); if (!avs_time_monotonic_valid(next_retransmit)) { LOG(ERROR, - _("unable to schedule retransmit: get_first_retransmit_time() " - "returned invalid time; either the monotonic clock " - "malfunctioned, UDP tx params are too large to handle or " - "PRNG failed")); + _("unable to schedule retransmit: calculated retransmit time is " + "invalid; either the monotonic clock, UDP tx params are too " + "large to handle or PRNG failed")); // We can't rely on getting valid times for any held job. Fail all // of them immediately. @@ -561,10 +447,9 @@ static void resume_next_unconfirmed(avs_coap_udp_ctx_t *ctx) { // AVS_LIST_SIZE(ctx->unconfirmed_messages) recursive calls . // // Note: this loop may be infinite in the most degenerate case - // where get_first_retransmit_time returns an invalid time **just - // once** (call above) and every response handler calls - // avs_coap_client_send_async_request, adding a new held entry to - // the context. + // where next_retransmit is an invalid time **just once** and every + // response handler calls avs_coap_client_send_async_request, adding + // a new held entry to the context. avs_coap_udp_unconfirmed_msg_t *unconfirmed = AVS_LIST_DETACH(&held_messages); (void) call_send_result_handler( @@ -817,8 +702,7 @@ retransmit_next_message_without_reschedule(avs_coap_udp_ctx_t *ctx) { return; } - if (_avs_coap_udp_all_retries_sent(&unconfirmed->retry_state, - &ctx->tx_params)) { + if (_avs_coap_udp_all_retries_sent(&unconfirmed->retry_state)) { LOG(DEBUG, _("msg ") "%s" _(": MAX_RETRANSMIT reached without response from " "the server"), @@ -830,7 +714,7 @@ retransmit_next_message_without_reschedule(avs_coap_udp_ctx_t *ctx) { return; } - if (_avs_coap_udp_update_retry_state(&unconfirmed->retry_state)) { + if (_avs_coap_udp_update_retry_state(ctx, &unconfirmed->retry_state)) { fail_unconfirmed(ctx, &ctx->unconfirmed_messages, NULL, _avs_coap_err(AVS_COAP_ERR_TIME_INVALID)); return; @@ -838,7 +722,8 @@ retransmit_next_message_without_reschedule(avs_coap_udp_ctx_t *ctx) { LOG(DEBUG, _("msg ") "%s" _(": retry ") "%u/%u", AVS_COAP_TOKEN_HEX(&unconfirmed->msg.token), - unconfirmed->retry_state.retry_count, ctx->tx_params.max_retransmit); + ctx->tx_params.max_retransmit - unconfirmed->retry_state.retries_left, + ctx->tx_params.max_retransmit); avs_error_t err = coap_udp_send_serialized_msg(ctx, &unconfirmed->msg, unconfirmed->packet, @@ -899,9 +784,12 @@ enqueue_unconfirmed(avs_coap_udp_ctx_t *ctx, // use current time for all held jobs to not cause accidental reordering // due to ACK_RANDOM_FACTOR - avs_time_monotonic_t next_retransmit = - unconfirmed->hold ? avs_time_monotonic_now() - : get_first_retransmit_time(ctx); + avs_time_monotonic_t next_retransmit = avs_time_monotonic_now(); + if (!unconfirmed->hold) { + next_retransmit = + avs_time_monotonic_add(next_retransmit, + unconfirmed->retry_state.recv_timeout); + } if (!avs_time_monotonic_valid(unconfirmed->next_retransmit)) { LOG(ERROR, _("unable to enqueue msg: next_retransmit time invalid; " @@ -955,8 +843,7 @@ static avs_error_t create_unconfirmed( avs_error_t err; if (avs_is_err((err = _avs_coap_udp_initial_retry_state( - &ctx->tx_params, ctx->base.prng_ctx, - &unconfirmed_msg->retry_state)))) { + ctx, &unconfirmed_msg->retry_state)))) { LOG(ERROR, _("PRNG failed")); AVS_LIST_CLEAR(&unconfirmed_msg); return err; @@ -1300,7 +1187,7 @@ ack_request(avs_coap_udp_ctx_t *ctx, avs_coap_udp_unconfirmed_msg_t *unconfirmed = AVS_LIST_DETACH(unconfirmed_ptr); // disable further retransmissions - unconfirmed->retry_state.retry_count = UINT_MAX; + unconfirmed->retry_state.retries_left = 0; unconfirmed->next_retransmit = next_retransmit; AVS_LIST_INSERT(find_unconfirmed_insert_ptr(ctx, unconfirmed), unconfirmed); diff --git a/deps/avs_coap/src/udp/avs_coap_udp_ctx.h b/deps/avs_coap/src/udp/avs_coap_udp_ctx.h new file mode 100644 index 00000000..e6e04242 --- /dev/null +++ b/deps/avs_coap/src/udp/avs_coap_udp_ctx.h @@ -0,0 +1,139 @@ +/* + * Copyright 2017-2023 AVSystem + * AVSystem CoAP library + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#ifndef AVS_COAP_SRC_UDP_UDP_CTX_H +#define AVS_COAP_SRC_UDP_UDP_CTX_H + +#include "../avs_coap_ctx.h" +#include "avs_coap_udp_msg.h" + +VISIBILITY_PRIVATE_HEADER_BEGIN + +/** Retry state object used to calculate retransmission timeouts. */ +typedef struct { + /** + * Number of original packet retransmissions left to try. + * + * The value of retries_left shall vary between 0 and MAX_RETRANSMIT + * inclusively. + */ + unsigned retries_left; + /** + * Amount of time to wait for the response (either to an initial packet or a + * retransmitted one). + */ + avs_time_duration_t recv_timeout; +} avs_coap_retry_state_t; + +/** + * Owning wrapper around an unconfirmed outgoing CoAP/UDP message. + * + * List of CoAP/UDP exchanges is kept sorted by (hold, next_retransmit) tuple: + * + * - up to NSTART first entries are "not held", i.e. are currently being + * retransmitted, + * + * - if more than NSTART exchanges were created, the rest is "held", + * i.e. not transmitted at all to honor NSTART defined by RFC7252. + * + * Whenever an exchange is retransmitted, next_retransmit is updated to the + * time of a next retransmission, and the exchange entry moved to appropriate + * place in the exchange list to keep described ordering. + */ +typedef struct { + /** Handler to call when context is done with the message */ + avs_coap_send_result_handler_t *send_result_handler; + /** Opaque argument to pass to send_result_handler */ + void *send_result_handler_arg; + + /** Current state of retransmission timeout calculation. */ + avs_coap_retry_state_t retry_state; + + /** If true, exchange retransmissions are disabled due to NSTART */ + bool hold; + + /** Time at which this packet has to be retransmitted next time. */ + avs_time_monotonic_t next_retransmit; + + /** CoAP message view. Points to @ref avs_coap_udp_exchange_t#packet . */ + avs_coap_udp_msg_t msg; + + /** Number of initialized bytes in @ref avs_coap_udp_exchange_t#packet . */ + size_t packet_size; + + /** Serialized packet data. */ + uint8_t packet[]; +} avs_coap_udp_unconfirmed_msg_t; + +#ifdef WITH_AVS_COAP_OBSERVE +typedef struct { + uint16_t msg_id; + avs_coap_token_t token; +} avs_coap_udp_sent_notify_t; + +/** + * Fixed-size cache with queue semantics used to store (message ID, token) + * pairs of recently sent notification messages. + * + * RFC 7641 defines Reset response to sent notification to be a preferred + * method of cancelling an established observation. This cache allows us to + * match incoming Reset messages to established observations so that we can + * cancel them. + * + * Technically, entries in this cache should expire after MAX_TRANSMIT_WAIT + * since the first retransmission, but we keep them around as long as there + * is enough space and we don't try to reuse the same message ID. That means + * some Reset messages may not cancel observations if notifications are + * generated at a high rate, or that Reset messages that come later are still + * handled as valid observe cancellation. + * + * This implementation trades correctness in all cases for simplicity. + */ +typedef struct { + avs_coap_udp_sent_notify_t entries[AVS_COAP_UDP_NOTIFY_CACHE_SIZE]; + size_t size; +} avs_coap_udp_notify_cache_t; +#endif // WITH_AVS_COAP_OBSERVE + +typedef struct { + const struct avs_coap_ctx_vtable *vtable; + + avs_coap_base_t base; + + AVS_LIST(avs_coap_udp_unconfirmed_msg_t) unconfirmed_messages; + + avs_net_socket_t *socket; + size_t last_mtu; + size_t forced_incoming_mtu; + avs_coap_udp_tx_params_t tx_params; + + avs_coap_stats_t stats; + + uint16_t last_msg_id; + + /** + * Any Piggybacked response we send MUST echo message ID of received + * request. Its ID/token pair is stored here to ensure that. + */ + struct { + /** true if we're currently processing a request */ + bool exists; + uint16_t msg_id; + avs_coap_token_t token; + } current_request; + + avs_coap_udp_response_cache_t *response_cache; +#ifdef WITH_AVS_COAP_OBSERVE + avs_coap_udp_notify_cache_t notify_cache; +#endif // WITH_AVS_COAP_OBSERVE +} avs_coap_udp_ctx_t; + +VISIBILITY_PRIVATE_HEADER_END + +#endif // AVS_COAP_SRC_UDP_UDP_CTX_H diff --git a/deps/avs_coap/src/udp/avs_coap_udp_tx_params.h b/deps/avs_coap/src/udp/avs_coap_udp_tx_params.h index ef628da3..7e403021 100644 --- a/deps/avs_coap/src/udp/avs_coap_udp_tx_params.h +++ b/deps/avs_coap/src/udp/avs_coap_udp_tx_params.h @@ -16,7 +16,7 @@ #include -#include "../avs_coap_ctx.h" +#include "avs_coap_udp_ctx.h" VISIBILITY_PRIVATE_HEADER_BEGIN @@ -32,41 +32,22 @@ _avs_coap_udp_max_transmit_span(const avs_coap_udp_tx_params_t *tx_params) { * tx_params->ack_random_factor); } -/** Retry state object used to calculate retransmission timeouts. */ -typedef struct { - /** - * Number of retransmissions of the original packet already sent. - * - * If zero, the avs_coap_retry_state_t::recv_timeout indicates how long - * should one wait for the response before attempting a retransmission. - * - * The value of retry_count shall vary between 0 and MAX_RETRANSMIT - * inclusively. - */ - unsigned retry_count; - /** - * Amount of time to wait for the response (either to an initial packet or a - * retransmitted one). - */ - avs_time_duration_t recv_timeout; -} avs_coap_retry_state_t; - static inline avs_error_t -_avs_coap_udp_initial_retry_state(const avs_coap_udp_tx_params_t *tx_params, - avs_crypto_prng_ctx_t *prng_ctx, +_avs_coap_udp_initial_retry_state(avs_coap_udp_ctx_t *ctx, avs_coap_retry_state_t *out_retry_state) { uint32_t random; - if (avs_crypto_prng_bytes( - prng_ctx, (unsigned char *) &random, sizeof(random))) { + if (avs_crypto_prng_bytes(ctx->base.prng_ctx, + (unsigned char *) &random, + sizeof(random))) { return _avs_coap_err(AVS_COAP_ERR_PRNG_FAIL); } double random_factor = ((double) random / (double) UINT32_MAX) - * (tx_params->ack_random_factor - 1.0); + * (ctx->tx_params.ack_random_factor - 1.0); *out_retry_state = (avs_coap_retry_state_t) { - .retry_count = 0, - .recv_timeout = avs_time_duration_fmul(tx_params->ack_timeout, + .retries_left = ctx->tx_params.max_retransmit, + .recv_timeout = avs_time_duration_fmul(ctx->tx_params.ack_timeout, 1.0 + random_factor) }; return AVS_OK; @@ -77,12 +58,16 @@ _avs_coap_udp_initial_retry_state(const avs_coap_udp_tx_params_t *tx_params, #endif // AVS_UNIT_TESTING static inline int -_avs_coap_udp_update_retry_state(avs_coap_retry_state_t *retry_state) { +_avs_coap_udp_update_retry_state(avs_coap_udp_ctx_t *ctx, + avs_coap_retry_state_t *retry_state) { retry_state->recv_timeout = avs_time_duration_mul(retry_state->recv_timeout, 2); - ++retry_state->retry_count; - - return avs_time_duration_valid(retry_state->recv_timeout) ? 0 : -1; + --retry_state->retries_left; + if (!avs_time_duration_valid(retry_state->recv_timeout)) { + return -1; + } + (void) ctx; + return 0; } /** @@ -90,9 +75,8 @@ _avs_coap_udp_update_retry_state(avs_coap_retry_state_t *retry_state) { * sent, false otherwise. */ static inline bool -_avs_coap_udp_all_retries_sent(const avs_coap_retry_state_t *retry_state, - const avs_coap_udp_tx_params_t *tx_params) { - return retry_state->retry_count >= tx_params->max_retransmit; +_avs_coap_udp_all_retries_sent(const avs_coap_retry_state_t *retry_state) { + return retry_state->retries_left <= 0; } VISIBILITY_PRIVATE_HEADER_END diff --git a/deps/avs_coap/tests/tcp/csm.c b/deps/avs_coap/tests/tcp/csm.c new file mode 100644 index 00000000..a351f704 --- /dev/null +++ b/deps/avs_coap/tests/tcp/csm.c @@ -0,0 +1,246 @@ +/* + * Copyright 2017-2023 AVSystem + * AVSystem CoAP library + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#include + +#if defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_TCP) + +# define MODULE_NAME test +# include + +# include "./helper_functions.h" + +typedef struct { + avs_coap_send_result_t result; + avs_error_t err; + bool has_response; + avs_coap_borrowed_msg_t response; +} test_handler_expected_response_t; + +typedef AVS_LIST( + test_handler_expected_response_t) test_handler_expect_responses_list_t; + +typedef struct { + test_handler_expect_responses_list_t expect_responses_list; +} response_handler_args_t; + +static avs_coap_send_result_handler_result_t +test_response_handler(avs_coap_ctx_t *ctx, + avs_coap_send_result_t result, + avs_error_t err, + const avs_coap_borrowed_msg_t *response, + void *arg) { + (void) ctx; + response_handler_args_t *args = (response_handler_args_t *) arg; + + test_handler_expect_responses_list_t *expect_list = + &args->expect_responses_list; + ASSERT_NOT_NULL(expect_list); + ASSERT_NOT_NULL(*expect_list); + const test_handler_expected_response_t *expected = *expect_list; + + ASSERT_EQ(result, expected->result); + if (avs_is_ok(expected->err)) { + ASSERT_OK(err); + } else { + ASSERT_EQ(err.category, expected->err.category); + ASSERT_EQ(err.code, expected->err.code); + } + + if (expected->has_response) { + const avs_coap_borrowed_msg_t *actual_res = response; + const avs_coap_borrowed_msg_t *expected_res = &expected->response; + + ASSERT_EQ(actual_res->code, expected_res->code); + ASSERT_TRUE( + avs_coap_token_equal(&actual_res->token, &expected_res->token)); + if (result != AVS_COAP_SEND_RESULT_FAIL) { + ASSERT_EQ(actual_res->options.size, expected_res->options.size); + ASSERT_EQ_BYTES_SIZED(actual_res->options.begin, + expected_res->options.begin, + actual_res->options.size); + ASSERT_EQ(actual_res->payload_size, expected_res->payload_size); + ASSERT_EQ_BYTES_SIZED(actual_res->payload, expected_res->payload, + actual_res->payload_size); + } + } else { + ASSERT_NULL(response); + } + + AVS_LIST_DELETE(expect_list); + + return AVS_COAP_RESPONSE_ACCEPTED; +} + +static void expect_response_handler_call(response_handler_args_t *args, + avs_coap_send_result_t result, + avs_error_t err, + const test_msg_t *msg) { + test_handler_expected_response_t *expect = + AVS_LIST_NEW_ELEMENT(test_handler_expected_response_t); + + expect->result = result; + expect->err = err; + expect->has_response = (msg != NULL); + if (msg) { + expect->response = msg->msg.content; + } + + AVS_LIST_APPEND(&args->expect_responses_list, expect); +} + +static response_handler_args_t setup_response_handler_args(void) { + return (response_handler_args_t) { 0 }; +} + +static void cleanup_response_handler_args(response_handler_args_t *args) { + ASSERT_NULL(args->expect_responses_list); +} + +AVS_UNIT_TEST(coap_tcp_csm, request_before_peer_csm) { + _avs_mock_clock_start(avs_time_monotonic_from_scalar(0, AVS_TIME_S)); + avs_shared_buffer_t *inbuf = avs_shared_buffer_new(IN_BUFFER_SIZE); + ASSERT_NOT_NULL(inbuf); + avs_shared_buffer_t *outbuf = avs_shared_buffer_new(OUT_BUFFER_SIZE); + ASSERT_NOT_NULL(outbuf); + test_env_t env __attribute__((cleanup(test_teardown))) = + test_setup_with_external_buffers_without_mock_clock_and_peer_csm( + inbuf, outbuf); + response_handler_args_t args + __attribute__((cleanup(cleanup_response_handler_args))) = + setup_response_handler_args(); + + const test_msg_t *request = COAP_MSG(GET, MAKE_TOKEN("A token")); + const test_msg_t *response = COAP_MSG(CONTENT, MAKE_TOKEN("A token")); + + expect_send(&env, request); + ASSERT_OK( + send_request(env.coap_ctx, request, test_response_handler, &args)); + + expect_recv(&env, COAP_MSG(CSM)); + avs_coap_borrowed_msg_t borrowed_request; + ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &borrowed_request)); + + expect_recv(&env, response); + expect_response_handler_call(&args, AVS_COAP_SEND_RESULT_OK, AVS_OK, + response); + ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &borrowed_request)); +} + +AVS_UNIT_TEST(coap_tcp_csm, no_peer_csm) { + _avs_mock_clock_start(avs_time_monotonic_from_scalar(0, AVS_TIME_S)); + response_handler_args_t args + __attribute__((cleanup(cleanup_response_handler_args))) = + setup_response_handler_args(); + + avs_shared_buffer_t *inbuf = avs_shared_buffer_new(IN_BUFFER_SIZE); + ASSERT_NOT_NULL(inbuf); + avs_shared_buffer_t *outbuf = avs_shared_buffer_new(OUT_BUFFER_SIZE); + ASSERT_NOT_NULL(outbuf); + test_env_t env __attribute__((cleanup(test_teardown))) = + test_setup_with_external_buffers_without_mock_clock_and_peer_csm( + inbuf, outbuf); + + const test_msg_t *request = COAP_MSG(GET, MAKE_TOKEN("A token")); + const test_msg_t *response = COAP_MSG(CONTENT, MAKE_TOKEN("A token")); + + expect_send(&env, request); + ASSERT_OK( + send_request(env.coap_ctx, request, test_response_handler, &args)); + + expect_recv(&env, response); + expect_send(&env, COAP_MSG(ABORT, MAKE_TOKEN("A token"))); + avs_coap_borrowed_msg_t borrowed_request; + avs_error_t err = + receive_nonrequest_message(env.coap_ctx, &borrowed_request); + ASSERT_FAIL(err); + ASSERT_EQ(err.category, AVS_COAP_ERR_CATEGORY); + ASSERT_EQ(err.code, AVS_COAP_ERR_TCP_CSM_NOT_RECEIVED); + + expect_response_handler_call(&args, AVS_COAP_SEND_RESULT_CANCEL, + _avs_coap_err(AVS_COAP_ERR_EXCHANGE_CANCELED), + NULL); +} + +AVS_UNIT_TEST(coap_tcp_csm, signalling_without_peer_csm) { + _avs_mock_clock_start(avs_time_monotonic_from_scalar(0, AVS_TIME_S)); + avs_shared_buffer_t *inbuf = avs_shared_buffer_new(IN_BUFFER_SIZE); + ASSERT_NOT_NULL(inbuf); + avs_shared_buffer_t *outbuf = avs_shared_buffer_new(OUT_BUFFER_SIZE); + ASSERT_NOT_NULL(outbuf); + test_env_t env __attribute__((cleanup(test_teardown))) = + test_setup_with_external_buffers_without_mock_clock_and_peer_csm( + inbuf, outbuf); + + expect_recv(&env, COAP_MSG(PING, MAKE_TOKEN("A token"))); + expect_send(&env, COAP_MSG(ABORT, MAKE_TOKEN("A token"))); + avs_coap_borrowed_msg_t borrowed_request; + avs_error_t err = + receive_nonrequest_message(env.coap_ctx, &borrowed_request); + ASSERT_FAIL(err); + ASSERT_EQ(err.category, AVS_COAP_ERR_CATEGORY); + ASSERT_EQ(err.code, AVS_COAP_ERR_TCP_CSM_NOT_RECEIVED); +} + +AVS_UNIT_TEST(coap_tcp_csm, peer_csm_timeout) { + _avs_mock_clock_start(avs_time_monotonic_from_scalar(0, AVS_TIME_S)); + response_handler_args_t args + __attribute__((cleanup(cleanup_response_handler_args))) = + setup_response_handler_args(); + + avs_shared_buffer_t *inbuf = avs_shared_buffer_new(IN_BUFFER_SIZE); + ASSERT_NOT_NULL(inbuf); + avs_shared_buffer_t *outbuf = avs_shared_buffer_new(OUT_BUFFER_SIZE); + ASSERT_NOT_NULL(outbuf); + test_env_t env __attribute__((cleanup(test_teardown))) = + test_setup_with_external_buffers_without_mock_clock_and_peer_csm( + inbuf, outbuf); + + const test_msg_t *request = COAP_MSG(GET, MAKE_TOKEN("A token")); + + _avs_mock_clock_advance(avs_time_duration_from_scalar(1, AVS_TIME_S)); + + expect_send(&env, request); + ASSERT_OK( + send_request(env.coap_ctx, request, test_response_handler, &args)); + + avs_time_duration_t time_to_expiry = avs_sched_time_to_next(env.sched); + ASSERT_TRUE(avs_time_duration_valid(time_to_expiry)); + + _avs_mock_clock_advance(time_to_expiry); + + expect_send(&env, + COAP_MSG(ABORT, PAYLOAD("CSM not received within timeout"))); + avs_sched_run(env.sched); + + expect_response_handler_call(&args, AVS_COAP_SEND_RESULT_CANCEL, + _avs_coap_err(AVS_COAP_ERR_EXCHANGE_CANCELED), + NULL); +} + +AVS_UNIT_TEST(coap_tcp_csm, error_sending_csm) { + test_env_t env __attribute__((cleanup(test_teardown))) = + test_setup_without_socket(); + + ASSERT_NULL(env.mocksock); + avs_unit_mocksock_create(&env.mocksock); + avs_unit_mocksock_enable_recv_timeout_getsetopt( + env.mocksock, avs_time_duration_from_scalar(0, AVS_TIME_S)); + ASSERT_NOT_NULL(env.mocksock); + avs_unit_mocksock_expect_connect(env.mocksock, NULL, NULL); + avs_net_socket_connect(env.mocksock, NULL, NULL); + + // Attempt to send CSM + avs_unit_mocksock_output_fail(env.mocksock, avs_errno(AVS_ECONNRESET)); + // Failed, now send Abort + expect_send(&env, COAP_MSG(ABORT, PAYLOAD("failed to send CSM"))); + ASSERT_FAIL(avs_coap_ctx_set_socket(env.coap_ctx, env.mocksock)); +} + +#endif // defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_TCP) diff --git a/deps/avs_coap/tests/tcp/env.h b/deps/avs_coap/tests/tcp/env.h index dbb13864..6d671150 100644 --- a/deps/avs_coap/tests/tcp/env.h +++ b/deps/avs_coap/tests/tcp/env.h @@ -113,7 +113,8 @@ static inline test_env_t test_setup_without_socket(void) { }); } -static inline test_env_t test_setup_with_external_buffers_without_mock_clock( +static inline test_env_t +test_setup_with_external_buffers_without_mock_clock_and_peer_csm( avs_shared_buffer_t *inbuf, avs_shared_buffer_t *outbuf) { reset_token_generator(); @@ -134,9 +135,6 @@ static inline test_env_t test_setup_with_external_buffers_without_mock_clock( MAX_MESSAGE_SIZE(SIZE_MAX)); avs_unit_mocksock_expect_output(socket, csm->data, csm->size); - const test_msg_t *peer_csm = COAP_MSG(CSM); - avs_unit_mocksock_input(socket, peer_csm->data, peer_csm->size); - test_env_t env = test_setup_from_args(&(const test_env_args_t) { .mocksock = socket, .inbuf = inbuf, @@ -145,6 +143,22 @@ static inline test_env_t test_setup_with_external_buffers_without_mock_clock( ASSERT_NOT_NULL(env.coap_ctx); ASSERT_OK(avs_coap_ctx_set_socket(env.coap_ctx, socket)); + + return env; +} + +static inline test_env_t test_setup_with_external_buffers_without_mock_clock( + avs_shared_buffer_t *inbuf, avs_shared_buffer_t *outbuf) { + test_env_t env = + test_setup_with_external_buffers_without_mock_clock_and_peer_csm( + inbuf, outbuf); + ASSERT_NOT_NULL(env.coap_ctx); + + const test_msg_t *peer_csm = COAP_MSG(CSM); + avs_unit_mocksock_input(env.mocksock, peer_csm->data, peer_csm->size); + expect_has_buffered_data_check(&env, false); + ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL)); + return env; } diff --git a/deps/avs_coap/tests/tcp/helper_functions.h b/deps/avs_coap/tests/tcp/helper_functions.h index d74e7a3e..298cd9a6 100644 --- a/deps/avs_coap/tests/tcp/helper_functions.h +++ b/deps/avs_coap/tests/tcp/helper_functions.h @@ -42,6 +42,7 @@ receive_message(avs_coap_ctx_t *ctx, avs_coap_borrowed_msg_t *out_request) { assert(!coap_base->in_buffer_in_use); coap_base->in_buffer_in_use = true; uint8_t *buf = avs_shared_buffer_acquire(coap_base->in_buffer); + memset(out_request, 0, sizeof(*out_request)); avs_error_t err = ctx->vtable->receive_message( ctx, buf, coap_base->in_buffer->capacity, out_request); avs_shared_buffer_release(coap_base->in_buffer); diff --git a/deps/avs_coap/tests/udp/async_observe.c b/deps/avs_coap/tests/udp/async_observe.c index 8d5c0c7f..a3bac06d 100644 --- a/deps/avs_coap/tests/udp/async_observe.c +++ b/deps/avs_coap/tests/udp/async_observe.c @@ -327,7 +327,6 @@ AVS_UNIT_TEST(udp_observe, }; expect_send(&env, responses[1]); - expect_observe_cancel(&env, observe_id.token); avs_coap_exchange_id_t id; ASSERT_OK(avs_coap_notify_async(env.coap_ctx, &id, observe_id, @@ -338,6 +337,7 @@ AVS_UNIT_TEST(udp_observe, expect_recv(&env, requests[1]); expect_observe_delivery(&env, AVS_OK); + expect_observe_cancel(&env, observe_id.token); expect_has_buffered_data_check(&env, false); ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL)); diff --git a/deps/avs_coap/tests/udp/udp_tx_params.c b/deps/avs_coap/tests/udp/udp_tx_params.c index 61b9229a..a308dd8b 100644 --- a/deps/avs_coap/tests/udp/udp_tx_params.c +++ b/deps/avs_coap/tests/udp/udp_tx_params.c @@ -32,24 +32,24 @@ static const avs_coap_udp_tx_params_t DETERMINISTIC_TX_PARAMS = { }; AVS_UNIT_TEST(udp_tx_params, correct_backoff) { - avs_crypto_prng_ctx_t *prng_ctx = avs_crypto_prng_new(NULL, NULL); + avs_coap_udp_ctx_t ctx = { + .base.prng_ctx = avs_crypto_prng_new(NULL, NULL), + .tx_params = DETERMINISTIC_TX_PARAMS + }; avs_coap_retry_state_t state; - ASSERT_OK(_avs_coap_udp_initial_retry_state(&DETERMINISTIC_TX_PARAMS, - prng_ctx, &state)); + ASSERT_OK(_avs_coap_udp_initial_retry_state(&ctx, &state)); size_t backoff_s = (size_t) DETERMINISTIC_TX_PARAMS.ack_timeout.seconds; - ASSERT_EQ(state.retry_count, 0); + ASSERT_EQ(state.retries_left, 4); ASSERT_EQ(state.recv_timeout.seconds, backoff_s); for (size_t i = 0; i < DETERMINISTIC_TX_PARAMS.max_retransmit; ++i) { - ASSERT_FALSE(_avs_coap_udp_all_retries_sent(&state, - &DETERMINISTIC_TX_PARAMS)); - ASSERT_OK(_avs_coap_udp_update_retry_state(&state)); + ASSERT_FALSE(_avs_coap_udp_all_retries_sent(&state)); + ASSERT_OK(_avs_coap_udp_update_retry_state(&ctx, &state)); backoff_s *= 2; ASSERT_EQ(state.recv_timeout.seconds, backoff_s); } - ASSERT_TRUE( - _avs_coap_udp_all_retries_sent(&state, &DETERMINISTIC_TX_PARAMS)); - avs_crypto_prng_free(&prng_ctx); + ASSERT_TRUE(_avs_coap_udp_all_retries_sent(&state)); + avs_crypto_prng_free(&ctx.base.prng_ctx); } static void assert_tx_params_equal(const avs_coap_udp_tx_params_t *actual, @@ -166,19 +166,16 @@ static inline double avg_factor(double factor) { } static avs_error_t -fake_avs_coap_udp_initial_retry_state(const avs_coap_udp_tx_params_t *tx_params, - avs_crypto_prng_ctx_t *prng_ctx, +fake_avs_coap_udp_initial_retry_state(avs_coap_udp_ctx_t *ctx, avs_coap_retry_state_t *out_retry_state) { - (void) prng_ctx; - - if (!tx_params || !out_retry_state) { + if (!ctx || !out_retry_state) { return avs_errno(AVS_EINVAL); } - out_retry_state->retry_count = 0; - out_retry_state->recv_timeout = - avs_time_duration_fmul(tx_params->ack_timeout, - avg_factor(tx_params->ack_random_factor)); + out_retry_state->retries_left = 0; + out_retry_state->recv_timeout = avs_time_duration_fmul( + ctx->tx_params.ack_timeout, + avg_factor(ctx->tx_params.ack_random_factor)); return AVS_OK; } diff --git a/deps/avs_commons b/deps/avs_commons index a227958d..5d578fc0 160000 --- a/deps/avs_commons +++ b/deps/avs_commons @@ -1 +1 @@ -Subproject commit a227958d88cd9fc689a90d2c336ee0eaa1daf17f +Subproject commit 5d578fc0a70616ebb589e3c98e30afdb1a730e0d diff --git a/devconfig b/devconfig index aa3d77c8..b3a0759a 100755 --- a/devconfig +++ b/devconfig @@ -129,6 +129,9 @@ if [ "$BREW_OPENSSL" ]; then EXTRA_FLAGS[${#EXTRA_FLAGS[@]}]="-DOPENSSL_ROOT_DIR=$BREW_OPENSSL" fi +# sysctl for macOS, nproc for most of everything else, echo 1 as a last resort +NPROC="$((nproc || sysctl -n hw.logicalcpu || echo 1) 2>/dev/null)" + rm -f CMakeCache.txt rm -rf CMakeFiles ${CMAKE_COMMAND} \ @@ -151,7 +154,7 @@ ${CMAKE_COMMAND} \ -D CMAKE_C_FLAGS="${C_FLAGS} ${EXTRA_C_FLAGS}" \ -D CMAKE_CXX_FLAGS="${EXTRA_C_FLAGS}" \ -D ANJAY_VERSION="${ANJAY_VERSION}" \ - -D NPROC="$((3 * $(nproc)))" \ + -D NPROC="$((3 * NPROC))" \ "${EXTRA_FLAGS[@]}" \ -H"$(dirname "$0")" -B. && make clean diff --git a/doc/sphinx/snippet_sources.md5 b/doc/sphinx/snippet_sources.md5 index 6da1a894..b303b158 100644 --- a/doc/sphinx/snippet_sources.md5 +++ b/doc/sphinx/snippet_sources.md5 @@ -1,4 +1,4 @@ -0afe7586298eaf52bf210557fe88a296 demo/demo_cmds.c +8a6d05a275ef8e8c871bf395154b5164 demo/demo_cmds.c cd5ba3adb34a01fdd2ceceb6a35d5be2 demo/objects/ipso_objects.c 90e172b4b00a21b7290510a21498e0b8 deps/avs_coap/include_public/avsystem/coap/tcp.h 4670a0997896b4ab2b973fb833c4a039 deps/avs_coap/include_public/avsystem/coap/udp.h @@ -63,19 +63,19 @@ b95986a7fc990c24cec0c1674ef32c6e examples/tutorial/BC-Initialization/CMakeLists 19350ae71ff9e7c3db83a14d2d915247 examples/tutorial/BC-Initialization/src/main.c dfc4f61ac401fb1c42795c00cf1fc943 examples/tutorial/BC-MandatoryObjects/src/main.c b83639ef51d445067137ec09baa05886 examples/tutorial/BC-Notifications/src/main.c -2ba1db291dd9df7545875c01fd42cfef examples/tutorial/BC-Notifications/src/time_object.c +63d99ace88ebbf31763a1fbe8563050c examples/tutorial/BC-Notifications/src/time_object.c f1516f08b1fdc34192d7ae8db4332980 examples/tutorial/BC-Notifications/src/time_object.h ed043c285a2ba53eaafb581f756995b6 examples/tutorial/BC-ObjectImplementation/CMakeLists.txt 403e8a9c34e14ba25066b3358b25aeca examples/tutorial/BC-ObjectImplementation/src/main.c -fa4836f85e6e4f150810e54307605a74 examples/tutorial/BC-ObjectImplementation/src/time_object.c +4c43b921c25a63b76edbdb9f2531ac88 examples/tutorial/BC-ObjectImplementation/src/time_object.c 2cc2aa2169283d1553696b4022de6110 examples/tutorial/BC-ObjectImplementation/src/time_object.h e630f80842c08178765271d6e08148db examples/tutorial/BC-Security/src/main.c 46223e32f9b53e04c5b90b2c006e5157 examples/tutorial/BC-Send/src/main.c -e486c30f9f4531f6e824fa37f33a2d66 examples/tutorial/BC-Send/src/time_object.c +7aa4a9eb11d14dbf0fedef72e1699b76 examples/tutorial/BC-Send/src/time_object.c b9e1ed0eeaa7bf74af92278ce6e37fc5 examples/tutorial/BC-Send/src/time_object.h 0a07f4dddcbc6ff212a758846d78c3f3 examples/tutorial/BC-ThreadSafety/CMakeLists.txt 015f7222280dbe28e92a68d311991f16 examples/tutorial/BC-ThreadSafety/src/main.c -854fac3a121f2602b24f9912c9e217f8 examples/tutorial/BC-ThreadSafety/src/time_object.c +14138aa1ed29ec5fad3bee0b12f83555 examples/tutorial/BC-ThreadSafety/src/time_object.c 55313e5351782caf5bcb91190f07c974 examples/tutorial/firmware-update/advanced-firmware-update/src/advanced_firmware_update.c 3250ba9efc66031e101d2213e170f30a examples/tutorial/firmware-update/advanced-firmware-update/src/advanced_firmware_update.h f971df7872a8f052bb72ae64610a8031 examples/tutorial/firmware-update/basic-implementation/src/firmware_update.c @@ -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 -10310e97d55c12989ee56511cf0788c6 include_public/anjay/advanced_fw_update.h +b8af5f803c445a4b1f7824407c979037 include_public/anjay/advanced_fw_update.h af2f0e0ac0de25dc04713f05a378d469 include_public/anjay/attr_storage.h -9ffba29d019480c84e3ace840c6b51c7 include_public/anjay/core.h -0534e6f5f96ff707378f59e8236f0bad include_public/anjay/dm.h -f9fabfaff536cb7bbb4b2b10c549797a include_public/anjay/fw_update.h +0223e485b0ea245f2b0e7982a502528b include_public/anjay/core.h +c88697977b9c8e8a34d0229137ae793a include_public/anjay/dm.h +638664942f4503dec39d1fbe9d4d7cbc 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/BasicClient/BC-ObjectImplementation.rst b/doc/sphinx/source/BasicClient/BC-ObjectImplementation.rst index e110c034..e67db749 100644 --- a/doc/sphinx/source/BasicClient/BC-ObjectImplementation.rst +++ b/doc/sphinx/source/BasicClient/BC-ObjectImplementation.rst @@ -133,7 +133,7 @@ case. .. highlight:: c .. snippet-source:: examples/tutorial/BC-ObjectImplementation/src/time_object.c - :emphasize-lines: 11 + :emphasize-lines: 10 static int instance_reset(anjay_t *anjay, const anjay_dm_object_def_t *const *obj_ptr, @@ -141,7 +141,6 @@ case. (void) anjay; time_object_t *obj = get_obj(obj_ptr); - assert(obj); time_instance_t *inst = find_instance(obj, iid); assert(inst); @@ -192,7 +191,7 @@ We may use ``avs_time_real_now()`` to get the current time. .. highlight:: c .. snippet-source:: examples/tutorial/BC-ObjectImplementation/src/time_object.c - :emphasize-lines: 15-27 + :emphasize-lines: 14-26 static int resource_read(anjay_t *anjay, const anjay_dm_object_def_t *const *obj_ptr, @@ -203,7 +202,6 @@ We may use ``avs_time_real_now()`` to get the current time. (void) anjay; time_object_t *obj = get_obj(obj_ptr); - assert(obj); time_instance_t *inst = find_instance(obj, iid); assert(inst); @@ -232,7 +230,7 @@ allowed only on Application Type resource. .. highlight:: c .. snippet-source:: examples/tutorial/BC-ObjectImplementation/src/time_object.c - :emphasize-lines: 15-18 + :emphasize-lines: 14-17 static int resource_write(anjay_t *anjay, const anjay_dm_object_def_t *const *obj_ptr, @@ -243,7 +241,6 @@ allowed only on Application Type resource. (void) anjay; time_object_t *obj = get_obj(obj_ptr); - assert(obj); time_instance_t *inst = find_instance(obj, iid); assert(inst); diff --git a/doc/sphinx/source/BasicClient/BC-ThreadSafety.rst b/doc/sphinx/source/BasicClient/BC-ThreadSafety.rst index 59ae2397..5ac66033 100644 --- a/doc/sphinx/source/BasicClient/BC-ThreadSafety.rst +++ b/doc/sphinx/source/BasicClient/BC-ThreadSafety.rst @@ -106,8 +106,8 @@ properly guarded by a mutex: .. highlight:: c .. snippet-source:: examples/tutorial/BC-ThreadSafety/src/time_object.c :caption: time_object.c - :emphasize-lines: 4,46,77,82,131-137,147,161,172,176,207,232,246,260,269, - 274,283,288,312-318,326-333,338-344,352,356-357,367,381 + :emphasize-lines: 4,46,77,81,129-135,144,158,168,172,202,227,240,254,263, + 268,277,282,306-312,320-327,332-338,346,350-351,361,375 #include #include @@ -183,7 +183,6 @@ properly guarded by a mutex: anjay_dm_list_ctx_t *ctx) { (void) anjay; time_object_t *obj = get_obj(obj_ptr); - assert(obj); pthread_mutex_lock(&obj->mutex); AVS_LIST(time_instance_t) it; @@ -237,7 +236,6 @@ properly guarded by a mutex: anjay_iid_t iid) { (void) anjay; time_object_t *obj = get_obj(obj_ptr); - assert(obj); pthread_mutex_lock(&obj->mutex); int result = 0; @@ -253,7 +251,6 @@ properly guarded by a mutex: anjay_iid_t iid) { (void) anjay; time_object_t *obj = get_obj(obj_ptr); - assert(obj); pthread_mutex_lock(&obj->mutex); int result = ANJAY_ERR_NOT_FOUND; @@ -278,7 +275,6 @@ properly guarded by a mutex: anjay_iid_t iid) { (void) anjay; time_object_t *obj = get_obj(obj_ptr); - assert(obj); pthread_mutex_lock(&obj->mutex); time_instance_t *inst = find_instance(obj, iid); @@ -313,7 +309,6 @@ properly guarded by a mutex: anjay_output_ctx_t *ctx) { (void) anjay; time_object_t *obj = get_obj(obj_ptr); - assert(obj); pthread_mutex_lock(&obj->mutex); time_instance_t *inst = find_instance(obj, iid); @@ -352,7 +347,6 @@ properly guarded by a mutex: anjay_input_ctx_t *ctx) { (void) anjay; time_object_t *obj = get_obj(obj_ptr); - assert(obj); pthread_mutex_lock(&obj->mutex); time_instance_t *inst = find_instance(obj, iid); diff --git a/doc/sphinx/source/CommercialFeatures/CF-SmartCardBootstrap.rst b/doc/sphinx/source/CommercialFeatures/CF-SmartCardBootstrap.rst index 4479b47a..97861828 100644 --- a/doc/sphinx/source/CommercialFeatures/CF-SmartCardBootstrap.rst +++ b/doc/sphinx/source/CommercialFeatures/CF-SmartCardBootstrap.rst @@ -247,7 +247,7 @@ to `anjay_security_object_install() .. highlight:: c .. snippet-source:: examples/commercial-features/CF-SmartCardBootstrap/src/main.c - :emphasize-lines: 23-24, 28-30 + :emphasize-lines: 22-23, 27-29 int main(int argc, char *argv[]) { if (argc != 3) { diff --git a/doc/sphinx/source/Migrating.rst b/doc/sphinx/source/Migrating.rst index b26f52f1..c63fd464 100644 --- a/doc/sphinx/source/Migrating.rst +++ b/doc/sphinx/source/Migrating.rst @@ -34,3 +34,4 @@ Migrating from older versions Migrating/MigratingFromAnjay30 Migrating/MigratingFromAnjay32 Migrating/MigratingFromAnjay33 + Migrating/MigratingFromAnjay34 diff --git a/doc/sphinx/source/Migrating/MigratingFromAnjay214.rst b/doc/sphinx/source/Migrating/MigratingFromAnjay214.rst index bf436a03..f1f12fc6 100644 --- a/doc/sphinx/source/Migrating/MigratingFromAnjay214.rst +++ b/doc/sphinx/source/Migrating/MigratingFromAnjay214.rst @@ -125,6 +125,27 @@ might need to be enabled by defining ``ANJAY_WITH_SECURITY_STRUCTURED`` in ``anjay_config.h``. +Changes in avs_coap +------------------- + +Changed flow of cancelling observations in case of errors +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +CoAP observations are implicitly cancelled if a notification bearing a 4.xx or +5.xx error code is delivered, or if an attempt to deliver a notification times +out. + +In Anjay 3.4.x and earlier, this cancellation (which involves calling the +``avs_coap_observe_cancel_handler_t`` callback) was performed *before* calling +the ``avs_coap_delivery_status_handler_t`` callback for the specific +notification. Since Anjay 3.5.0, this order is reversed, so any code that relies +on this logic may break. + +This change is only relevant if you are using ``avs_coap`` APIs directly (e.g. +when communicating over raw CoAP protocol) and in case of notifications intended +to be delivered as confirmable. The LwM2M Observe/Notify implementation in Anjay +has been updated accordingly. + Changes in avs_commons ---------------------- diff --git a/doc/sphinx/source/Migrating/MigratingFromAnjay215.rst b/doc/sphinx/source/Migrating/MigratingFromAnjay215.rst index fb7dabbf..e77111ba 100644 --- a/doc/sphinx/source/Migrating/MigratingFromAnjay215.rst +++ b/doc/sphinx/source/Migrating/MigratingFromAnjay215.rst @@ -120,6 +120,27 @@ might need to be enabled by defining ``ANJAY_WITH_SECURITY_STRUCTURED`` in ``anjay_config.h``. +Changes in avs_coap +------------------- + +Changed flow of cancelling observations in case of errors +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +CoAP observations are implicitly cancelled if a notification bearing a 4.xx or +5.xx error code is delivered, or if an attempt to deliver a notification times +out. + +In Anjay 3.4.x and earlier, this cancellation (which involves calling the +``avs_coap_observe_cancel_handler_t`` callback) was performed *before* calling +the ``avs_coap_delivery_status_handler_t`` callback for the specific +notification. Since Anjay 3.5.0, this order is reversed, so any code that relies +on this logic may break. + +This change is only relevant if you are using ``avs_coap`` APIs directly (e.g. +when communicating over raw CoAP protocol) and in case of notifications intended +to be delivered as confirmable. The LwM2M Observe/Notify implementation in Anjay +has been updated accordingly. + Changes in avs_commons ---------------------- diff --git a/doc/sphinx/source/Migrating/MigratingFromAnjay225.rst b/doc/sphinx/source/Migrating/MigratingFromAnjay225.rst index 6589d8ef..851c82b9 100644 --- a/doc/sphinx/source/Migrating/MigratingFromAnjay225.rst +++ b/doc/sphinx/source/Migrating/MigratingFromAnjay225.rst @@ -203,66 +203,84 @@ If you are using ``avs_coap`` APIs directly (e.g. when communicating over raw CoAP protocol), please note that following breaking changes in the ``avs_coap`` component: -* In line with Anjay and ``avs_commons``, to improve file name uniqueness, the - ``avsystem/coap/config.h`` file has been renamed to - ``avsystem/coap/avs_coap_config.h``. +avs_coap header rename +^^^^^^^^^^^^^^^^^^^^^^ + +In line with Anjay and ``avs_commons``, to improve file name uniqueness, the +``avsystem/coap/config.h`` file has been renamed to +``avsystem/coap/avs_coap_config.h``. + +Context creation API change +^^^^^^^^^^^^^^^^^^^^^^^^^^^ -* Moreover, context creation functions now take an explicit PRNG context - argument: +Context creation functions now take an explicit PRNG context argument: - * **UDP context creation** +* **UDP context creation** - - **Old API:** - :: + - **Old API:** + :: - avs_coap_ctx_t * - avs_coap_udp_ctx_create(avs_sched_t *sched, - const avs_coap_udp_tx_params_t *udp_tx_params, - avs_shared_buffer_t *in_buffer, - avs_shared_buffer_t *out_buffer, - avs_coap_udp_response_cache_t *cache); + avs_coap_ctx_t * + avs_coap_udp_ctx_create(avs_sched_t *sched, + const avs_coap_udp_tx_params_t *udp_tx_params, + avs_shared_buffer_t *in_buffer, + avs_shared_buffer_t *out_buffer, + avs_coap_udp_response_cache_t *cache); - - **New API:** + - **New API:** - .. snippet-source:: deps/avs_coap/include_public/avsystem/coap/udp.h - :emphasize-lines: 7 + .. snippet-source:: deps/avs_coap/include_public/avsystem/coap/udp.h + :emphasize-lines: 7 - avs_coap_ctx_t * - avs_coap_udp_ctx_create(avs_sched_t *sched, - const avs_coap_udp_tx_params_t *udp_tx_params, - avs_shared_buffer_t *in_buffer, - avs_shared_buffer_t *out_buffer, - avs_coap_udp_response_cache_t *cache, - avs_crypto_prng_ctx_t *prng_ctx); + avs_coap_ctx_t * + avs_coap_udp_ctx_create(avs_sched_t *sched, + const avs_coap_udp_tx_params_t *udp_tx_params, + avs_shared_buffer_t *in_buffer, + avs_shared_buffer_t *out_buffer, + avs_coap_udp_response_cache_t *cache, + avs_crypto_prng_ctx_t *prng_ctx); - * **TCP context creation** +* **TCP context creation** - - **Old API:** - :: + - **Old API:** + :: - avs_coap_ctx_t *avs_coap_tcp_ctx_create(avs_sched_t *sched, - avs_shared_buffer_t *in_buffer, - avs_shared_buffer_t *out_buffer, - size_t max_opts_size, - avs_time_duration_t request_timeout); + avs_coap_ctx_t *avs_coap_tcp_ctx_create(avs_sched_t *sched, + avs_shared_buffer_t *in_buffer, + avs_shared_buffer_t *out_buffer, + size_t max_opts_size, + avs_time_duration_t request_timeout); - - **New API:** + - **New API:** - .. snippet-source:: deps/avs_coap/include_public/avsystem/coap/tcp.h - :emphasize-lines: 6 + .. snippet-source:: deps/avs_coap/include_public/avsystem/coap/tcp.h + :emphasize-lines: 6 - avs_coap_ctx_t *avs_coap_tcp_ctx_create(avs_sched_t *sched, - avs_shared_buffer_t *in_buffer, - avs_shared_buffer_t *out_buffer, - size_t max_opts_size, - avs_time_duration_t request_timeout, - avs_crypto_prng_ctx_t *prng_ctx); + avs_coap_ctx_t *avs_coap_tcp_ctx_create(avs_sched_t *sched, + avs_shared_buffer_t *in_buffer, + avs_shared_buffer_t *out_buffer, + size_t max_opts_size, + avs_time_duration_t request_timeout, + avs_crypto_prng_ctx_t *prng_ctx); .. note :: It is now **mandatory** to pass a non-NULL value as the ``prng_ctx`` argument to the functions above. +Changed flow of cancelling observations in case of errors +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +CoAP observations are implicitly cancelled if a notification bearing a 4.xx or +5.xx error code is delivered, or if an attempt to deliver a notification times +out. + +In Anjay 3.4.x and earlier, this cancellation (which involves calling the +``avs_coap_observe_cancel_handler_t`` callback) was performed *before* calling +the ``avs_coap_delivery_status_handler_t`` callback for the specific +notification. Since Anjay 3.5.0, this order is reversed, so any code that relies +on this logic may break. + Changes in avs_commons ---------------------- diff --git a/doc/sphinx/source/Migrating/MigratingFromAnjay24.rst b/doc/sphinx/source/Migrating/MigratingFromAnjay24.rst index a52846f0..8bcf56c5 100644 --- a/doc/sphinx/source/Migrating/MigratingFromAnjay24.rst +++ b/doc/sphinx/source/Migrating/MigratingFromAnjay24.rst @@ -155,6 +155,27 @@ Please note that Mbed TLS 3.0 has dropped support for TLS 1.1 and earlier, so this change will not affect behavior with that library. +Changes in avs_coap +------------------- + +Changed flow of cancelling observations in case of errors +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +CoAP observations are implicitly cancelled if a notification bearing a 4.xx or +5.xx error code is delivered, or if an attempt to deliver a notification times +out. + +In Anjay 3.4.x and earlier, this cancellation (which involves calling the +``avs_coap_observe_cancel_handler_t`` callback) was performed *before* calling +the ``avs_coap_delivery_status_handler_t`` callback for the specific +notification. Since Anjay 3.5.0, this order is reversed, so any code that relies +on this logic may break. + +This change is only relevant if you are using ``avs_coap`` APIs directly (e.g. +when communicating over raw CoAP protocol) and in case of notifications intended +to be delivered as confirmable. The LwM2M Observe/Notify implementation in Anjay +has been updated accordingly. + Changes in avs_commons ---------------------- diff --git a/doc/sphinx/source/Migrating/MigratingFromAnjay26.rst b/doc/sphinx/source/Migrating/MigratingFromAnjay26.rst index a32edb2c..c61e284c 100644 --- a/doc/sphinx/source/Migrating/MigratingFromAnjay26.rst +++ b/doc/sphinx/source/Migrating/MigratingFromAnjay26.rst @@ -155,6 +155,27 @@ Please note that Mbed TLS 3.0 has dropped support for TLS 1.1 and earlier, so this change will not affect behavior with that library. +Changes in avs_coap +------------------- + +Changed flow of cancelling observations in case of errors +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +CoAP observations are implicitly cancelled if a notification bearing a 4.xx or +5.xx error code is delivered, or if an attempt to deliver a notification times +out. + +In Anjay 3.4.x and earlier, this cancellation (which involves calling the +``avs_coap_observe_cancel_handler_t`` callback) was performed *before* calling +the ``avs_coap_delivery_status_handler_t`` callback for the specific +notification. Since Anjay 3.5.0, this order is reversed, so any code that relies +on this logic may break. + +This change is only relevant if you are using ``avs_coap`` APIs directly (e.g. +when communicating over raw CoAP protocol) and in case of notifications intended +to be delivered as confirmable. The LwM2M Observe/Notify implementation in Anjay +has been updated accordingly. + Changes in avs_commons ---------------------- diff --git a/doc/sphinx/source/Migrating/MigratingFromAnjay27.rst b/doc/sphinx/source/Migrating/MigratingFromAnjay27.rst index e6672a27..c31367b0 100644 --- a/doc/sphinx/source/Migrating/MigratingFromAnjay27.rst +++ b/doc/sphinx/source/Migrating/MigratingFromAnjay27.rst @@ -148,6 +148,27 @@ option and enabled by default if available. Otherwise, it might need to be enabled by defining ``ANJAY_WITH_SECURITY_STRUCTURED`` in ``anjay_config.h``. +Changes in avs_coap +------------------- + +Changed flow of cancelling observations in case of errors +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +CoAP observations are implicitly cancelled if a notification bearing a 4.xx or +5.xx error code is delivered, or if an attempt to deliver a notification times +out. + +In Anjay 3.4.x and earlier, this cancellation (which involves calling the +``avs_coap_observe_cancel_handler_t`` callback) was performed *before* calling +the ``avs_coap_delivery_status_handler_t`` callback for the specific +notification. Since Anjay 3.5.0, this order is reversed, so any code that relies +on this logic may break. + +This change is only relevant if you are using ``avs_coap`` APIs directly (e.g. +when communicating over raw CoAP protocol) and in case of notifications intended +to be delivered as confirmable. The LwM2M Observe/Notify implementation in Anjay +has been updated accordingly. + Changes in avs_commons ---------------------- diff --git a/doc/sphinx/source/Migrating/MigratingFromAnjay28.rst b/doc/sphinx/source/Migrating/MigratingFromAnjay28.rst index cf0ee710..68c1216a 100644 --- a/doc/sphinx/source/Migrating/MigratingFromAnjay28.rst +++ b/doc/sphinx/source/Migrating/MigratingFromAnjay28.rst @@ -143,6 +143,27 @@ option and enabled by default if available. Otherwise, it might need to be enabled by defining ``ANJAY_WITH_SECURITY_STRUCTURED`` in ``anjay_config.h``. +Changes in avs_coap +------------------- + +Changed flow of cancelling observations in case of errors +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +CoAP observations are implicitly cancelled if a notification bearing a 4.xx or +5.xx error code is delivered, or if an attempt to deliver a notification times +out. + +In Anjay 3.4.x and earlier, this cancellation (which involves calling the +``avs_coap_observe_cancel_handler_t`` callback) was performed *before* calling +the ``avs_coap_delivery_status_handler_t`` callback for the specific +notification. Since Anjay 3.5.0, this order is reversed, so any code that relies +on this logic may break. + +This change is only relevant if you are using ``avs_coap`` APIs directly (e.g. +when communicating over raw CoAP protocol) and in case of notifications intended +to be delivered as confirmable. The LwM2M Observe/Notify implementation in Anjay +has been updated accordingly. + Changes in avs_commons ---------------------- diff --git a/doc/sphinx/source/Migrating/MigratingFromAnjay30.rst b/doc/sphinx/source/Migrating/MigratingFromAnjay30.rst index fb972641..3af33330 100644 --- a/doc/sphinx/source/Migrating/MigratingFromAnjay30.rst +++ b/doc/sphinx/source/Migrating/MigratingFromAnjay30.rst @@ -55,6 +55,27 @@ Please note that Mbed TLS 3.0 has dropped support for TLS 1.1 and earlier, so this change will not affect behavior with that library. +Changes in avs_coap +------------------- + +Changed flow of cancelling observations in case of errors +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +CoAP observations are implicitly cancelled if a notification bearing a 4.xx or +5.xx error code is delivered, or if an attempt to deliver a notification times +out. + +In Anjay 3.4.x and earlier, this cancellation (which involves calling the +``avs_coap_observe_cancel_handler_t`` callback) was performed *before* calling +the ``avs_coap_delivery_status_handler_t`` callback for the specific +notification. Since Anjay 3.5.0, this order is reversed, so any code that relies +on this logic may break. + +This change is only relevant if you are using ``avs_coap`` APIs directly (e.g. +when communicating over raw CoAP protocol) and in case of notifications intended +to be delivered as confirmable. The LwM2M Observe/Notify implementation in Anjay +has been updated accordingly. + Changes in avs_commons ---------------------- diff --git a/doc/sphinx/source/Migrating/MigratingFromAnjay32.rst b/doc/sphinx/source/Migrating/MigratingFromAnjay32.rst index 2f39170f..1c2a444e 100644 --- a/doc/sphinx/source/Migrating/MigratingFromAnjay32.rst +++ b/doc/sphinx/source/Migrating/MigratingFromAnjay32.rst @@ -58,6 +58,24 @@ If you are scheduling custom jobs through the avs_sched module, you may want or need to modify their logic accordingly to accommodate for these changes. In most typical use cases, no changes are expected to be necessary. +Changed flow of cancelling observations in case of errors +--------------------------------------------------------- + +CoAP observations are implicitly cancelled if a notification bearing a 4.xx or +5.xx error code is delivered, or if an attempt to deliver a notification times +out. + +In Anjay 3.4.x and earlier, this cancellation (which involves calling the +``avs_coap_observe_cancel_handler_t`` callback) was performed *before* calling +the ``avs_coap_delivery_status_handler_t`` callback for the specific +notification. Since Anjay 3.5.0, this order is reversed, so any code that relies +on this logic may break. + +This change is only relevant if you are using ``avs_coap`` APIs directly (e.g. +when communicating over raw CoAP protocol) and in case of notifications intended +to be delivered as confirmable. The LwM2M Observe/Notify implementation in Anjay +has been updated accordingly. + Removal of avs_unit_memstream ----------------------------- diff --git a/doc/sphinx/source/Migrating/MigratingFromAnjay33.rst b/doc/sphinx/source/Migrating/MigratingFromAnjay33.rst index 176705f8..fe7545bd 100644 --- a/doc/sphinx/source/Migrating/MigratingFromAnjay33.rst +++ b/doc/sphinx/source/Migrating/MigratingFromAnjay33.rst @@ -55,3 +55,21 @@ If you are scheduling custom jobs through the avs_sched module, you may want or need to modify their logic accordingly to accommodate for these changes. In most typical use cases, no changes are expected to be necessary. +Changed flow of cancelling observations in case of errors +--------------------------------------------------------- + +CoAP observations are implicitly cancelled if a notification bearing a 4.xx or +5.xx error code is delivered, or if an attempt to deliver a notification times +out. + +In Anjay 3.4.x and earlier, this cancellation (which involves calling the +``avs_coap_observe_cancel_handler_t`` callback) was performed *before* calling +the ``avs_coap_delivery_status_handler_t`` callback for the specific +notification. Since Anjay 3.5.0, this order is reversed, so any code that relies +on this logic may break. + +This change is only relevant if you are using ``avs_coap`` APIs directly (e.g. +when communicating over raw CoAP protocol) and in case of notifications intended +to be delivered as confirmable. The LwM2M Observe/Notify implementation in Anjay +has been updated accordingly. + diff --git a/doc/sphinx/source/Migrating/MigratingFromAnjay34.rst b/doc/sphinx/source/Migrating/MigratingFromAnjay34.rst new file mode 100644 index 00000000..d7543fa3 --- /dev/null +++ b/doc/sphinx/source/Migrating/MigratingFromAnjay34.rst @@ -0,0 +1,40 @@ +.. + 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. + +Migrating from Anjay 3.4 +======================== + +.. contents:: :local: + +.. highlight:: c + +Introduction +------------ + +Since Anjay 3.5.0, the code flow of delivering notifications has been +refactored so that user handler callbacks may be called in a different order. +This only affects direct users of ``avs_coap`` APIs (e.g. when communicating +over raw CoAP protocol). + +Changed flow of cancelling observations in case of errors +--------------------------------------------------------- + +CoAP observations are implicitly cancelled if a notification bearing a 4.xx or +5.xx error code is delivered, or if an attempt to deliver a notification times +out. + +In Anjay 3.4.x and earlier, this cancellation (which involves calling the +``avs_coap_observe_cancel_handler_t`` callback) was performed *before* calling +the ``avs_coap_delivery_status_handler_t`` callback for the specific +notification. Since Anjay 3.5.0, this order is reversed, so any code that relies +on this logic may break. + +This change is only relevant if you are using ``avs_coap`` APIs directly (e.g. +when communicating over raw CoAP protocol) and in case of notifications intended +to be delivered as confirmable. The LwM2M Observe/Notify implementation in Anjay +has been updated accordingly. diff --git a/doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/NetworkingAPI.rst b/doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/NetworkingAPI.rst index 295cf6d0..2c6a8dd8 100644 --- a/doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/NetworkingAPI.rst +++ b/doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/NetworkingAPI.rst @@ -99,9 +99,7 @@ If POSIX socket API is not available: used to keep the bound port stable if possible - ``get_local_port`` - used to keep the bound port stable if possible - ``get_remote_host`` - required for CoAP message cache to work - - ``get_remote_hostname`` - required for ability to suspend CoAP downloads - - ``get_remote_port`` - required for CoAP message cache to work and for - ability to suspend CoAP downloads + - ``get_remote_port`` - required for CoAP message cache to work - ``shutdown`` - required for ability to suspend CoAP downloads - ``_avs_net_create_tcp_socket`` - only required if the ``fw_update`` module diff --git a/doc/sphinx/source/Tools/StandaloneObjects.rst b/doc/sphinx/source/Tools/StandaloneObjects.rst new file mode 100644 index 00000000..fa0341bd --- /dev/null +++ b/doc/sphinx/source/Tools/StandaloneObjects.rst @@ -0,0 +1,87 @@ +.. + 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. + +Standalone LwM2M Object implementations +======================================= + +As described in :doc:`../BasicClient/BC-MandatoryObjects`, Anjay contains +implementations of some LwM2M Core Objects, including the LwM2M Security (0) and +LwM2M Server (1) objects. + +These implementations should be appropriate for most users, but some users might +have specific requirements that deviate from the default Anjay behavior. For +this reason, standalone versions of the Security and Server objects are provided +in the ``standalone`` directory of the repository (or commercial distribution +package). + +.. warning:: + + Customizing the logic of Core Objects is likely to violate the LwM2M + technical specification. Please proceed with care. + +Using the standalone objects +---------------------------- + +To use the standalone objects: + +* Copy the ``standalone/security`` and/or ``standalone/server`` directories into + your project, and make sure that all the ``*.c`` files are compiled. + +* The ``standalone_security.h`` and ``standalone_server.h`` files mirror the + public header files of the default implementations. Please include them in + your application code to use the object implementations. + +* Make sure to account for the following differences between the default and + standalone implementations: + + * Prefix for public APIs (including public function and type names) is changed + from ``anjay_`` to ``standalone_`` + + * The install functions (i.e., ``standalone_security_object_install()``, + ``standalone_security_object_install_with_hsm()`` and + ``standalone_server_object_install()``), unlike their default versions, + return ``const anjay_dm_object_def_t **`` pointers. Please store this value + during installation, as it needs to be passed for further API calls. + However, you **do not** need to call `anjay_register_object() + <../api/dm_8h.html#a1468b47fa9169474920c8c86d533b991>`_ as the install + functions already call it. + + * All other public APIs take the aforementioned + ``const anjay_dm_object_def_t *const *`` pointer instead of the `anjay_t + <../api/core_8h.html#a6c9664a3b0c2d5629c9639dce7b1dbfb>`_ object. Adjust the + calls accordingly. + + * Unlike the default implementation, the standalone objects are **not + automatically cleaned up** at the time of `anjay_delete() + <../api/core_8h.html#a243f18f976bca57b5a7b0714bfb99095>`_. If your code ever + cleans up the Anjay object, please make sure to call + ``standalone_security_object_cleanup()`` and/or + ``standalone_server_object_cleanup()`` afterwards. + +This will replicate the functionality of the default implementations. You can +apply any modifications you need from there. + +.. note:: + + Even though these implementations are standalone, they still contain + conditional compilation directives that refer to Anjay configuration + options, including those related to Security and Server objects, e.g. + ``ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT``. If you wish to disable the + modules completely, please update the code accordingly. + +Limitations +----------- + +The standalone implementations have the same limitations as application code - +they cannot access internal or private Anjay APIs. For this reason, the "OSCORE +Security Mode" Resource in the Security object (/0/\*/17) is not validated, as +the code does not have access to the OSCORE object implementation. + +Also please note that when upgrading Anjay, you will be responsible for porting +any fixes and improvements that may be made to the Security and Server object +implementations between releases. diff --git a/doc/sphinx/source/Tools/StubGenerator.rst b/doc/sphinx/source/Tools/StubGenerator.rst index 145f8b65..81764e25 100644 --- a/doc/sphinx/source/Tools/StubGenerator.rst +++ b/doc/sphinx/source/Tools/StubGenerator.rst @@ -238,7 +238,6 @@ The source of the example object looks like this: anjay_iid_t iid) { (void) anjay; some_object_name_object_t *obj = get_obj(obj_ptr); - assert(obj); return add_instance(obj, iid) ? 0 : ANJAY_ERR_INTERNAL; } @@ -248,7 +247,6 @@ The source of the example object looks like this: anjay_iid_t iid) { (void) anjay; some_object_name_object_t *obj = get_obj(obj_ptr); - assert(obj); AVS_LIST(some_object_name_instance_t) *it; AVS_LIST_FOREACH_PTR(it, &obj->instances) { @@ -271,7 +269,6 @@ The source of the example object looks like this: (void) anjay; some_object_name_object_t *obj = get_obj(obj_ptr); - assert(obj); some_object_name_instance_t *inst = find_instance(obj, iid); assert(inst); @@ -305,7 +302,6 @@ The source of the example object looks like this: (void) anjay; some_object_name_object_t *obj = get_obj(obj_ptr); - assert(obj); some_object_name_instance_t *inst = find_instance(obj, iid); assert(inst); @@ -336,7 +332,6 @@ The source of the example object looks like this: (void) anjay; some_object_name_object_t *obj = get_obj(obj_ptr); - assert(obj); some_object_name_instance_t *inst = find_instance(obj, iid); assert(inst); @@ -371,7 +366,6 @@ The source of the example object looks like this: (void) anjay; some_object_name_object_t *obj = get_obj(obj_ptr); - assert(obj); some_object_name_instance_t *inst = find_instance(obj, iid); assert(inst); @@ -392,7 +386,6 @@ The source of the example object looks like this: (void) anjay; some_object_name_object_t *obj = get_obj(obj_ptr); - assert(obj); some_object_name_instance_t *inst = find_instance(obj, iid); assert(inst); @@ -472,7 +465,7 @@ The source of the example object looks like this: instance and then the memory is deallocated. * Each handler (apart from ``instance_create`` and ``instance_remove``) taking - ``anjay_iid_t iid`` as an argument utilizes auxiliary ``find_instance`` + ``anjay_iid_t iid`` as an argument utilizes auxiliary ``find_instance`` function to get the pointer to the instance. * All allocated instances are deallocated in ``some_object_name_object_release`` @@ -565,7 +558,6 @@ The resulting code is following: (void) anjay; some_object_name_object_t *obj = get_obj(obj_ptr); - assert(obj); assert(iid < AVS_ARRAY_SIZE(obj->instances)); some_object_name_instance_t *inst = &obj->instances[iid]; @@ -601,7 +593,6 @@ The resulting code is following: (void) anjay; some_object_name_object_t *obj = get_obj(obj_ptr); - assert(obj); assert(iid < AVS_ARRAY_SIZE(obj->instances)); some_object_name_instance_t *inst = &obj->instances[iid]; @@ -632,7 +623,6 @@ The resulting code is following: (void) anjay; some_object_name_object_t *obj = get_obj(obj_ptr); - assert(obj); assert(iid < AVS_ARRAY_SIZE(obj->instances)); some_object_name_instance_t *inst = &obj->instances[iid]; @@ -667,7 +657,6 @@ The resulting code is following: (void) anjay; some_object_name_object_t *obj = get_obj(obj_ptr); - assert(obj); assert(iid < AVS_ARRAY_SIZE(obj->instances)); some_object_name_instance_t *inst = &obj->instances[iid]; @@ -688,7 +677,6 @@ The resulting code is following: (void) anjay; some_object_name_object_t *obj = get_obj(obj_ptr); - assert(obj); assert(iid < AVS_ARRAY_SIZE(obj->instances)); some_object_name_instance_t *inst = &obj->instances[iid]; @@ -774,7 +762,7 @@ To create a C++ template of the same object with 10 static instances run: The main difference between the two is that the former approach uses the `C++ wrapper of AVS_LIST `_, -and the latter one takes advantage of +and the latter one takes advantage of `std::array `_ container. After generating the object template diff --git a/example_configs/embedded_lwm2m10/anjay/anjay_config.h b/example_configs/embedded_lwm2m10/anjay/anjay_config.h index 8629e28b..919b3837 100644 --- a/example_configs/embedded_lwm2m10/anjay/anjay_config.h +++ b/example_configs/embedded_lwm2m10/anjay/anjay_config.h @@ -247,8 +247,9 @@ * (anjay_new_from_core_persistence() and * anjay_delete_with_core_persistence() APIs). * - * Requires ANJAY_WITH_OBSERVE to be enabled, and - * AVS_COMMONS_WITH_AVS_PERSISTENCE to be enabled in avs_commons + * Requires ANJAY_WITH_OBSERVE to be enabled, + * AVS_COMMONS_WITH_AVS_PERSISTENCE to be enabled in avs_commons, and + * WITH_AVS_COAP_OBSERVE_PERSISTENCE to be enabled in avs_coap * configuration. * * IMPORTANT: Only available as a commercial feature. Ignored in the open diff --git a/example_configs/embedded_lwm2m10/avsystem/commons/lwip-posix-compat.h b/example_configs/embedded_lwm2m10/avsystem/commons/lwip-posix-compat.h index 605e7c50..15447e23 100644 --- a/example_configs/embedded_lwm2m10/avsystem/commons/lwip-posix-compat.h +++ b/example_configs/embedded_lwm2m10/avsystem/commons/lwip-posix-compat.h @@ -27,6 +27,9 @@ #include "lwipopts.h" +/* Provides lwIP's alternative errno header, used in avs_net_impl.c */ +#include "lwip/errno.h" + /* Provides htons/ntohs/htonl/ntohl */ #include "lwip/inet.h" diff --git a/example_configs/embedded_lwm2m11/anjay/anjay_config.h b/example_configs/embedded_lwm2m11/anjay/anjay_config.h index cbe82999..dabb5e8d 100644 --- a/example_configs/embedded_lwm2m11/anjay/anjay_config.h +++ b/example_configs/embedded_lwm2m11/anjay/anjay_config.h @@ -247,8 +247,9 @@ * (anjay_new_from_core_persistence() and * anjay_delete_with_core_persistence() APIs). * - * Requires ANJAY_WITH_OBSERVE to be enabled, and - * AVS_COMMONS_WITH_AVS_PERSISTENCE to be enabled in avs_commons + * Requires ANJAY_WITH_OBSERVE to be enabled, + * AVS_COMMONS_WITH_AVS_PERSISTENCE to be enabled in avs_commons, and + * WITH_AVS_COAP_OBSERVE_PERSISTENCE to be enabled in avs_coap * configuration. * * IMPORTANT: Only available as a commercial feature. Ignored in the open diff --git a/example_configs/embedded_lwm2m11/avsystem/commons/lwip-posix-compat.h b/example_configs/embedded_lwm2m11/avsystem/commons/lwip-posix-compat.h index 605e7c50..15447e23 100644 --- a/example_configs/embedded_lwm2m11/avsystem/commons/lwip-posix-compat.h +++ b/example_configs/embedded_lwm2m11/avsystem/commons/lwip-posix-compat.h @@ -27,6 +27,9 @@ #include "lwipopts.h" +/* Provides lwIP's alternative errno header, used in avs_net_impl.c */ +#include "lwip/errno.h" + /* Provides htons/ntohs/htonl/ntohl */ #include "lwip/inet.h" diff --git a/example_configs/linux_lwm2m10/anjay/anjay_config.h b/example_configs/linux_lwm2m10/anjay/anjay_config.h index df15e888..0812b22e 100644 --- a/example_configs/linux_lwm2m10/anjay/anjay_config.h +++ b/example_configs/linux_lwm2m10/anjay/anjay_config.h @@ -247,8 +247,9 @@ * (anjay_new_from_core_persistence() and * anjay_delete_with_core_persistence() APIs). * - * Requires ANJAY_WITH_OBSERVE to be enabled, and - * AVS_COMMONS_WITH_AVS_PERSISTENCE to be enabled in avs_commons + * Requires ANJAY_WITH_OBSERVE to be enabled, + * AVS_COMMONS_WITH_AVS_PERSISTENCE to be enabled in avs_commons, and + * WITH_AVS_COAP_OBSERVE_PERSISTENCE to be enabled in avs_coap * configuration. * * IMPORTANT: Only available as a commercial feature. Ignored in the open diff --git a/example_configs/linux_lwm2m11/anjay/anjay_config.h b/example_configs/linux_lwm2m11/anjay/anjay_config.h index 5257d679..7fb7c704 100644 --- a/example_configs/linux_lwm2m11/anjay/anjay_config.h +++ b/example_configs/linux_lwm2m11/anjay/anjay_config.h @@ -247,8 +247,9 @@ * (anjay_new_from_core_persistence() and * anjay_delete_with_core_persistence() APIs). * - * Requires ANJAY_WITH_OBSERVE to be enabled, and - * AVS_COMMONS_WITH_AVS_PERSISTENCE to be enabled in avs_commons + * Requires ANJAY_WITH_OBSERVE to be enabled, + * AVS_COMMONS_WITH_AVS_PERSISTENCE to be enabled in avs_commons, and + * WITH_AVS_COAP_OBSERVE_PERSISTENCE to be enabled in avs_coap * configuration. * * IMPORTANT: Only available as a commercial feature. Ignored in the open diff --git a/examples/tutorial/BC-Notifications/src/time_object.c b/examples/tutorial/BC-Notifications/src/time_object.c index fd9a1912..edf24611 100644 --- a/examples/tutorial/BC-Notifications/src/time_object.c +++ b/examples/tutorial/BC-Notifications/src/time_object.c @@ -131,7 +131,6 @@ static int instance_create(anjay_t *anjay, anjay_iid_t iid) { (void) anjay; time_object_t *obj = get_obj(obj_ptr); - assert(obj); return add_instance(obj, iid) ? 0 : ANJAY_ERR_INTERNAL; } @@ -141,7 +140,6 @@ static int instance_remove(anjay_t *anjay, anjay_iid_t iid) { (void) anjay; time_object_t *obj = get_obj(obj_ptr); - assert(obj); AVS_LIST(time_instance_t) *it; AVS_LIST_FOREACH_PTR(it, &obj->instances) { @@ -164,7 +162,6 @@ static int instance_reset(anjay_t *anjay, (void) anjay; time_object_t *obj = get_obj(obj_ptr); - assert(obj); time_instance_t *inst = find_instance(obj, iid); assert(inst); @@ -199,7 +196,6 @@ static int resource_read(anjay_t *anjay, (void) anjay; time_object_t *obj = get_obj(obj_ptr); - assert(obj); time_instance_t *inst = find_instance(obj, iid); assert(inst); @@ -232,7 +228,6 @@ static int resource_write(anjay_t *anjay, (void) anjay; time_object_t *obj = get_obj(obj_ptr); - assert(obj); time_instance_t *inst = find_instance(obj, iid); assert(inst); diff --git a/examples/tutorial/BC-ObjectImplementation/src/time_object.c b/examples/tutorial/BC-ObjectImplementation/src/time_object.c index eb43c3be..a7db2b87 100644 --- a/examples/tutorial/BC-ObjectImplementation/src/time_object.c +++ b/examples/tutorial/BC-ObjectImplementation/src/time_object.c @@ -130,7 +130,6 @@ static int instance_create(anjay_t *anjay, anjay_iid_t iid) { (void) anjay; time_object_t *obj = get_obj(obj_ptr); - assert(obj); return add_instance(obj, iid) ? 0 : ANJAY_ERR_INTERNAL; } @@ -140,7 +139,6 @@ static int instance_remove(anjay_t *anjay, anjay_iid_t iid) { (void) anjay; time_object_t *obj = get_obj(obj_ptr); - assert(obj); AVS_LIST(time_instance_t) *it; AVS_LIST_FOREACH_PTR(it, &obj->instances) { @@ -163,7 +161,6 @@ static int instance_reset(anjay_t *anjay, (void) anjay; time_object_t *obj = get_obj(obj_ptr); - assert(obj); time_instance_t *inst = find_instance(obj, iid); assert(inst); @@ -198,7 +195,6 @@ static int resource_read(anjay_t *anjay, (void) anjay; time_object_t *obj = get_obj(obj_ptr); - assert(obj); time_instance_t *inst = find_instance(obj, iid); assert(inst); @@ -231,7 +227,6 @@ static int resource_write(anjay_t *anjay, (void) anjay; time_object_t *obj = get_obj(obj_ptr); - assert(obj); time_instance_t *inst = find_instance(obj, iid); assert(inst); diff --git a/examples/tutorial/BC-Send/src/time_object.c b/examples/tutorial/BC-Send/src/time_object.c index 2cd4d075..593dc305 100644 --- a/examples/tutorial/BC-Send/src/time_object.c +++ b/examples/tutorial/BC-Send/src/time_object.c @@ -133,7 +133,6 @@ static int instance_create(anjay_t *anjay, anjay_iid_t iid) { (void) anjay; time_object_t *obj = get_obj(obj_ptr); - assert(obj); return add_instance(obj, iid) ? 0 : ANJAY_ERR_INTERNAL; } @@ -143,7 +142,6 @@ static int instance_remove(anjay_t *anjay, anjay_iid_t iid) { (void) anjay; time_object_t *obj = get_obj(obj_ptr); - assert(obj); AVS_LIST(time_instance_t) *it; AVS_LIST_FOREACH_PTR(it, &obj->instances) { @@ -166,7 +164,6 @@ static int instance_reset(anjay_t *anjay, (void) anjay; time_object_t *obj = get_obj(obj_ptr); - assert(obj); time_instance_t *inst = find_instance(obj, iid); assert(inst); @@ -201,7 +198,6 @@ static int resource_read(anjay_t *anjay, (void) anjay; time_object_t *obj = get_obj(obj_ptr); - assert(obj); time_instance_t *inst = find_instance(obj, iid); assert(inst); @@ -234,7 +230,6 @@ static int resource_write(anjay_t *anjay, (void) anjay; time_object_t *obj = get_obj(obj_ptr); - assert(obj); time_instance_t *inst = find_instance(obj, iid); assert(inst); diff --git a/examples/tutorial/BC-ThreadSafety/src/time_object.c b/examples/tutorial/BC-ThreadSafety/src/time_object.c index 2bd8ad1c..17b29332 100644 --- a/examples/tutorial/BC-ThreadSafety/src/time_object.c +++ b/examples/tutorial/BC-ThreadSafety/src/time_object.c @@ -83,7 +83,6 @@ static int list_instances(anjay_t *anjay, anjay_dm_list_ctx_t *ctx) { (void) anjay; time_object_t *obj = get_obj(obj_ptr); - assert(obj); pthread_mutex_lock(&obj->mutex); AVS_LIST(time_instance_t) it; @@ -137,7 +136,6 @@ static int instance_create(anjay_t *anjay, anjay_iid_t iid) { (void) anjay; time_object_t *obj = get_obj(obj_ptr); - assert(obj); pthread_mutex_lock(&obj->mutex); int result = 0; @@ -153,7 +151,6 @@ static int instance_remove(anjay_t *anjay, anjay_iid_t iid) { (void) anjay; time_object_t *obj = get_obj(obj_ptr); - assert(obj); pthread_mutex_lock(&obj->mutex); int result = ANJAY_ERR_NOT_FOUND; @@ -178,7 +175,6 @@ static int instance_reset(anjay_t *anjay, anjay_iid_t iid) { (void) anjay; time_object_t *obj = get_obj(obj_ptr); - assert(obj); pthread_mutex_lock(&obj->mutex); time_instance_t *inst = find_instance(obj, iid); @@ -213,7 +209,6 @@ static int resource_read(anjay_t *anjay, anjay_output_ctx_t *ctx) { (void) anjay; time_object_t *obj = get_obj(obj_ptr); - assert(obj); pthread_mutex_lock(&obj->mutex); time_instance_t *inst = find_instance(obj, iid); @@ -252,7 +247,6 @@ static int resource_write(anjay_t *anjay, anjay_input_ctx_t *ctx) { (void) anjay; time_object_t *obj = get_obj(obj_ptr); - assert(obj); pthread_mutex_lock(&obj->mutex); time_instance_t *inst = find_instance(obj, iid); diff --git a/include_public/anjay/advanced_fw_update.h b/include_public/anjay/advanced_fw_update.h index 3a0cd2b6..c3643c98 100644 --- a/include_public/anjay/advanced_fw_update.h +++ b/include_public/anjay/advanced_fw_update.h @@ -470,6 +470,9 @@ typedef int anjay_advanced_fw_update_get_security_config_t( * If this handler is not implemented at all (with the corresponding field set * to NULL), udp_tx_params from anjay_t object are used. * + * NOTE: This callback is called even for non-CoAP downloads, + * but the returned transmission parameters are ignored in that case. + * * @param iid Instance ID of an Advanced Firmware Object which query * tx_params. * @@ -736,6 +739,12 @@ int anjay_advanced_fw_update_set_linked_instances( * performing upgrade of a @p iid instance. See AVSystem specification of * Advanced Firmware Update for details. * + * NOTE: The returned array points directly into the internal + * structures of Anjay; however, it may only be modified by a call to + * @ref anjay_advanced_fw_update_set_linked_instances . Nevertheless, if your + * code calls the "get" and "set" functions from different threads, the calls + * need to be additionally synchronized to achieve thread safety. + * * @param anjay Anjay object to operate on. * * @param iid Instance ID of an Advanced Firmware Object. @@ -796,6 +805,12 @@ int anjay_advanced_fw_update_set_conflicting_instances( * Firmware Update object instances that caused the conflict. See LwM2M * specification for details. * + * NOTE: The returned array points directly into the internal + * structures of Anjay; however, it may only be modified by a call to + * @ref anjay_advanced_fw_update_set_conflicting_instances . Nevertheless, if + * your code calls the "get" and "set" functions from different threads, the + * calls need to be additionally synchronized to achieve thread safety. + * * @param anjay Anjay object to operate on. * * @param iid Instance ID of an Advanced Firmware Object. @@ -855,6 +870,45 @@ avs_time_real_t anjay_advanced_fw_update_get_last_state_change_time(anjay_t *anjay, anjay_iid_t iid); +#ifdef ANJAY_WITH_DOWNLOADER +/** + * Suspends the operation of PULL-mode downloads in the Advanced Firmware Update + * module. + * + * This will have the effect of suspending any ongoing downloads (see + * @ref anjay_download_suspend for details), as well as preventing new downloads + * from being started. + * + * 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. + * + * @param anjay Anjay object to operate on. + */ +void anjay_advanced_fw_update_pull_suspend(anjay_t *anjay); + +/** + * Reconnects any ongoing PULL-mode downloads in the Advanced Firmware Update + * module. Additionally, if PULL-mode downloads are suspended (see + * @ref anjay_advanced_fw_update_pull_suspend), resumes normal operation. + * + * If an ongoing PULL-mode download exists, this will call + * @ref anjay_download_reconnect internally, so you may want to reference the + * documentation of that function for details. + * + * @param anjay Anjay object to operate on. + * + * @returns 0 for success; -1 if @p anjay does not have the Firmware Update + * object installed or if the call to @ref anjay_download_reconnect + * fails. + */ +int anjay_advanced_fw_update_pull_reconnect(anjay_t *anjay); +#endif // ANJAY_WITH_DOWNLOADER + #ifdef __cplusplus } #endif diff --git a/include_public/anjay/anjay_config.h.in b/include_public/anjay/anjay_config.h.in index 93cb1f91..fd4ac707 100644 --- a/include_public/anjay/anjay_config.h.in +++ b/include_public/anjay/anjay_config.h.in @@ -247,8 +247,9 @@ * (anjay_new_from_core_persistence() and * anjay_delete_with_core_persistence() APIs). * - * Requires ANJAY_WITH_OBSERVE to be enabled, and - * AVS_COMMONS_WITH_AVS_PERSISTENCE to be enabled in avs_commons + * Requires ANJAY_WITH_OBSERVE to be enabled, + * AVS_COMMONS_WITH_AVS_PERSISTENCE to be enabled in avs_commons, and + * WITH_AVS_COAP_OBSERVE_PERSISTENCE to be enabled in avs_coap * configuration. * * IMPORTANT: Only available as a commercial feature. Ignored in the open diff --git a/include_public/anjay/core.h b/include_public/anjay/core.h index 45ce424b..9aab4269 100644 --- a/include_public/anjay/core.h +++ b/include_public/anjay/core.h @@ -316,7 +316,8 @@ typedef struct anjay_configuration { */ avs_ssl_additional_configuration_clb_t *additional_tls_config_clb; -#if defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP) +#if defined(WITH_AVS_COAP_TCP) \ + && (defined(ANJAY_WITH_LWM2M11) || defined(ANJAY_WITH_COAP_DOWNLOAD)) /** * Maximum expected TCP options size. CoAP messages with options longer * than this value will be rejected. @@ -333,7 +334,8 @@ typedef struct anjay_configuration { * value of 30s is used. */ avs_time_duration_t coap_tcp_request_timeout; -#endif // defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP) +#endif // defined(WITH_AVS_COAP_TCP) && (defined(ANJAY_WITH_LWM2M11) || + // defined(ANJAY_WITH_COAP_DOWNLOAD)) #ifdef ANJAY_WITH_LWM2M11 /** @@ -442,8 +444,14 @@ anjay_t *anjay_new(const anjay_configuration_t *config); /** * Cleans up all resources and releases the Anjay object. * - * NOTE: It shall be called before freeing LwM2M Objects - * registered within the anjay object. + * NOTE: It shall be called before freeing + * LwM2M Objects registered within the anjay object. + * + * NOTE: The anjay pointer is invalidated during the + * call to this function. If Anjay is compiled with thread safety enabled, all + * the intermediary cleanup code is properly synchronized, but you should still + * make sure that no other thread is able to access the anjay object + * before calling this function, to avoid its usage after free. * * @param anjay Anjay object to delete. MUST NOT be @c NULL . */ @@ -477,6 +485,19 @@ void anjay_delete(anjay_t *anjay); * } * @endcode * + * NOTE: The returned list will be invalidated by any + * subsequent call to anjay_get_sockets() or + * @ref anjay_get_socket_entries . If you need to call these functions from + * multiple threads, you need to implement additional synchronization to achieve + * thread safety. + * + * The socket object pointers themselves may additionally be invalidated by a + * call to @ref anjay_sched_run, @ref anjay_serve, @ref anjay_serve_any or + * during the execution of @ref anjay_event_loop_run or + * @ref anjay_event_loop_run_with_error_handling . For this reason, it is + * recommended to only call this function from callback functions called from + * within Anjay, in scheduler jobs, or as part of a custom event loop. + * * @param anjay Anjay object to operate on. * * @returns A list of valid server sockets on success, @@ -548,6 +569,18 @@ typedef struct { * socket in addition to the socket itself. See @ref anjay_socket_entry_t for * details. * + * NOTE: The returned list will be invalidated by any + * subsequent call to @ref anjay_get_sockets or anjay_get_socket_entries. + * If you need to call these functions from multiple threads, you need to + * implement additional synchronization to achieve thread safety. + * + * The socket object pointers themselves may additionally be invalidated by a + * call to @ref anjay_sched_run, @ref anjay_serve, @ref anjay_serve_any or + * during the execution of @ref anjay_event_loop_run or + * @ref anjay_event_loop_run_with_error_handling . For this reason, it is + * recommended to only call this function from callback functions called from + * within Anjay, in scheduler jobs, or as part of a custom event loop. + * * @param anjay Anjay object to operate on. * * @returns A list of valid server socket entries on success, @@ -1311,6 +1344,11 @@ typedef struct { * @ref anjay_fw_update_get_security_config_t implementations. If you need this * information for a longer period, you will need to manually create a deep * copy. + * + * In particular, you may need to implement additional synchronization to + * achieve thread safety if calling this function from multiple threads. + * Instead, it is recommended to only call it from callback functions called + * from within Anjay, or in scheduler jobs. */ int anjay_security_config_from_dm(anjay_t *anjay, anjay_security_config_t *out_config, diff --git a/include_public/anjay/dm.h b/include_public/anjay/dm.h index 54a5cdc4..1c9d3078 100644 --- a/include_public/anjay/dm.h +++ b/include_public/anjay/dm.h @@ -19,6 +19,12 @@ extern "C" { #endif +#define ANJAY_DM_OID_SECURITY 0 +#define ANJAY_DM_OID_SERVER 1 +#define ANJAY_DM_OID_ACCESS_CONTROL 2 +#define ANJAY_DM_OID_DEVICE 3 +#define ANJAY_DM_OID_FIRMWARE_UPDATE 5 + typedef struct anjay_dm_object_def_struct anjay_dm_object_def_t; /** Values for the con attribute. */ diff --git a/include_public/anjay/download.h b/include_public/anjay/download.h index 3acc6393..407efec1 100644 --- a/include_public/anjay/download.h +++ b/include_public/anjay/download.h @@ -208,6 +208,12 @@ typedef void *anjay_download_handle_t; * Request packet retransmissions are managed by Anjay scheduler, and sent by * @ref anjay_sched_run whenever required. * + * No network activity is performed immediately during the call to + * anjay_download(). Instead, the TCP and/or (D)TLS handshakes and the + * first request packet, will be sent during subsequent calls to + * @ref anjay_sched_run. This also means that you can create a postponed + * download by calling @ref anjay_download_suspend immediately afterwards. + * * @param anjay Anjay object that will manage the download process. * @param config Download configuration. * @param out_handle Pointer to a variable that will be set to a download @@ -286,6 +292,48 @@ anjay_download_set_next_block_offset(anjay_t *anjay, */ void anjay_download_abort(anjay_t *anjay, anjay_download_handle_t dl_handle); +/** + * Suspends a download identified by @p dl_handle. Does nothing if @p dl_handle + * does not represent a valid download handle. + * + * The suspend operation is performed immediately and synchronously. The socket + * is disconnected, but the rest of the download context is kept intact. The + * download can be resumed by calling @p anjay_download_reconnect. + * + * If the download is already suspended due to the transport being offline (see + * @ref anjay_transport_set_online), no immediate action is performed, but the + * download is marked in such a way that it will not be automatically resumed + * until an explicit call to @ref anjay_download_reconnect. + * + * @param anjay Anjay object managing the download process. + * @param dl_handle Download handle previously returned by + * @ref anjay_download. + */ +void anjay_download_suspend(anjay_t *anjay, anjay_download_handle_t dl_handle); + +/** + * Reconnects a download identified by @p dl_handle while retaining the download + * progress. + * + * If the download has been previously suspended using + * @ref anjay_download_suspend, it will be resumed. If the download is suspended + * due to the transport being offline (see @ref anjay_transport_set_online), no + * immediate action is performed, but the suspended state as per @ref + * anjay_download_suspend will be cleared. + * + * This function only schedules the actual reconnect operation. The socket will + * be actually reconnected during subsequent calls to @ref anjay_sched_run. + * + * @param anjay Anjay object managing the download process. + * @param dl_handle Download handle previously returned by + * @ref anjay_download. + * + * @returns 0 for success; -1 if @p dl_handle does not represent a valid + * download handle, or if the reconnect job could not be scheduled + * (e.g. due to an out-of-memory condition). + */ +int anjay_download_reconnect(anjay_t *anjay, anjay_download_handle_t dl_handle); + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/include_public/anjay/fw_update.h b/include_public/anjay/fw_update.h index 55aa5c13..1c6ed012 100644 --- a/include_public/anjay/fw_update.h +++ b/include_public/anjay/fw_update.h @@ -453,6 +453,9 @@ typedef int anjay_fw_update_get_security_config_t( * If this handler is not implemented at all (with the corresponding field set * to NULL), udp_tx_params from anjay_t object are used. * + * NOTE: This callback is called even for non-CoAP 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 . * @@ -610,6 +613,44 @@ int anjay_fw_update_install( */ int anjay_fw_update_set_result(anjay_t *anjay, anjay_fw_update_result_t result); +#ifdef ANJAY_WITH_DOWNLOADER +/** + * Suspends the operation of PULL-mode downloads in the Firmware Update module. + * + * This will have the effect of suspending any ongoing downloads (see + * @ref anjay_download_suspend for details), as well as preventing new downloads + * from being started. + * + * 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 + * @ref anjay_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. + */ +void anjay_fw_update_pull_suspend(anjay_t *anjay); + +/** + * Reconnects any ongoing PULL-mode downloads in the Firmware Update module. + * Which could be disconnected due to connection loss or deliberate suspend. + * In the latter case, when PULL-mode downloads are suspended (see + * @ref anjay_fw_update_pull_suspend), resumes normal operation. + * + * If an ongoing PULL-mode download exists, this will call + * @ref anjay_download_reconnect internally, so you may want to reference the + * documentation of that function for details. + * + * @param anjay Anjay object to operate on. + * + * @returns 0 for success; -1 if @p anjay does not have the Firmware Update + * object installed or if the call to @ref anjay_download_reconnect + * fails. + */ +int anjay_fw_update_pull_reconnect(anjay_t *anjay); +#endif // ANJAY_WITH_DOWNLOADER + #ifdef __cplusplus } #endif diff --git a/include_public/anjay/server.h b/include_public/anjay/server.h index 8b82c8b7..40aed7a9 100644 --- a/include_public/anjay/server.h +++ b/include_public/anjay/server.h @@ -104,7 +104,9 @@ void anjay_server_object_purge(anjay_t *anjay); * * NOTE: If Anjay is compiled with thread safety enabled, the * list that is returned is normally accessed with the Anjay mutex locked. You - * will need to ensure thread safety yourself if using this function. + * will need to ensure thread safety yourself if using this function. It is + * recommended to only call it from callback functions called from within Anjay, + * or in scheduler jobs. * * @param anjay Anjay instance with Server Object installed. * diff --git a/src/anjay_modules/anjay_dm_utils.h b/src/anjay_modules/anjay_dm_utils.h index f200905a..d86a5553 100644 --- a/src/anjay_modules/anjay_dm_utils.h +++ b/src/anjay_modules/anjay_dm_utils.h @@ -163,7 +163,7 @@ const char *_anjay_debug_make_path__(char *buffer, ANJAY_ID_INVALID) #define MAKE_URI_PATH(...) \ - ((const anjay_uri_path_t) URI_PATH_INITIALIZER(__VA_ARGS__)) + ((anjay_uri_path_t) URI_PATH_INITIALIZER(__VA_ARGS__)) #define MAKE_RESOURCE_INSTANCE_PATH(Oid, Iid, Rid, Riid) \ MAKE_URI_PATH(Oid, Iid, Rid, Riid) @@ -787,12 +787,6 @@ int _anjay_dm_verify_instance_present( const anjay_dm_installed_object_t *obj_ptr, anjay_iid_t iid); -#define ANJAY_DM_OID_SECURITY 0 -#define ANJAY_DM_OID_SERVER 1 -#define ANJAY_DM_OID_ACCESS_CONTROL 2 -#define ANJAY_DM_OID_DEVICE 3 -#define ANJAY_DM_OID_FIRMWARE_UPDATE 5 - #define ANJAY_DM_RID_SECURITY_SERVER_URI 0 #define ANJAY_DM_RID_SECURITY_BOOTSTRAP 1 #define ANJAY_DM_RID_SECURITY_MODE 2 @@ -972,9 +966,6 @@ int _anjay_execute_get_arg_value_unlocked(anjay_unlocked_execute_ctx_t *ctx, char *out_buf, size_t buf_size); -AVS_LIST(const anjay_ssid_t) -_anjay_server_get_ssids_unlocked(anjay_unlocked_t *anjay); - VISIBILITY_PRIVATE_HEADER_END #endif /* ANJAY_INCLUDE_ANJAY_MODULES_DM_H */ diff --git a/src/anjay_modules/anjay_utils_core.h b/src/anjay_modules/anjay_utils_core.h index 82a9d31e..26dc8ae3 100644 --- a/src/anjay_modules/anjay_utils_core.h +++ b/src/anjay_modules/anjay_utils_core.h @@ -409,6 +409,12 @@ avs_error_t _anjay_download_unlocked(anjay_unlocked_t *anjay, void _anjay_download_abort_unlocked(anjay_unlocked_t *anjay, anjay_download_handle_t handle); + +void _anjay_download_suspend_unlocked(anjay_unlocked_t *anjay, + anjay_download_handle_t dl_handle); + +int _anjay_download_reconnect_unlocked(anjay_unlocked_t *anjay, + anjay_download_handle_t dl_handle); #endif // ANJAY_WITH_DOWNLOADER bool _anjay_ongoing_registration_exists_unlocked(anjay_unlocked_t *anjay); diff --git a/src/core/anjay_core.c b/src/core/anjay_core.c index 4175550c..2f9f2680 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.4.1" +# define ANJAY_VERSION "3.5.0" #endif // ANJAY_VERSION #ifdef ANJAY_WITH_LWM2M11 @@ -176,8 +176,10 @@ static int init_anjay(anjay_unlocked_t *anjay, #ifdef ANJAY_WITH_LWM2M11 anjay->queue_mode_preference = ANJAY_PREFER_ONLINE_MODE; +#endif // ANJAY_WITH_LWM2M11 -# ifdef WITH_AVS_COAP_TCP +#ifdef WITH_AVS_COAP_TCP +# if defined(ANJAY_WITH_LWM2M11) || defined(ANJAY_WITH_COAP_DOWNLOAD) // completely arbitrary default value static const size_t ANJAY_DEFAULT_COAP_TCP_MAX_OPTIONS_SIZE = 128; if (config->coap_tcp_max_options_size == 0) { @@ -199,8 +201,8 @@ static int init_anjay(anjay_unlocked_t *anjay, ANJAY_DEFAULT_COAP_TCP_REQUEST_TIMEOUT; } anjay->tcp_exchange_timeout = AVS_COAP_DEFAULT_EXCHANGE_MAX_TIME; -# endif // WITH_AVS_COAP_TCP -#endif // ANJAY_WITH_LWM2M11 +# endif // defined(ANJAY_WITH_LWM2M11) || defined(ANJAY_WITH_COAP_DOWNLOAD) +#endif // WITH_AVS_COAP_TCP anjay->in_shared_buffer = avs_shared_buffer_new(config->in_buffer_size); if (!anjay->in_shared_buffer) { @@ -1151,7 +1153,7 @@ avs_error_t anjay_download(anjay_t *anjay_locked, (void) anjay_locked; (void) config; (void) out_handle; - anjay_log(ERROR, _("CoAP download support disabled")); + anjay_log(ERROR, _("Download support disabled")); return avs_errno(AVS_ENOTSUP); #endif // ANJAY_WITH_DOWNLOADER } @@ -1171,7 +1173,7 @@ anjay_download_set_next_block_offset(anjay_t *anjay_locked, (void) anjay_locked; (void) dl_handle; (void) next_block_offset; - anjay_log(ERROR, _("CoAP download support disabled")); + anjay_log(ERROR, _("Download support disabled")); return avs_errno(AVS_ENOTSUP); #endif // ANJAY_WITH_DOWNLOADER } @@ -1181,6 +1183,17 @@ void _anjay_download_abort_unlocked(anjay_unlocked_t *anjay, anjay_download_handle_t handle) { _anjay_downloader_abort(&anjay->downloader, handle); } + +void _anjay_download_suspend_unlocked(anjay_unlocked_t *anjay, + anjay_download_handle_t handle) { + _anjay_downloader_suspend(&anjay->downloader, handle); +} + +int _anjay_download_reconnect_unlocked(anjay_unlocked_t *anjay, + anjay_download_handle_t handle) { + return _anjay_downloader_sched_reconnect_by_handle(&anjay->downloader, + handle); +} #endif // ANJAY_WITH_DOWNLOADER void anjay_download_abort(anjay_t *anjay_locked, @@ -1192,10 +1205,39 @@ void anjay_download_abort(anjay_t *anjay_locked, #else // ANJAY_WITH_DOWNLOADER (void) anjay_locked; (void) handle; - anjay_log(ERROR, _("CoAP download support disabled")); + anjay_log(ERROR, _("Download support disabled")); #endif // ANJAY_WITH_DOWNLOADER } +void anjay_download_suspend(anjay_t *anjay_locked, + anjay_download_handle_t handle) { +#ifdef ANJAY_WITH_DOWNLOADER + ANJAY_MUTEX_LOCK(anjay, anjay_locked); + _anjay_downloader_suspend(&anjay->downloader, handle); + ANJAY_MUTEX_UNLOCK(anjay_locked); +#else // ANJAY_WITH_DOWNLOADER + (void) anjay_locked; + (void) handle; + anjay_log(ERROR, _("Download support disabled")); +#endif // ANJAY_WITH_DOWNLOADER +} + +int anjay_download_reconnect(anjay_t *anjay_locked, + anjay_download_handle_t handle) { + int result = -1; +#ifdef ANJAY_WITH_DOWNLOADER + ANJAY_MUTEX_LOCK(anjay, anjay_locked); + result = _anjay_downloader_sched_reconnect_by_handle(&anjay->downloader, + handle); + ANJAY_MUTEX_UNLOCK(anjay_locked); +#else // ANJAY_WITH_DOWNLOADER + (void) anjay_locked; + (void) handle; + anjay_log(ERROR, _("Download support disabled")); +#endif // ANJAY_WITH_DOWNLOADER + return result; +} + #ifdef ANJAY_WITH_LWM2M11 int anjay_set_queue_mode_preference(anjay_t *anjay_locked, anjay_queue_mode_preference_t preference) { @@ -1207,7 +1249,6 @@ int anjay_set_queue_mode_preference(anjay_t *anjay_locked, case ANJAY_PREFER_ONLINE_MODE: case ANJAY_FORCE_ONLINE_MODE: anjay->queue_mode_preference = preference; - // defined(ANJAY_WITH_CORE_PERSISTENCE) result = 0; } if (result) { diff --git a/src/core/anjay_core.h b/src/core/anjay_core.h index ad3770a9..8d0dde46 100644 --- a/src/core/anjay_core.h +++ b/src/core/anjay_core.h @@ -88,8 +88,6 @@ struct anjay_security_config_cache_t security_config_from_dm_cache; uint16_t udp_listen_port; - // defined(ANJAY_WITH_CORE_PERSISTENCE) - /** * List of known LwM2M servers we may want to be connected to. This is * semantically a map, keyed (and ordered) by SSID. @@ -119,11 +117,13 @@ struct avs_net_dtls_handshake_timeouts_t udp_dtls_hs_tx_params; avs_net_socket_tls_ciphersuites_t default_tls_ciphersuites; -#if defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP) +#ifdef WITH_AVS_COAP_TCP +# if defined(ANJAY_WITH_LWM2M11) || defined(ANJAY_WITH_COAP_DOWNLOAD) size_t coap_tcp_max_options_size; avs_time_duration_t coap_tcp_request_timeout; avs_time_duration_t tcp_exchange_timeout; -#endif // defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP) +# endif // defined(ANJAY_WITH_LWM2M11) || defined(ANJAY_WITH_COAP_DOWNLOAD) +#endif // WITH_AVS_COAP_TCP anjay_scheduled_notify_t scheduled_notify; diff --git a/src/core/anjay_downloader.h b/src/core/anjay_downloader.h index 57ac8529..3e2924e3 100644 --- a/src/core/anjay_downloader.h +++ b/src/core/anjay_downloader.h @@ -92,6 +92,12 @@ _anjay_downloader_set_next_block_offset(anjay_downloader_t *dl, void _anjay_downloader_abort(anjay_downloader_t *dl, anjay_download_handle_t handle); +void _anjay_downloader_suspend(anjay_downloader_t *dl, + anjay_download_handle_t handle); + +int _anjay_downloader_sched_reconnect_by_handle(anjay_downloader_t *dl, + anjay_download_handle_t handle); + bool _anjay_downloader_same_socket_transfer_ongoing(anjay_downloader_t *dl, avs_net_socket_t *socket); @@ -101,8 +107,8 @@ void _anjay_downloader_suspend_same_socket(anjay_downloader_t *dl, void _anjay_downloader_abort_same_socket(anjay_downloader_t *dl, avs_net_socket_t *socket); -int _anjay_downloader_sched_reconnect(anjay_downloader_t *dl, - anjay_transport_set_t transport_set); +int _anjay_downloader_sched_reconnect_by_transports( + anjay_downloader_t *dl, anjay_transport_set_t transport_set); int _anjay_downloader_sync_online_transports(anjay_downloader_t *dl); diff --git a/src/core/anjay_lwm2m_send.c b/src/core/anjay_lwm2m_send.c index b84fb950..cf32d805 100644 --- a/src/core/anjay_lwm2m_send.c +++ b/src/core/anjay_lwm2m_send.c @@ -195,12 +195,19 @@ static void response_handler(avs_coap_ctx_t *ctx, const avs_coap_client_async_response_t *response, avs_error_t err, void *entry_) { - (void) ctx; - (void) exchange_id; - (void) err; anjay_send_entry_t *entry = (anjay_send_entry_t *) entry_; assert(entry); assert(avs_coap_exchange_id_equal(exchange_id, entry->exchange_status.id)); + if (avs_is_ok(err)) { + anjay_server_info_t *server = + _anjay_servers_find_active(entry->anjay, entry->target_ssid); + if (server) { + _anjay_connection_mark_stable((anjay_connection_ref_t) { + .server = server, + .conn_type = ANJAY_CONNECTION_PRIMARY + }); + } + } if (entry->finished_handler) { static const int STATE_TO_RESULT[] = { [AVS_COAP_CLIENT_REQUEST_OK] = ANJAY_SEND_SUCCESS, diff --git a/src/core/anjay_servers_private.h b/src/core/anjay_servers_private.h index d6b86a82..1d89aa2b 100644 --- a/src/core/anjay_servers_private.h +++ b/src/core/anjay_servers_private.h @@ -287,7 +287,7 @@ int _anjay_servers_foreach_active(anjay_unlocked_t *anjay, * message) * - will send REGISTER message if the server has no valid registration * (i.e. it's new or its session has been replaced instead of resumed), - * or if the aforementioned attept to send Update failed (Updates + * or if the aforementioned attempt to send Update failed (Updates * automatically degenerate to Registers within this function, and * internal structures tracking registration state are updated * accordingly) - NOTE THAT THIS IS THE **ONLY** PLACE IN THE ENTIRE CODE @@ -490,7 +490,14 @@ bool _anjay_connection_ready_for_outgoing_message(anjay_connection_ref_t ref); * connection as "stable", which makes it eligible for reconnection if * communication error occurs from now on. * - * It is called from _anjay_server_on_refreshed(). + * It is generally called whenever an incoming message is received on a given + * connection. Specifically: + * - from handle_register_response() and receive_update_response() in + * anjay_register.c + * - from _anjay_server_on_refreshed() in anjay_activate.c for the Bootstrap + * Server connection + * - from handle_notify_delivery() in anjay_observe_core.c + * - from response_handler() in anjay_lwm2m_send.c */ void _anjay_connection_mark_stable(anjay_connection_ref_t ref); diff --git a/src/core/downloader/anjay_coap.c b/src/core/downloader/anjay_coap.c index b72387c5..e8e942a6 100644 --- a/src/core/downloader/anjay_coap.c +++ b/src/core/downloader/anjay_coap.c @@ -433,18 +433,16 @@ static void suspend_coap_transfer(anjay_download_ctx_t *ctx_) { } } -static avs_error_t sched_download_resumption(anjay_coap_download_ctx_t *ctx) { +static avs_error_t sched_start_download(anjay_coap_download_ctx_t *ctx) { anjay_unlocked_t *anjay = _anjay_downloader_get_anjay(ctx->common.dl); if (AVS_SCHED_NOW(anjay->sched, &ctx->job_start, start_download_job, &ctx->common.id, sizeof(ctx->common.id))) { dl_log(WARNING, - _("could not schedule resumption for download id " - "= ") "%" PRIuPTR, + _("could not schedule download job for id = ") "%" PRIuPTR, ctx->common.id); return avs_errno(AVS_ENOMEM); } - dl_log(INFO, _("scheduling download ") "%" PRIuPTR _(" resumption"), - ctx->common.id); + dl_log(INFO, _("scheduling download ") "%" PRIuPTR, ctx->common.id); return AVS_OK; } @@ -459,34 +457,28 @@ reconnect_coap_transfer(AVS_LIST(anjay_download_ctx_t) *ctx_ptr) { // the Registration be sent even if NSTART=1. avs_coap_exchange_cancel(ctx->coap, ctx->exchange_id); assert(!avs_coap_exchange_id_valid(ctx->exchange_id)); - return sched_download_resumption(ctx); + return sched_start_download(ctx); } - char hostname[ANJAY_MAX_URL_HOSTNAME_SIZE]; - char port[ANJAY_MAX_URL_PORT_SIZE]; - - avs_error_t err; - if (avs_is_err((err = avs_net_socket_get_remote_hostname( - ctx->socket, hostname, sizeof(hostname)))) - || avs_is_err((err = avs_net_socket_get_remote_port( - ctx->socket, port, sizeof(port)))) - || ((void) avs_net_socket_shutdown(ctx->socket), 0) - || ((void) avs_net_socket_close(ctx->socket), 0) - || avs_is_err((err = avs_net_socket_connect(ctx->socket, hostname, - port)))) { + + avs_net_socket_shutdown(ctx->socket); + avs_net_socket_close(ctx->socket); + avs_error_t err = + avs_net_socket_connect(ctx->socket, ctx->uri.host, ctx->uri.port); + if (avs_is_err(err)) { dl_log(WARNING, - _("could not reconnect socket for download id = ") "%" PRIuPTR, + _("could not connect socket for download id = ") "%" PRIuPTR, ctx->common.id); return err; } else { // A new DTLS session requires resetting the CoAP context. // If we manage to resume the session, we can simply continue sending // retransmissions as if nothing happened. - if (!_anjay_was_session_resumed(ctx->socket) + if ((!ctx->coap || !_anjay_was_session_resumed(ctx->socket)) && avs_is_err((err = reset_coap_ctx(ctx)))) { return err; } if (!avs_coap_exchange_id_valid(ctx->exchange_id)) { - return sched_download_resumption(ctx); + return sched_start_download(ctx); } } return AVS_OK; @@ -634,11 +626,6 @@ _anjay_downloader_coap_ctx_new(anjay_downloader_t *dl, })))) { anjay_log(ERROR, _("could not configure DANE TLSA record")); _anjay_socket_cleanup(anjay, &ctx->socket); - } else if (avs_is_err((err = avs_net_socket_connect(ctx->socket, - ctx->uri.host, - ctx->uri.port)))) { - dl_log(ERROR, _("could not connect CoAP socket")); - _anjay_socket_cleanup(anjay, &ctx->socket); } if (!ctx->socket) { assert(avs_is_err(err)); @@ -673,14 +660,8 @@ _anjay_downloader_coap_ctx_new(anjay_downloader_t *dl, } # endif // WITH_AVS_COAP_UDP - if (!ctx->common.same_socket_download - && avs_is_err((err = reset_coap_ctx(ctx)))) { - goto error; - } - - if (AVS_SCHED_NOW(anjay->sched, &ctx->job_start, start_download_job, - &ctx->common.id, sizeof(ctx->common.id))) { - dl_log(ERROR, _("could not schedule download job")); + 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); goto error; } diff --git a/src/core/downloader/anjay_downloader.c b/src/core/downloader/anjay_downloader.c index f1076357..3bc688b6 100644 --- a/src/core/downloader/anjay_downloader.c +++ b/src/core/downloader/anjay_downloader.c @@ -49,12 +49,12 @@ int _anjay_downloader_init(anjay_downloader_t *dl, anjay_unlocked_t *anjay) { return 0; } -static void cleanup_transfer(AVS_LIST(anjay_download_ctx_t) *ctx) { - assert(ctx); - assert(*ctx); - assert((*ctx)->common.vtable); +static void cleanup_transfer(AVS_LIST(anjay_download_ctx_t) *ctx_ptr) { + assert(ctx_ptr); + assert(*ctx_ptr); + assert((*ctx_ptr)->common.vtable); - (*ctx)->common.vtable->cleanup(ctx); + (*ctx_ptr)->common.vtable->cleanup(ctx_ptr); } avs_error_t @@ -88,44 +88,44 @@ static void call_on_download_finished(anjay_download_ctx_t *ctx, ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked); } -void _anjay_downloader_abort_transfer(AVS_LIST(anjay_download_ctx_t) *ctx, +void _anjay_downloader_abort_transfer(AVS_LIST(anjay_download_ctx_t) *ctx_ptr, anjay_download_status_t status) { - assert(ctx); - assert(*ctx); + assert(ctx_ptr); + assert(*ctx_ptr); switch (status.result) { case ANJAY_DOWNLOAD_FINISHED: dl_log(TRACE, _("aborting download id = ") "%" PRIuPTR _( ": finished successfully"), - (*ctx)->common.id); + (*ctx_ptr)->common.id); break; case ANJAY_DOWNLOAD_ERR_FAILED: dl_log(TRACE, _("aborting download id = ") "%" PRIuPTR _( ": failed, error: ") "%s", - (*ctx)->common.id, AVS_COAP_STRERROR(status.details.error)); + (*ctx_ptr)->common.id, AVS_COAP_STRERROR(status.details.error)); break; case ANJAY_DOWNLOAD_ERR_INVALID_RESPONSE: dl_log(TRACE, _("aborting download id = ") "%" PRIuPTR _( ": invalid response, status code: ") "%d", - (*ctx)->common.id, status.details.status_code); + (*ctx_ptr)->common.id, status.details.status_code); break; case ANJAY_DOWNLOAD_ERR_EXPIRED: dl_log(TRACE, _("aborting download id = ") "%" PRIuPTR _(": expired"), - (*ctx)->common.id); + (*ctx_ptr)->common.id); break; case ANJAY_DOWNLOAD_ERR_ABORTED: dl_log(TRACE, _("aborting download id = ") "%" PRIuPTR _(": aborted"), - (*ctx)->common.id); + (*ctx_ptr)->common.id); break; } - call_on_download_finished(*ctx, status); + call_on_download_finished(*ctx_ptr, status); - avs_sched_del(&(*ctx)->common.reconnect_job_handle); - cleanup_transfer(ctx); + avs_sched_del(&(*ctx_ptr)->common.reconnect_job_handle); + cleanup_transfer(ctx_ptr); } static void suspend_transfer(anjay_download_ctx_t *ctx) { @@ -134,14 +134,14 @@ static void suspend_transfer(anjay_download_ctx_t *ctx) { ctx->common.vtable->suspend(ctx); } -static void reconnect_transfer(AVS_LIST(anjay_download_ctx_t) *ctx) { - assert(ctx); - assert(*ctx); - assert((*ctx)->common.vtable); +static void reconnect_transfer(AVS_LIST(anjay_download_ctx_t) *ctx_ptr) { + assert(ctx_ptr); + assert(*ctx_ptr); + assert((*ctx_ptr)->common.vtable); - avs_error_t err = (*ctx)->common.vtable->reconnect(ctx); + avs_error_t err = (*ctx_ptr)->common.vtable->reconnect(ctx_ptr); if (avs_is_err(err)) { - _anjay_downloader_abort_transfer(ctx, + _anjay_downloader_abort_transfer(ctx_ptr, _anjay_download_status_failed(err)); } } @@ -170,13 +170,13 @@ get_ctx_socket_transport(anjay_download_ctx_t *ctx) { static AVS_LIST(anjay_download_ctx_t) * find_ctx_ptr_by_socket(anjay_downloader_t *dl, avs_net_socket_t *socket) { assert(socket); - AVS_LIST(anjay_download_ctx_t) *ctx; - AVS_LIST_FOREACH_PTR(ctx, &dl->downloads) { - if ((*ctx)->common.same_socket_download) { + AVS_LIST(anjay_download_ctx_t) *ctx_ptr; + AVS_LIST_FOREACH_PTR(ctx_ptr, &dl->downloads) { + if ((*ctx_ptr)->common.same_socket_download) { continue; } - if (get_ctx_socket(*ctx) == socket) { - return ctx; + if (get_ctx_socket(*ctx_ptr) == socket) { + return ctx_ptr; } } return NULL; @@ -215,10 +215,10 @@ int _anjay_downloader_get_sockets(anjay_downloader_t *dl, AVS_LIST(anjay_download_ctx_t) * _anjay_downloader_find_ctx_ptr_by_id(anjay_downloader_t *dl, uintptr_t id) { - AVS_LIST(anjay_download_ctx_t) *ctx; - AVS_LIST_FOREACH_PTR(ctx, &dl->downloads) { - if ((*ctx)->common.id == id) { - return ctx; + AVS_LIST(anjay_download_ctx_t) *ctx_ptr; + AVS_LIST_FOREACH_PTR(ctx_ptr, &dl->downloads) { + if ((*ctx_ptr)->common.id == id) { + return ctx_ptr; } } @@ -229,15 +229,16 @@ int _anjay_downloader_handle_packet(anjay_downloader_t *dl, avs_net_socket_t *socket) { assert(&_anjay_downloader_get_anjay(dl)->downloader == dl); - AVS_LIST(anjay_download_ctx_t) *ctx = find_ctx_ptr_by_socket(dl, socket); - if (!ctx) { + AVS_LIST(anjay_download_ctx_t) *ctx_ptr = + find_ctx_ptr_by_socket(dl, socket); + if (!ctx_ptr) { // unknown socket return -1; } - assert(*ctx); - assert((*ctx)->common.vtable); - (*ctx)->common.vtable->handle_packet(ctx); + assert(*ctx_ptr); + assert((*ctx_ptr)->common.vtable); + (*ctx_ptr)->common.vtable->handle_packet(ctx_ptr); return 0; } @@ -340,28 +341,92 @@ _anjay_downloader_set_next_block_offset(anjay_downloader_t *dl, size_t next_block_offset) { uintptr_t id = (uintptr_t) handle; - AVS_LIST(anjay_download_ctx_t) *ctx = + AVS_LIST(anjay_download_ctx_t) *ctx_ptr = _anjay_downloader_find_ctx_ptr_by_id(dl, id); - if (!ctx) { + if (!ctx_ptr) { dl_log(DEBUG, _("download id = ") "%" PRIuPTR _(" not found"), id); return avs_errno(AVS_ENOENT); } - return (*ctx)->common.vtable->set_next_block_offset(*ctx, - next_block_offset); + return (*ctx_ptr)->common.vtable->set_next_block_offset(*ctx_ptr, + next_block_offset); } void _anjay_downloader_abort(anjay_downloader_t *dl, anjay_download_handle_t handle) { uintptr_t id = (uintptr_t) handle; - AVS_LIST(anjay_download_ctx_t) *ctx = + AVS_LIST(anjay_download_ctx_t) *ctx_ptr = _anjay_downloader_find_ctx_ptr_by_id(dl, id); - if (!ctx) { + if (!ctx_ptr) { dl_log(DEBUG, _("download id = ") "%" PRIuPTR _(" not found (expired?)"), id); } else { - _anjay_downloader_abort_transfer(ctx, _anjay_download_status_aborted()); + _anjay_downloader_abort_transfer(ctx_ptr, + _anjay_download_status_aborted()); + } +} + +void _anjay_downloader_suspend(anjay_downloader_t *dl, + anjay_download_handle_t handle) { + uintptr_t id = (uintptr_t) handle; + + AVS_LIST(anjay_download_ctx_t) *ctx_ptr = + _anjay_downloader_find_ctx_ptr_by_id(dl, id); + if (!ctx_ptr) { + dl_log(DEBUG, + _("download id = ") "%" PRIuPTR _(" not found (expired?)"), id); + } else if (!(*ctx_ptr)->common.administratively_suspended) { + (*ctx_ptr)->common.administratively_suspended = true; + suspend_transfer(*ctx_ptr); + } +} + +static void reconnect_job(avs_sched_t *sched, const void *id_ptr) { + anjay_t *anjay_locked = _anjay_get_from_sched(sched); + ANJAY_MUTEX_LOCK(anjay, anjay_locked); + uintptr_t id = *(const uintptr_t *) id_ptr; + AVS_LIST(anjay_download_ctx_t) *ctx_ptr = + _anjay_downloader_find_ctx_ptr_by_id(&anjay->downloader, id); + if (!ctx_ptr) { + dl_log(DEBUG, + _("download id = ") "%" PRIuPTR _(" not found (expired?)"), id); + } else if (!(*ctx_ptr)->common.administratively_suspended) { + if (_anjay_socket_transport_included(anjay->online_transports, + get_ctx_socket_transport( + *ctx_ptr))) { + reconnect_transfer(ctx_ptr); + } else { + suspend_transfer(*ctx_ptr); + } + } + ANJAY_MUTEX_UNLOCK(anjay_locked); +} + +int _anjay_downloader_sched_reconnect_ctx(anjay_download_ctx_t *ctx) { + return AVS_SCHED_NOW(_anjay_downloader_get_anjay(ctx->common.dl)->sched, + &ctx->common.reconnect_job_handle, reconnect_job, + &ctx->common.id, sizeof(ctx->common.id)); +} + +int _anjay_downloader_sched_reconnect_by_handle( + anjay_downloader_t *dl, anjay_download_handle_t handle) { + uintptr_t id = (uintptr_t) handle; + + AVS_LIST(anjay_download_ctx_t) *ctx_ptr = + _anjay_downloader_find_ctx_ptr_by_id(dl, id); + if (!ctx_ptr) { + dl_log(DEBUG, + _("download id = ") "%" PRIuPTR _(" not found (expired?)"), id); + return -1; } + (*ctx_ptr)->common.administratively_suspended = false; + if (!(*ctx_ptr)->common.reconnect_job_handle + && _anjay_socket_transport_included( + _anjay_downloader_get_anjay(dl)->online_transports, + get_ctx_socket_transport(*ctx_ptr))) { + return _anjay_downloader_sched_reconnect_ctx(*ctx_ptr); + } + return 0; } bool _anjay_downloader_same_socket_transfer_ongoing(anjay_downloader_t *dl, @@ -385,12 +450,13 @@ void _anjay_downloader_suspend_same_socket(anjay_downloader_t *dl, if (!socket) { return; } - AVS_LIST(anjay_download_ctx_t) *ctx; + AVS_LIST(anjay_download_ctx_t) *ctx_ptr; AVS_LIST(anjay_download_ctx_t) helper; - AVS_LIST_DELETABLE_FOREACH_PTR(ctx, helper, &dl->downloads) { - if ((*ctx)->common.same_socket_download - && get_ctx_socket(*ctx) == socket) { - suspend_transfer(*ctx); + AVS_LIST_DELETABLE_FOREACH_PTR(ctx_ptr, helper, &dl->downloads) { + if (!(*ctx_ptr)->common.administratively_suspended + && (*ctx_ptr)->common.same_socket_download + && get_ctx_socket(*ctx_ptr) == socket) { + suspend_transfer(*ctx_ptr); } } } @@ -401,51 +467,26 @@ void _anjay_downloader_abort_same_socket(anjay_downloader_t *dl, if (!socket) { return; } - AVS_LIST(anjay_download_ctx_t) *ctx; + AVS_LIST(anjay_download_ctx_t) *ctx_ptr; AVS_LIST(anjay_download_ctx_t) helper; - AVS_LIST_DELETABLE_FOREACH_PTR(ctx, helper, &dl->downloads) { - if ((*ctx)->common.same_socket_download - && get_ctx_socket(*ctx) == socket) { + AVS_LIST_DELETABLE_FOREACH_PTR(ctx_ptr, helper, &dl->downloads) { + if ((*ctx_ptr)->common.same_socket_download + && get_ctx_socket(*ctx_ptr) == socket) { _anjay_downloader_abort_transfer(&dl->downloads, _anjay_download_status_aborted()); } } } -static void reconnect_job(avs_sched_t *sched, const void *id_ptr) { - anjay_t *anjay_locked = _anjay_get_from_sched(sched); - ANJAY_MUTEX_LOCK(anjay, anjay_locked); - uintptr_t id = *(const uintptr_t *) id_ptr; - AVS_LIST(anjay_download_ctx_t) *ctx_ptr = - _anjay_downloader_find_ctx_ptr_by_id(&anjay->downloader, id); - if (!ctx_ptr) { - dl_log(DEBUG, - _("download id = ") "%" PRIuPTR _(" not found (expired?)"), id); - } else if (_anjay_socket_transport_included(anjay->online_transports, - get_ctx_socket_transport( - *ctx_ptr))) { - reconnect_transfer(ctx_ptr); - } else { - suspend_transfer(*ctx_ptr); - } - ANJAY_MUTEX_UNLOCK(anjay_locked); -} - -static int schedule_reconnect(anjay_download_ctx_t *ctx) { - return AVS_SCHED_NOW(_anjay_downloader_get_anjay(ctx->common.dl)->sched, - &ctx->common.reconnect_job_handle, reconnect_job, - &ctx->common.id, sizeof(ctx->common.id)); -} - -int _anjay_downloader_sched_reconnect(anjay_downloader_t *dl, - anjay_transport_set_t transport_set) { +int _anjay_downloader_sched_reconnect_by_transports( + anjay_downloader_t *dl, anjay_transport_set_t transport_set) { int result = 0; AVS_LIST(anjay_download_ctx_t) ctx; AVS_LIST_FOREACH(ctx, dl->downloads) { if (!ctx->common.reconnect_job_handle && _anjay_socket_transport_included( transport_set, get_ctx_socket_transport(ctx))) { - int partial_result = schedule_reconnect(ctx); + int partial_result = _anjay_downloader_sched_reconnect_ctx(ctx); if (!result && partial_result) { result = partial_result; } @@ -464,7 +505,7 @@ int _anjay_downloader_sync_online_transports(anjay_downloader_t *dl) { get_ctx_socket_transport(ctx)) != _anjay_socket_is_online( get_ctx_socket(ctx))) { - int partial_result = schedule_reconnect(ctx); + int partial_result = _anjay_downloader_sched_reconnect_ctx(ctx); if (!result && partial_result) { result = partial_result; } diff --git a/src/core/downloader/anjay_http.c b/src/core/downloader/anjay_http.c index 89f52f63..f3432c1d 100644 --- a/src/core/downloader/anjay_http.c +++ b/src/core/downloader/anjay_http.c @@ -154,10 +154,14 @@ handle_http_packet_with_locked_buffer(AVS_LIST(anjay_download_ctx_t) *ctx_ptr, } nonblock_read_ready = avs_stream_nonblock_read_ready(ctx->stream); } while (nonblock_read_ready); - int result = AVS_RESCHED_DELAYED(&ctx->next_action_job, - AVS_NET_SOCKET_DEFAULT_RECV_TIMEOUT); - assert(!result); - (void) result; + // NOTE: ctx->next_action_job might be NULL + // 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); + assert(!result); + (void) result; + } } static void handle_http_packet(AVS_LIST(anjay_download_ctx_t) *ctx_ptr) { diff --git a/src/core/downloader/anjay_private.h b/src/core/downloader/anjay_private.h index 27e815d9..daf9ebb4 100644 --- a/src/core/downloader/anjay_private.h +++ b/src/core/downloader/anjay_private.h @@ -43,6 +43,7 @@ typedef struct { anjay_download_finished_handler_t *on_download_finished; void *user_data; + bool administratively_suspended; // This download re-uses socket of the already existing connection. bool same_socket_download; } anjay_download_ctx_common_t; @@ -55,9 +56,11 @@ _anjay_downloader_get_anjay(anjay_downloader_t *dl) { AVS_LIST(anjay_download_ctx_t) * _anjay_downloader_find_ctx_ptr_by_id(anjay_downloader_t *dl, uintptr_t id); -void _anjay_downloader_abort_transfer(AVS_LIST(anjay_download_ctx_t) *ctx, +void _anjay_downloader_abort_transfer(AVS_LIST(anjay_download_ctx_t) *ctx_ptr, anjay_download_status_t status); +int _anjay_downloader_sched_reconnect_ctx(anjay_download_ctx_t *ctx); + avs_error_t _anjay_downloader_call_on_next_block(anjay_download_ctx_common_t *ctx, const uint8_t *data, diff --git a/src/core/observe/anjay_observe_core.c b/src/core/observe/anjay_observe_core.c index 79b406c6..26fe0d05 100644 --- a/src/core/observe/anjay_observe_core.c +++ b/src/core/observe/anjay_observe_core.c @@ -98,7 +98,6 @@ static void delete_value(anjay_unlocked_t *anjay, } } AVS_LIST_DELETE(value_ptr); - // defined(ANJAY_WITH_CORE_PERSISTENCE) } static inline const anjay_observe_path_entry_t * @@ -559,7 +558,6 @@ static int insert_new_value(anjay_observe_connection_entry_t *conn_state, conn_state->unsent = res_value; } observation->last_unsent = res_value; - // defined(ANJAY_WITH_CORE_PERSISTENCE) return 0; } @@ -1239,7 +1237,6 @@ static int observe_handle(anjay_connection_ref_t ref, delete_connection_if_empty(conn_ptr); } } - // defined(ANJAY_WITH_CORE_PERSISTENCE) delete_batch_array(&batches, paths->count); if (result && !send_result) { @@ -1537,17 +1534,20 @@ handle_notify_delivery(avs_coap_ctx_t *coap, avs_error_t err, void *conn_) { anjay_observe_connection_entry_t *conn = (anjay_observe_connection_entry_t *) conn_; + bool is_error = is_error_value(conn->unsent); conn->notify_exchange_id = AVS_COAP_EXCHANGE_ID_INVALID; cleanup_serialization_state(&conn->serialization_state); if (avs_is_ok(err)) { - assert(!is_error_value(conn->unsent)); + _anjay_connection_mark_stable(conn->conn_ref); if (conn->unsent->reliability_hint == AVS_COAP_NOTIFY_PREFER_CONFIRMABLE) { conn->unsent->ref->last_confirmable = avs_time_real_now(); } value_sent(conn); } - on_entry_flushed(conn, err); + if (!is_error || avs_is_err(err)) { + on_entry_flushed(conn, err); + } } static void flush_next_unsent(anjay_observe_connection_entry_t *conn) { @@ -1589,21 +1589,32 @@ static void flush_next_unsent(anjay_observe_connection_entry_t *conn) { } if (avs_is_err(err)) { on_entry_flushed(conn, err); - } else if (avs_is_err( - (err = avs_coap_notify_async( - coap, &conn->notify_exchange_id, - (avs_coap_observe_id_t) { - .token = observation->token - }, - &response, conn->unsent->reliability_hint, - payload_writer, conn, - handle_notify_delivery, conn)))) { - if (connection_exists(anjay, conn)) { - cleanup_serialization_state(&conn->serialization_state); - on_entry_flushed(conn, err); + } else { + // NOTE: handle_notify_delivery() or _anjay_observe_cancel_handler() + // may be called by avs_coap_notify_async(), which may invalidate + // conn. That's also why we need this intermediate exchange_id + avs_coap_exchange_id_t exchange_id = AVS_COAP_EXCHANGE_ID_INVALID; + err = avs_coap_notify_async(coap, &exchange_id, + (avs_coap_observe_id_t) { + .token = observation->token + }, + &response, + conn->unsent->reliability_hint, + payload_writer, conn, + handle_notify_delivery, conn); + if (avs_is_err(err)) { + if (connection_exists(anjay, conn)) { + cleanup_serialization_state(&conn->serialization_state); + on_entry_flushed(conn, err); + } + } else { + if (connection_exists(anjay, conn)) { + assert(!avs_coap_exchange_id_valid( + conn->notify_exchange_id)); + conn->notify_exchange_id = exchange_id; + } } } - // defined(ANJAY_WITH_CORE_PERSISTENCE) } avs_coap_options_cleanup(&response.options); # ifndef ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE @@ -1616,7 +1627,7 @@ static void flush_next_unsent(anjay_observe_connection_entry_t *conn) { # ifdef ANJAY_WITH_COMMUNICATION_TIMESTAMP_API if (avs_is_ok(err)) { - _anjay_server_set_last_communication_time(conn->conn_ref.server); + _anjay_server_set_last_communication_time(conn_ref.server); } # endif // ANJAY_WITH_COMMUNICATION_TIMESTAMP_API } diff --git a/src/core/servers/anjay_activate.c b/src/core/servers/anjay_activate.c index ef723ac6..bb09924b 100644 --- a/src/core/servers/anjay_activate.c +++ b/src/core/servers/anjay_activate.c @@ -128,7 +128,6 @@ query_server_communication_retry_params(anjay_server_info_t *server) { void _anjay_server_on_failure(anjay_server_info_t *server, const char *debug_msg) { - // defined(ANJAY_WITH_CORE_PERSISTENCE) _anjay_server_clean_active_data(server); server->refresh_failed = true; @@ -240,7 +239,6 @@ void _anjay_server_on_server_communication_timeout( server->anjay, server->ssid, AVS_TIME_DURATION_ZERO)) { server->refresh_failed = true; } else { - // defined(ANJAY_WITHOUT_TELIT_STABLE_CONNECTION_TIMEOUT_REG_FAILURE) _anjay_server_on_server_communication_error(server, avs_errno(AVS_EBADF)); } @@ -258,7 +256,6 @@ void _anjay_server_on_fatal_coap_error(anjay_connection_ref_t conn_ref, && _anjay_server_registration_expired(conn_ref.server)) { _anjay_server_on_server_communication_error(conn_ref.server, err); } else { - // defined(ANJAY_WITH_CORE_PERSISTENCE) _anjay_connection_internal_clean_socket(conn_ref.server->anjay, conn); _anjay_active_server_refresh(conn_ref.server); } @@ -568,7 +565,6 @@ void _anjay_disable_server_with_timeout_from_dm_sync( server_iid); server->reactivate_time = avs_time_real_add(avs_time_real_now(), disable_timeout); - // defined(ANJAY_WITH_TELIT_CUSTOM_FEATURES) server->disabled_explicitly = true; if (deactivate_server(server)) { anjay_log(ERROR, _("unable to deactivate server: ") "%" PRIu16, @@ -617,7 +613,6 @@ static int disable_server_impl(anjay_unlocked_t *anjay, server->reactivate_time = avs_time_real_add(avs_time_real_now(), timeout); } - // defined(ANJAY_WITH_CORE_PERSISTENCE) return 0; } @@ -640,7 +635,6 @@ int anjay_disable_server(anjay_t *anjay_locked, anjay_ssid_t ssid) { void _anjay_disable_server_with_explicit_timeout_sync( anjay_server_info_t *server) { - // defined(ANJAY_WITH_TELIT_CUSTOM_FEATURES) server->disabled_explicitly = true; if (deactivate_server(server)) { anjay_log(ERROR, _("unable to deactivate server: ") "%" PRIu16, @@ -704,7 +698,6 @@ int _anjay_enable_server_unlocked(anjay_unlocked_t *anjay, anjay_ssid_t ssid) { } (*server_ptr)->reactivate_time = avs_time_real_now(); - // defined(ANJAY_WITH_CORE_PERSISTENCE) return _anjay_server_sched_activate(*server_ptr); } diff --git a/src/core/servers/anjay_connection_ip.c b/src/core/servers/anjay_connection_ip.c index 8ee8ab47..68b13237 100644 --- a/src/core/servers/anjay_connection_ip.c +++ b/src/core/servers/anjay_connection_ip.c @@ -25,6 +25,7 @@ #define ANJAY_SERVERS_INTERNALS #include "anjay_connections_internal.h" +#include "anjay_server_connections.h" #include "anjay_servers_internal.h" #ifdef ANJAY_TEST @@ -141,7 +142,6 @@ static avs_error_t connect_socket(anjay_unlocked_t *anjay, if (strcmp(local_port, connection->nontransient_state.last_local_port) != 0) { strcpy(connection->nontransient_state.last_local_port, local_port); - // defined(ANJAY_WITH_CORE_PERSISTENCE) } return AVS_OK; } @@ -149,8 +149,10 @@ static avs_error_t connect_socket(anjay_unlocked_t *anjay, || (defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP)) */ #if defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP) -static int ensure_tcp_coap_context(anjay_unlocked_t *anjay, - anjay_server_connection_t *connection) { + +static int ensure_tcp_coap_context(anjay_connection_ref_t ref) { + anjay_unlocked_t *anjay = _anjay_from_server(ref.server); + anjay_server_connection_t *connection = _anjay_get_server_connection(ref); if (!connection->coap_ctx) { connection->coap_ctx = avs_coap_tcp_ctx_create( _anjay_get_coap_sched(anjay), anjay->in_shared_buffer, @@ -164,11 +166,14 @@ static int ensure_tcp_coap_context(anjay_unlocked_t *anjay, return 0; } + #endif // defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP) #ifdef WITH_AVS_COAP_UDP -static int ensure_udp_coap_context(anjay_unlocked_t *anjay, - anjay_server_connection_t *connection) { + +static int ensure_udp_coap_context(anjay_connection_ref_t ref) { + anjay_unlocked_t *anjay = _anjay_from_server(ref.server); + anjay_server_connection_t *connection = _anjay_get_server_connection(ref); if (!connection->coap_ctx) { connection->coap_ctx = avs_coap_udp_ctx_create( _anjay_get_coap_sched(anjay), &anjay->udp_tx_params, @@ -294,6 +299,7 @@ const anjay_connection_type_definition_t ANJAY_CONNECTION_DEF_UDP = { .ensure_coap_context = ensure_udp_coap_context, .connect_socket = connect_udp_socket }; + #endif // WITH_AVS_COAP_UDP #if defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP) diff --git a/src/core/servers/anjay_connections.c b/src/core/servers/anjay_connections.c index 3db160d7..323dbdd8 100644 --- a/src/core/servers/anjay_connections.c +++ b/src/core/servers/anjay_connections.c @@ -337,7 +337,10 @@ int _anjay_connection_ensure_coap_context(anjay_server_info_t *server, const anjay_connection_type_definition_t *def = get_connection_type_def(conn->transport); assert(def); - int result = def->ensure_coap_context(server->anjay, conn); + int result = def->ensure_coap_context((anjay_connection_ref_t) { + .server = server, + .conn_type = conn_type + }); if (!result) { update_exchange_timeout(server, conn_type); } @@ -358,7 +361,6 @@ avs_error_t _anjay_server_connection_internal_bring_online( if (_anjay_connection_is_online(connection)) { anjay_log(DEBUG, _("socket already connected")); - connection->state = ANJAY_SERVER_CONNECTION_STABLE; connection->needs_observe_flush = true; return AVS_OK; } @@ -499,7 +501,6 @@ recreate_socket(anjay_unlocked_t *anjay, avs_net_socket_shutdown(connection->conn_socket_); avs_net_socket_close(connection->conn_socket_); } - // defined(ANJAY_WITH_CORE_PERSISTENCE) } _anjay_security_config_cache_cleanup(&security_config_cache); return err; @@ -582,7 +583,6 @@ static avs_error_t refresh_connection(anjay_server_info_t *server, } else { // Disabled trigger connection does not matter much, // so treat it as stable - // defined(ANJAY_WITH_CORE_PERSISTENCE) _anjay_connection_internal_clean_socket(server->anjay, out_connection); out_connection->state = ANJAY_SERVER_CONNECTION_STABLE; @@ -655,14 +655,12 @@ void _anjay_server_connections_refresh( connection_cleanup(server->anjay, primary_conn); primary_conn->transport = server_info.transport_info->transport; server->registration_info.expire_time = AVS_TIME_REAL_INVALID; - // defined(ANJAY_WITH_CORE_PERSISTENCE) } anjay_connection_type_t conn_type; ANJAY_CONNECTION_TYPE_FOREACH(conn_type) { anjay_server_connection_t *connection = _anjay_connection_get(&server->connections, conn_type); - connection->state = ANJAY_SERVER_CONNECTION_IN_PROGRESS; #ifndef ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE avs_sched_del(&connection->queue_mode_close_socket_clb); #endif // ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE diff --git a/src/core/servers/anjay_connections.h b/src/core/servers/anjay_connections.h index eb9e62c8..ed24c7b4 100644 --- a/src/core/servers/anjay_connections.h +++ b/src/core/servers/anjay_connections.h @@ -33,10 +33,10 @@ typedef struct { typedef enum { /** - * _anjay_connections_refresh() has just been called, and the connection has + * Server connection object has just been created, and the connection has * not yet reached a usable state. */ - ANJAY_SERVER_CONNECTION_IN_PROGRESS, + ANJAY_SERVER_CONNECTION_INVALID, /** * If _anjay_server_on_refreshed() is called with server connection in this diff --git a/src/core/servers/anjay_connections_internal.h b/src/core/servers/anjay_connections_internal.h index d8976cba..2bd52495 100644 --- a/src/core/servers/anjay_connections_internal.h +++ b/src/core/servers/anjay_connections_internal.h @@ -44,9 +44,7 @@ typedef avs_error_t anjay_connection_connect_socket_t(anjay_unlocked_t *anjay, anjay_server_connection_t *connection); -typedef int -anjay_connection_ensure_coap_context_t(anjay_unlocked_t *anjay, - anjay_server_connection_t *connection); +typedef int anjay_connection_ensure_coap_context_t(anjay_connection_ref_t ref); typedef struct { const char *name; diff --git a/src/core/servers/anjay_register.c b/src/core/servers/anjay_register.c index 504a245d..07cbb7ab 100644 --- a/src/core/servers/anjay_register.c +++ b/src/core/servers/anjay_register.c @@ -472,10 +472,18 @@ static bool should_use_queue_mode(anjay_server_info_t *server, #ifdef ANJAY_WITH_LWM2M11 static bool lwm2m11_queue_mode_changed(anjay_server_info_t *server) { - if (server->registration_info.lwm2m_version >= ANJAY_LWM2M_VERSION_1_1 - && should_use_queue_mode(server, - server->registration_info.lwm2m_version) - != server->registration_info.queue_mode) { + if (server->registration_info.lwm2m_version < ANJAY_LWM2M_VERSION_1_1) { + return false; + } + bool is_currently_using_queue_mode = + avs_coap_exchange_id_valid( + server->registration_exchange_state.exchange_id) + ? server->registration_exchange_state.lwm2m11_queue_mode + : server->registration_info.queue_mode; + bool should_use_queue_mode_now = + should_use_queue_mode(server, + server->registration_info.lwm2m_version); + if (is_currently_using_queue_mode != should_use_queue_mode_now) { anjay_log(DEBUG, _("State of 1.1-style queue mode changed for SSID = ") "%u" _( ", forcing re-register"), @@ -622,6 +630,12 @@ handle_register_response(anjay_server_info_t *server, anjay_update_parameters_t *move_params, anjay_registration_result_t result, avs_error_t err) { + if (avs_is_ok(err)) { + _anjay_connection_mark_stable((anjay_connection_ref_t) { + .server = server, + .conn_type = ANJAY_CONNECTION_PRIMARY + }); + } if (result != ANJAY_REGISTRATION_SUCCESS) { anjay_log(WARNING, _("could not register to server ") "%u", _anjay_server_ssid(server)); @@ -727,7 +741,6 @@ static void move_assign_update_params(anjay_update_parameters_t *out, static void send_register(anjay_server_info_t *server, avs_coap_ctx_t *coap, anjay_lwm2m_version_t lwm2m_version, - bool lwm2m11_queue_mode, anjay_update_parameters_t *move_params) { const anjay_url_t *const connection_uri = _anjay_connection_uri((anjay_connection_ref_t) { @@ -742,12 +755,23 @@ static void send_register(anjay_server_info_t *server, get_binding_mode_for_version(server, lwm2m_version, &move_params->binding_mode); +#ifdef ANJAY_WITH_LWM2M11 + bool queue_mode = should_use_queue_mode(server, lwm2m_version); + bool lwm2m11_queue_mode = + (queue_mode && lwm2m_version >= ANJAY_LWM2M_VERSION_1_1); +#endif // ANJAY_WITH_LWM2M11 + avs_error_t err; if (avs_is_err((err = avs_coap_options_dynamic_init(&request.options))) || avs_is_err((err = setup_register_request_options( &request.options, lwm2m_version, server->anjay->endpoint_name, NULL, - connection_uri, lwm2m11_queue_mode, + connection_uri, +#ifdef ANJAY_WITH_LWM2M11 + lwm2m11_queue_mode, +#else // ANJAY_WITH_LWM2M11 + false, +#endif // ANJAY_WITH_LWM2M11 move_params->lifetime_s, &move_params->binding_mode)))) { _anjay_server_on_updated_registration( @@ -768,6 +792,9 @@ static void send_register(anjay_server_info_t *server, server->registration_exchange_state.attempted_version = lwm2m_version; move_assign_update_params(&server->registration_exchange_state.new_params, move_params); +#ifdef ANJAY_WITH_LWM2M11 + server->registration_exchange_state.lwm2m11_queue_mode = lwm2m11_queue_mode; +#endif // ANJAY_WITH_LWM2M11 if (avs_is_err( (err = avs_coap_client_send_async_request( coap, &server->registration_exchange_state.exchange_id, @@ -801,20 +828,8 @@ static void register_with_version(anjay_server_info_t *server, _anjay_server_on_updated_registration( server, ANJAY_REGISTRATION_ERROR_OTHER, avs_errno(AVS_EBADF)); } else { -#ifdef ANJAY_WITH_LWM2M11 - bool queue_mode = should_use_queue_mode(server, lwm2m_version); - bool lwm2m11_queue_mode = - (queue_mode && lwm2m_version >= ANJAY_LWM2M_VERSION_1_1); -#endif // ANJAY_WITH_LWM2M11 - send_register(server, _anjay_connection_get_coap(connection), - lwm2m_version, -#ifdef ANJAY_WITH_LWM2M11 - lwm2m11_queue_mode, -#else // ANJAY_WITH_LWM2M11 - false, -#endif // ANJAY_WITH_LWM2M11 - move_params); + lwm2m_version, move_params); _anjay_connection_schedule_queue_mode_close((anjay_connection_ref_t) { .server = server, .conn_type = ANJAY_CONNECTION_PRIMARY @@ -932,7 +947,6 @@ on_registration_update_result(anjay_server_info_t *server, _("; trying to re-register"), server->ssid); server->registration_info.expire_time = AVS_TIME_REAL_INVALID; - // defined(ANJAY_WITH_CORE_PERSISTENCE) do_register(server, move_params); break; @@ -942,7 +956,6 @@ on_registration_update_result(anjay_server_info_t *server, "; needs re-registration"), server->ssid); server->registration_info.expire_time = AVS_TIME_REAL_INVALID; - // defined(ANJAY_WITH_CORE_PERSISTENCE) do_register(server, move_params); break; @@ -986,6 +999,13 @@ receive_update_response(avs_coap_ctx_t *coap, state->exchange_id = AVS_COAP_EXCHANGE_ID_INVALID; } + if (request_state != AVS_COAP_CLIENT_REQUEST_CANCEL && avs_is_ok(err)) { + _anjay_connection_mark_stable((anjay_connection_ref_t) { + .server = server, + .conn_type = ANJAY_CONNECTION_PRIMARY + }); + } + switch (request_state) { case AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT: // Note: this will recursively call this function with @@ -1051,6 +1071,11 @@ static void send_update(anjay_server_info_t *server, old_info->lwm2m_version; move_assign_update_params(&server->registration_exchange_state.new_params, move_params); +#ifdef ANJAY_WITH_LWM2M11 + server->registration_exchange_state.lwm2m11_queue_mode = + (old_info->queue_mode + && old_info->lwm2m_version >= ANJAY_LWM2M_VERSION_1_1); +#endif // ANJAY_WITH_LWM2M11 if (avs_is_err(( err = avs_coap_client_send_async_request( coap, &server->registration_exchange_state.exchange_id, @@ -1074,14 +1099,10 @@ static void send_update(anjay_server_info_t *server, avs_coap_options_cleanup(&request.options); } -static bool -needs_registration_update(anjay_server_info_t *server, - const anjay_update_parameters_t *new_params) { - const anjay_registration_info_t *info = - _anjay_server_registration_info(server); - const anjay_update_parameters_t *old_params = &info->last_update_params; - return info->update_forced - || old_params->lifetime_s != new_params->lifetime_s +static bool params_require_registration_update( + const anjay_update_parameters_t *old_params, + const anjay_update_parameters_t *new_params) { + return old_params->lifetime_s != new_params->lifetime_s || strcmp(old_params->binding_mode.data, new_params->binding_mode.data) || !dm_caches_equal(old_params->dm, new_params->dm); @@ -1133,8 +1154,25 @@ void _anjay_server_ensure_valid_registration(anjay_server_info_t *server) { needs_reregistration = true; } #endif // ANJAY_WITH_LWM2M11 - bool needs_update = !needs_reregistration - && needs_registration_update(server, &new_params); + bool needs_update = false; + if (!needs_reregistration) { + const anjay_registration_info_t *info = + _anjay_server_registration_info(server); + if (info->update_forced) { + needs_update = true; + } else { + const anjay_update_parameters_t *current_params; + if (registration_or_update_in_progress) { + current_params = + &server->registration_exchange_state.new_params; + } else { + current_params = &info->last_update_params; + } + needs_update = + params_require_registration_update(current_params, + &new_params); + } + } if (needs_reregistration || (registration_or_update_in_progress && registration_expired && needs_update)) { @@ -1287,7 +1325,6 @@ void _anjay_server_update_registration_info( get_registration_expire_time(info->last_update_params.lifetime_s); info->update_forced = false; info->session_token = _anjay_server_primary_session_token(server); - // defined(ANJAY_WITH_CORE_PERSISTENCE) } static int diff --git a/src/core/servers/anjay_reload.c b/src/core/servers/anjay_reload.c index 9e26be3c..1216dc4a 100644 --- a/src/core/servers/anjay_reload.c +++ b/src/core/servers/anjay_reload.c @@ -388,8 +388,8 @@ int anjay_transport_schedule_reconnect(anjay_t *anjay_locked, result = _anjay_servers_sched_reactivate_all_given_up(anjay); #ifdef ANJAY_WITH_DOWNLOADER if (!result) { - result = _anjay_downloader_sched_reconnect(&anjay->downloader, - transport_set); + result = _anjay_downloader_sched_reconnect_by_transports( + &anjay->downloader, transport_set); } #endif // ANJAY_WITH_DOWNLOADER } diff --git a/src/core/servers/anjay_server_connections.c b/src/core/servers/anjay_server_connections.c index 4a001914..ae033cf5 100644 --- a/src/core/servers/anjay_server_connections.c +++ b/src/core/servers/anjay_server_connections.c @@ -256,8 +256,6 @@ static int read_server_sni(anjay_unlocked_t *anjay, void _anjay_active_server_refresh(anjay_server_info_t *server) { anjay_log(TRACE, _("refreshing SSID ") "%u", server->ssid); - // defined(ANJAY_WITH_TELIT_CUSTOM_FEATURES) - // Refreshed server is not deemed explicitly disabled anymore server->disabled_explicitly = false; diff --git a/src/core/servers/anjay_servers_internal.c b/src/core/servers/anjay_servers_internal.c index b810c2c8..0f718938 100644 --- a/src/core/servers/anjay_servers_internal.c +++ b/src/core/servers/anjay_servers_internal.c @@ -40,7 +40,6 @@ void _anjay_server_clean_active_data(anjay_server_info_t *server) { void _anjay_server_cleanup(anjay_server_info_t *server) { anjay_log(TRACE, _("clear_server SSID ") "%u", server->ssid); - // defined(ANJAY_WITH_CORE_PERSISTENCE) _anjay_server_clean_active_data(server); _anjay_registration_info_cleanup(&server->registration_info); } diff --git a/src/core/servers/anjay_servers_internal.h b/src/core/servers/anjay_servers_internal.h index 305b7965..861e7885 100644 --- a/src/core/servers/anjay_servers_internal.h +++ b/src/core/servers/anjay_servers_internal.h @@ -26,6 +26,9 @@ typedef struct { avs_coap_exchange_id_t exchange_id; anjay_lwm2m_version_t attempted_version; anjay_update_parameters_t new_params; +#ifdef ANJAY_WITH_LWM2M11 + bool lwm2m11_queue_mode; +#endif // ANJAY_WITH_LWM2M11 } anjay_registration_async_exchange_state_t; typedef enum { 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 5d7b640b..afa96307 100644 --- a/src/modules/advanced_fw_update/anjay_advanced_fw_update.c +++ b/src/modules/advanced_fw_update/anjay_advanced_fw_update.c @@ -20,7 +20,10 @@ # include # include -# include + +# ifdef ANJAY_WITH_DOWNLOADER +# include +# endif // ANJAY_WITH_DOWNLOADER # ifdef ANJAY_WITH_SEND # include @@ -71,10 +74,12 @@ typedef struct { anjay_advanced_fw_update_state_t state; anjay_advanced_fw_update_result_t result; const char *package_uri; - bool retry_download_on_expired; avs_sched_handle_t update_job; +# ifdef ANJAY_WITH_DOWNLOADER + bool retry_download_on_expired; avs_sched_handle_t resume_download_job; avs_time_monotonic_t resume_download_deadline; +# endif // ANJAY_WITH_DOWNLOADER anjay_advanced_fw_update_severity_t severity; avs_time_real_t last_state_change_time; int max_defer_period; @@ -91,7 +96,9 @@ typedef struct { anjay_dm_installed_object_t def_ptr; const anjay_unlocked_dm_object_def_t *def; +# ifdef ANJAY_WITH_DOWNLOADER bool prefer_same_socket_downloads; +# endif // ANJAY_WITH_DOWNLOADER # ifdef ANJAY_WITH_SEND bool use_lwm2m_send; # endif // ANJAY_WITH_SEND @@ -99,8 +106,11 @@ typedef struct { anjay_iid_t *supplemental_iid_cache; size_t supplemental_iid_cache_count; +# ifdef ANJAY_WITH_DOWNLOADER anjay_download_handle_t download_handle; + bool downloads_suspended; AVS_LIST(anjay_download_config_t) download_queue; +# endif // ANJAY_WITH_DOWNLOADER AVS_LIST(advanced_fw_instance_t) instances; } advanced_fw_repr_t; @@ -122,18 +132,36 @@ get_fw(const anjay_dm_installed_object_t obj_ptr) { # define SEND_FW_RES_PATH(Iid, Res) \ SEND_RES_PATH(ANJAY_ADVANCED_FW_UPDATE_OID, Iid, ADV_FW_RES_##Res) +static int perform_send(anjay_unlocked_t *anjay, + const anjay_dm_installed_object_t *obj, + anjay_iid_t iid, + void *batch) { + (void) obj; + anjay_ssid_t ssid; + const anjay_uri_path_t ssid_path = + MAKE_RESOURCE_PATH(ANJAY_DM_OID_SERVER, iid, + ANJAY_DM_RID_SERVER_SSID); + + if (_anjay_dm_read_resource_u16(anjay, &ssid_path, &ssid)) { + return 0; + } + + if (_anjay_send_deferrable_unlocked( + anjay, ssid, (anjay_send_batch_t *) batch, NULL, NULL) + != ANJAY_SEND_OK) { + fw_log(WARNING, _("failed to perform Send, SSID: ") "%" PRIu16, ssid); + } + + return 0; +} + static void send_batch_to_all_servers(anjay_unlocked_t *anjay, anjay_send_batch_t *batch) { - AVS_LIST(const anjay_ssid_t) ssids = - _anjay_server_get_ssids_unlocked(anjay); - AVS_LIST(const anjay_ssid_t) ssid; - AVS_LIST_FOREACH(ssid, ssids) { - if (_anjay_send_deferrable_unlocked(anjay, *ssid, batch, NULL, NULL) - != ANJAY_SEND_OK) { - fw_log(WARNING, - _("failed to perform Send, SSID: ") "%" PRIu16, - *ssid); - } + const anjay_dm_installed_object_t *obj = + _anjay_dm_find_object_by_oid(anjay, ANJAY_DM_OID_SERVER); + + if (_anjay_dm_foreach_instance(anjay, obj, perform_send, batch)) { + fw_log(ERROR, _("failed to perform Send to all servers")); } } @@ -194,7 +222,7 @@ static int set_update_result(anjay_unlocked_t *anjay, anjay_advanced_fw_update_result_t new_result) { if (inst->result != new_result) { fw_log(DEBUG, - _("Firmware Update Instance ") "%" PRIu16 _( + _("Advanced Firmware Update Instance ") "%" PRIu16 _( " Result change: ") "%d" _(" -> ") "%d", inst->iid, (int) inst->result, (int) new_result); inst->result = new_result; @@ -211,7 +239,7 @@ static int set_state(anjay_unlocked_t *anjay, if (inst->state != new_state) { inst->last_state_change_time = avs_time_real_now(); fw_log(DEBUG, - _("Firmware Update Instance ") "%" PRIu16 _( + _("Advanced Firmware Update Instance ") "%" PRIu16 _( " State change: ") "%d" _(" -> ") "%d", inst->iid, (int) inst->state, (int) new_state); inst->state = new_state; @@ -458,7 +486,8 @@ static void reset_state(anjay_unlocked_t *anjay, update_state_and_update_result(anjay, fw, inst, ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE, ANJAY_ADVANCED_FW_UPDATE_RESULT_INITIAL); - fw_log(INFO, _("Firmware Object Instance ") "%" PRIu16 _(" state reset"), + fw_log(INFO, + _("Advanced Firmware Object Instance ") "%" PRIu16 _(" state reset"), inst->iid); } @@ -712,7 +741,7 @@ static avs_error_t download_write_block(anjay_t *anjay_locked, const anjay_dm_installed_object_t *obj = _anjay_dm_find_object_by_oid(anjay, ANJAY_ADVANCED_FW_UPDATE_OID); if (!obj) { - fw_log(WARNING, _("Firmware Update object not installed")); + fw_log(WARNING, _("Advanced Firmware Update object not installed")); } else { advanced_fw_repr_t *fw = get_fw(*obj); advanced_fw_instance_t *inst = (advanced_fw_instance_t *) inst_; @@ -778,6 +807,9 @@ static int schedule_download_now(anjay_unlocked_t *anjay, # endif // ANJAY_WITH_SEND return -1; } + if (fw->downloads_suspended) { + _anjay_download_suspend_unlocked(anjay, fw->download_handle); + } inst->retry_download_on_expired = (false); update_state_and_update_result(anjay, fw, inst, ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADING, @@ -809,7 +841,7 @@ static void download_finished(anjay_t *anjay_locked, const anjay_dm_installed_object_t *obj = _anjay_dm_find_object_by_oid(anjay, ANJAY_ADVANCED_FW_UPDATE_OID); if (!obj) { - fw_log(WARNING, _("Firmware Update object not installed")); + fw_log(WARNING, _("Advanced Firmware Update object not installed")); } else { advanced_fw_repr_t *fw = get_fw(*obj); advanced_fw_instance_t *inst = (advanced_fw_instance_t *) inst_; @@ -1069,6 +1101,7 @@ static int write_firmware(anjay_unlocked_t *anjay, return result; } +# ifdef ANJAY_WITH_DOWNLOADER static void cancel_existing_download_if_in_progress(anjay_unlocked_t *anjay, advanced_fw_repr_t *fw, @@ -1086,6 +1119,7 @@ cancel_existing_download_if_in_progress(anjay_unlocked_t *anjay, assert(!fw->download_handle); } } +# endif // ANJAY_WITH_DOWNLOADER static int fw_write(anjay_unlocked_t *anjay, const anjay_dm_installed_object_t obj_ptr, @@ -1105,13 +1139,18 @@ static int fw_write(anjay_unlocked_t *anjay, assert(riid == ANJAY_ID_INVALID); int result = 0; +# ifdef ANJAY_WITH_DOWNLOADER bool is_any_in_progress = is_any_download_in_progress(fw); +# endif // ANJAY_WITH_DOWNLOADER if (inst->state == ANJAY_ADVANCED_FW_UPDATE_STATE_UPDATING) { fw_log(WARNING, _("cannot set Package resource while updating")); return ANJAY_ERR_METHOD_NOT_ALLOWED; } else if (inst->state == ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE - && !is_any_in_progress) { +# ifdef ANJAY_WITH_DOWNLOADER + && !is_any_in_progress +# endif // ANJAY_WITH_DOWNLOADER + ) { bool is_reset_request = false; result = write_firmware(anjay, fw, inst, ctx, &is_reset_request); @@ -1121,14 +1160,19 @@ static int fw_write(anjay_unlocked_t *anjay, } else { result = expect_single_nullbyte(ctx); if (!result) { +# ifdef ANJAY_WITH_DOWNLOADER cancel_existing_download_if_in_progress(anjay, fw, inst); +# endif // ANJAY_WITH_DOWNLOADER reset_state(anjay, fw, inst); - } else if (is_any_in_progress) { + } +# ifdef ANJAY_WITH_DOWNLOADER + else if (is_any_in_progress) { fw_log(ERROR, _("There is a download already in progress or in " "queue. Rejecting push mode download due do " "implementation limitation")); return ANJAY_ERR_METHOD_NOT_ALLOWED; } +# endif // ANJAY_WITH_DOWNLOADER } return result; } @@ -1147,7 +1191,9 @@ static int fw_write(anjay_unlocked_t *anjay, return ANJAY_ERR_METHOD_NOT_ALLOWED; } +# ifdef ANJAY_WITH_DOWNLOADER cancel_existing_download_if_in_progress(anjay, fw, inst); +# endif // ANJAY_WITH_DOWNLOADER avs_free((void *) (intptr_t) inst->package_uri); inst->package_uri = NULL; @@ -1325,7 +1371,7 @@ static int sort_supplemental_iid_cache(advanced_fw_repr_t *fw) { // The count should be usually low enough for it to not be a performance // hog. for (size_t i = 1; i < fw->supplemental_iid_cache_count; ++i) { - for (ptrdiff_t j = (ptrdiff_t) i; j >= 0; --j) { + for (ptrdiff_t j = (ptrdiff_t) i; j > 0; --j) { if (fw->supplemental_iid_cache[j - 1] == fw->supplemental_iid_cache[j]) { fw_log(ERROR, _("Duplicate instances specified in " @@ -1358,7 +1404,8 @@ static int handle_fw_execute_args(advanced_fw_repr_t *fw, } if (arg != 0) { - fw_log(ERROR, _("Invalid Firmware Update argument: ") "%d", arg); + fw_log(ERROR, _("Invalid Advanced Firmware Update argument: ") "%d", + arg); return ANJAY_ERR_BAD_REQUEST; } @@ -1384,7 +1431,8 @@ static int handle_fw_execute_args(advanced_fw_repr_t *fw, sizeof(arg_buf) - arg_buf_offset); if (result && result != ANJAY_BUFFER_TOO_SHORT) { fw_log(ERROR, - _("Error while reading Firmware Update arguments")); + _("Error while reading Advanced Firmware Update " + "arguments")); goto finish; } } @@ -1409,7 +1457,7 @@ static int handle_fw_execute_args(advanced_fw_repr_t *fw, } if (!supplemental_inst) { - fw_log(ERROR, _("Invalid argument for Firmware Update")); + fw_log(ERROR, _("Invalid argument for Advanced Firmware Update")); result = ANJAY_ERR_BAD_REQUEST; goto finish; } @@ -1417,9 +1465,10 @@ static int handle_fw_execute_args(advanced_fw_repr_t *fw, if (supplemental_inst->state != ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED) { fw_log(WARNING, - _("Firmware Update including supplemental instance ") "%" PRIu16 - _(" requested, but firmware not yet downloaded " - "(state = ") "%d" _(")"), + _("Advanced Firmware Update including supplemental " + "instance ") "%" PRIu16 _(" requested, but firmware not " + "yet downloaded (state " + "= ") "%d" _(")"), supplemental_iid, supplemental_inst->state); result = ANJAY_ERR_METHOD_NOT_ALLOWED; goto finish; @@ -1441,7 +1490,7 @@ static int handle_fw_execute_args(advanced_fw_repr_t *fw, arg_buf_offset += arg_buf_bytes_read; arg_buf_offset -= (size_t) char_count + 1; } else { - fw_log(ERROR, _("Invalid argument for Firmware Update")); + fw_log(ERROR, _("Invalid argument for Advanced Firmware Update")); result = ANJAY_ERR_BAD_REQUEST; goto finish; } @@ -1475,7 +1524,8 @@ static int handle_fw_execute_args(advanced_fw_repr_t *fw, if (!result && _anjay_execute_get_next_arg_unlocked(ctx, &arg, &arg_has_value) != ANJAY_EXECUTE_GET_ARG_END) { - fw_log(ERROR, _("Superfluous Firmware Update argument: ") "%d", arg); + fw_log(ERROR, _("Superfluous Advanced Firmware Update argument: ") "%d", + arg); result = ANJAY_ERR_BAD_REQUEST; } @@ -1501,7 +1551,7 @@ static int fw_execute(anjay_unlocked_t *anjay, case ADV_FW_RES_UPDATE: { if (inst->state != ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED) { fw_log(WARNING, - _("Firmware Update for instance ") "%" PRIu16 _( + _("Advanced Firmware Update for instance ") "%" PRIu16 _( " requested, but firmware not yet downloaded " "(state = ") "%d" _(")"), iid, inst->state); @@ -1542,13 +1592,15 @@ static int fw_execute(anjay_unlocked_t *anjay, if (inst->state != ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADING && inst->state != ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED) { fw_log(WARNING, - _("Firmware Update Cancel requested, but the firmware is " - "being installed or has already been installed " - "(state = ") "%d" _(")"), + _("Advanced Firmware Update Cancel requested, but the " + "firmware is being installed or has already been " + "installed (state = ") "%d" _(")"), inst->state); return ANJAY_ERR_METHOD_NOT_ALLOWED; } +# 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, @@ -1645,6 +1697,7 @@ static AVS_LIST(advanced_fw_instance_t) initialize_fw_instance( } # else // ANJAY_WITH_DOWNLOADER (void) anjay; + (void) fw; fw_log(WARNING, _("Unable to resume download: PULL download not supported")); # endif // ANJAY_WITH_DOWNLOADER @@ -1672,15 +1725,19 @@ static void fw_delete(void *fw_) { AVS_LIST_CLEAR(&fw->instances) { AVS_LIST(advanced_fw_instance_t) inst = fw->instances; avs_sched_del(&inst->update_job); +# ifdef ANJAY_WITH_DOWNLOADER avs_sched_del(&inst->resume_download_job); +# endif // ANJAY_WITH_DOWNLOADER avs_free(inst->linked_instances); avs_free(inst->conflicting_instances); avs_free((void *) (intptr_t) inst->package_uri); } +# 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); } +# endif // ANJAY_WITH_DOWNLOADER // NOTE: fw itself will be freed when cleaning the objects list } @@ -1697,8 +1754,10 @@ int anjay_advanced_fw_update_install( } else { repr->def = &FIRMWARE_UPDATE; if (config) { +# ifdef ANJAY_WITH_DOWNLOADER repr->prefer_same_socket_downloads = config->prefer_same_socket_downloads; +# endif // ANJAY_WITH_DOWNLOADER # ifdef ANJAY_WITH_SEND repr->use_lwm2m_send = config->use_lwm2m_send; # endif // ANJAY_WITH_SEND @@ -1738,7 +1797,7 @@ int anjay_advanced_fw_update_instance_add( const anjay_dm_installed_object_t *obj = _anjay_dm_find_object_by_oid(anjay, ANJAY_ADVANCED_FW_UPDATE_OID); if (!obj) { - fw_log(WARNING, _("Firmware Update object not installed")); + fw_log(WARNING, _("Advanced Firmware Update object not installed")); } else { advanced_fw_repr_t *fw = get_fw(*obj); @@ -1837,7 +1896,7 @@ int anjay_advanced_fw_update_set_state_and_result( const anjay_dm_installed_object_t *obj = _anjay_dm_find_object_by_oid(anjay, ANJAY_ADVANCED_FW_UPDATE_OID); if (!obj) { - fw_log(WARNING, _("Firmware Update object not installed")); + fw_log(WARNING, _("Advanced Firmware Update object not installed")); } else { advanced_fw_repr_t *fw = get_fw(*obj); @@ -1848,8 +1907,9 @@ int anjay_advanced_fw_update_set_state_and_result( fw_log(ERROR, _("Instance does not exist")); } else if (!is_state_change_allowed(inst->state, state, result)) { fw_log(WARNING, - _("Firmware Update State and Result change from ") "%d/%d" _( - " to ") "%d/%d" _(" is not allowed"), + _("Advanced Firmware Update State and Result change " + "from ") "%d/%d" _(" to ") "%d/%d" _(" is not " + "allowed"), (int) inst->state, (int) inst->result, (int) state, (int) result); } else { @@ -1875,7 +1935,7 @@ int anjay_advanced_fw_update_get_state( const anjay_dm_installed_object_t *obj = _anjay_dm_find_object_by_oid(anjay, ANJAY_ADVANCED_FW_UPDATE_OID); if (!obj) { - fw_log(WARNING, _("Firmware Update object not installed")); + fw_log(WARNING, _("Advanced Firmware Update object not installed")); } else { advanced_fw_repr_t *fw = get_fw(*obj); @@ -1904,7 +1964,7 @@ int anjay_advanced_fw_update_get_result( const anjay_dm_installed_object_t *obj = _anjay_dm_find_object_by_oid(anjay, ANJAY_ADVANCED_FW_UPDATE_OID); if (!obj) { - fw_log(WARNING, _("Firmware Update object not installed")); + fw_log(WARNING, _("Advanced Firmware Update object not installed")); } else { advanced_fw_repr_t *fw = get_fw(*obj); @@ -1995,7 +2055,7 @@ int anjay_advanced_fw_update_set_linked_instances( const anjay_dm_installed_object_t *obj = _anjay_dm_find_object_by_oid(anjay, ANJAY_ADVANCED_FW_UPDATE_OID); if (!obj) { - fw_log(WARNING, _("Firmware Update object not installed")); + fw_log(WARNING, _("Advanced Firmware Update object not installed")); } else { advanced_fw_repr_t *fw = get_fw(*obj); @@ -2038,7 +2098,7 @@ int anjay_advanced_fw_update_get_linked_instances( const anjay_dm_installed_object_t *obj = _anjay_dm_find_object_by_oid(anjay, ANJAY_ADVANCED_FW_UPDATE_OID); if (!obj) { - fw_log(WARNING, _("Firmware Update object not installed")); + fw_log(WARNING, _("Advanced Firmware Update object not installed")); } else { advanced_fw_repr_t *fw = get_fw(*obj); @@ -2068,7 +2128,7 @@ int anjay_advanced_fw_update_set_conflicting_instances( const anjay_dm_installed_object_t *obj = _anjay_dm_find_object_by_oid(anjay, ANJAY_ADVANCED_FW_UPDATE_OID); if (!obj) { - fw_log(WARNING, _("Firmware Update object not installed")); + fw_log(WARNING, _("Advanced Firmware Update object not installed")); } else { advanced_fw_repr_t *fw = get_fw(*obj); @@ -2110,7 +2170,7 @@ int anjay_advanced_fw_update_get_conflicting_instances( const anjay_dm_installed_object_t *obj = _anjay_dm_find_object_by_oid(anjay, ANJAY_ADVANCED_FW_UPDATE_OID); if (!obj) { - fw_log(WARNING, _("Firmware Update object not installed")); + fw_log(WARNING, _("Advanced Firmware Update object not installed")); } else { advanced_fw_repr_t *fw = get_fw(*obj); @@ -2137,7 +2197,7 @@ avs_time_real_t anjay_advanced_fw_update_get_deadline(anjay_t *anjay_locked, const anjay_dm_installed_object_t *obj = _anjay_dm_find_object_by_oid(anjay, ANJAY_ADVANCED_FW_UPDATE_OID); if (!obj) { - fw_log(WARNING, _("Firmware Update object not installed")); + fw_log(WARNING, _("Advanced Firmware Update object not installed")); } else { advanced_fw_repr_t *fw = get_fw(*obj); @@ -2163,7 +2223,7 @@ anjay_advanced_fw_update_get_severity(anjay_t *anjay_locked, anjay_iid_t iid) { const anjay_dm_installed_object_t *obj = _anjay_dm_find_object_by_oid(anjay, ANJAY_ADVANCED_FW_UPDATE_OID); if (!obj) { - fw_log(WARNING, _("Firmware Update object not installed")); + fw_log(WARNING, _("Advanced Firmware Update object not installed")); } else { advanced_fw_repr_t *fw = get_fw(*obj); @@ -2189,7 +2249,7 @@ anjay_advanced_fw_update_get_last_state_change_time(anjay_t *anjay_locked, const anjay_dm_installed_object_t *obj = _anjay_dm_find_object_by_oid(anjay, ANJAY_ADVANCED_FW_UPDATE_OID); if (!obj) { - fw_log(WARNING, _("Firmware Update object not installed")); + fw_log(WARNING, _("Advanced Firmware Update object not installed")); } else { advanced_fw_repr_t *fw = get_fw(*obj); @@ -2206,4 +2266,47 @@ anjay_advanced_fw_update_get_last_state_change_time(anjay_t *anjay_locked, return result; } +# ifdef ANJAY_WITH_DOWNLOADER +void anjay_advanced_fw_update_pull_suspend(anjay_t *anjay_locked) { + assert(anjay_locked); + ANJAY_MUTEX_LOCK(anjay, anjay_locked); + const anjay_dm_installed_object_t *obj = + _anjay_dm_find_object_by_oid(anjay, ANJAY_ADVANCED_FW_UPDATE_OID); + if (!obj) { + fw_log(WARNING, _("Advanced Firmware Update object not installed")); + } else { + advanced_fw_repr_t *fw = get_fw(*obj); + assert(fw); + if (fw->download_handle) { + _anjay_download_suspend_unlocked(anjay, fw->download_handle); + } + fw->downloads_suspended = true; + } + ANJAY_MUTEX_UNLOCK(anjay_locked); +} + +int anjay_advanced_fw_update_pull_reconnect(anjay_t *anjay_locked) { + assert(anjay_locked); + int result = -1; + ANJAY_MUTEX_LOCK(anjay, anjay_locked); + const anjay_dm_installed_object_t *obj = + _anjay_dm_find_object_by_oid(anjay, ANJAY_ADVANCED_FW_UPDATE_OID); + if (!obj) { + fw_log(WARNING, _("Advanced Firmware Update object not installed")); + } else { + 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); + } else { + result = 0; + } + } + ANJAY_MUTEX_UNLOCK(anjay_locked); + return result; +} +# endif // ANJAY_WITH_DOWNLOADER + #endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE diff --git a/src/modules/fw_update/anjay_fw_update.c b/src/modules/fw_update/anjay_fw_update.c index 8b3a881f..e24de78c 100644 --- a/src/modules/fw_update/anjay_fw_update.c +++ b/src/modules/fw_update/anjay_fw_update.c @@ -19,7 +19,10 @@ # include -# include +# ifdef ANJAY_WITH_DOWNLOADER +# include +# endif // ANJAY_WITH_DOWNLOADER + # include # ifdef ANJAY_WITH_SEND @@ -76,12 +79,15 @@ typedef struct fw_repr { fw_update_state_t state; anjay_fw_update_result_t result; const char *package_uri; + avs_sched_handle_t update_job; +# ifdef ANJAY_WITH_DOWNLOADER bool retry_download_on_expired; anjay_download_handle_t download_handle; - avs_sched_handle_t update_job; bool prefer_same_socket_downloads; + bool downloads_suspended; avs_sched_handle_t resume_download_job; avs_time_monotonic_t resume_download_deadline; +# endif // ANJAY_WITH_DOWNLOADER # ifdef ANJAY_WITH_SEND bool use_lwm2m_send; # endif // ANJAY_WITH_SEND @@ -703,6 +709,9 @@ static int schedule_download(anjay_unlocked_t *anjay, return -1; } + if (fw->downloads_suspended) { + _anjay_download_suspend_unlocked(anjay, fw->download_handle); + } fw->retry_download_on_expired = (etag != NULL); update_state_and_update_result(anjay, fw, UPDATE_STATE_DOWNLOADING, ANJAY_FW_UPDATE_RESULT_INITIAL); @@ -888,6 +897,7 @@ static int write_firmware(anjay_unlocked_t *anjay, } # endif // ANJAY_WITHOUT_MODULE_FW_UPDATE_PUSH_MODE +# ifdef ANJAY_WITH_DOWNLOADER static void cancel_existing_download_if_in_progress(anjay_unlocked_t *anjay, fw_repr_t *fw) { if (fw->state == UPDATE_STATE_DOWNLOADING) { @@ -903,6 +913,7 @@ static void cancel_existing_download_if_in_progress(anjay_unlocked_t *anjay, assert(!fw->download_handle); } } +# endif // ANJAY_WITH_DOWNLOADER static int fw_write(anjay_unlocked_t *anjay, const anjay_dm_installed_object_t obj_ptr, @@ -918,7 +929,7 @@ static int fw_write(anjay_unlocked_t *anjay, case FW_RES_PACKAGE: { # ifdef ANJAY_WITHOUT_MODULE_FW_UPDATE_PUSH_MODE return ANJAY_ERR_BAD_REQUEST; -# else // ANJAY_WITHOUT_MODULE_FW_UPDATE_PUSH_MODE +# else // ANJAY_WITHOUT_MODULE_FW_UPDATE_PUSH_MODE assert(riid == ANJAY_ID_INVALID); int result = 0; if (fw->state == UPDATE_STATE_UPDATING) { @@ -933,12 +944,14 @@ static int fw_write(anjay_unlocked_t *anjay, } else { result = expect_single_nullbyte(ctx); if (!result) { +# ifdef ANJAY_WITH_DOWNLOADER cancel_existing_download_if_in_progress(anjay, fw); +# endif // ANJAY_WITH_DOWNLOADER reset(anjay, fw); } } return result; -# endif // ANJAY_WITHOUT_MODULE_FW_UPDATE_PUSH_MODE +# endif // ANJAY_WITHOUT_MODULE_FW_UPDATE_PUSH_MODE } case FW_RES_PACKAGE_URI: { assert(riid == ANJAY_ID_INVALID); @@ -955,7 +968,9 @@ static int fw_write(anjay_unlocked_t *anjay, return ANJAY_ERR_METHOD_NOT_ALLOWED; } +# ifdef ANJAY_WITH_DOWNLOADER cancel_existing_download_if_in_progress(anjay, fw); +# endif // ANJAY_WITH_DOWNLOADER avs_free((void *) (intptr_t) fw->package_uri); fw->package_uri = NULL; @@ -1143,7 +1158,9 @@ static void send_result_after_fw_update(anjay_unlocked_t *anjay, static void fw_delete(void *fw_) { fw_repr_t *fw = (fw_repr_t *) fw_; avs_sched_del(&fw->update_job); +# ifdef ANJAY_WITH_DOWNLOADER avs_sched_del(&fw->resume_download_job); +# endif // ANJAY_WITH_DOWNLOADER avs_free((void *) (intptr_t) fw->package_uri); // NOTE: fw itself will be freed when cleaning the objects list } @@ -1155,8 +1172,10 @@ initialize_fw_repr(anjay_unlocked_t *anjay, if (!initial_state) { return 0; } +# ifdef ANJAY_WITH_DOWNLOADER repr->prefer_same_socket_downloads = initial_state->prefer_same_socket_downloads; +# endif // ANJAY_WITH_DOWNLOADER # ifdef ANJAY_WITH_SEND repr->use_lwm2m_send = initial_state->use_lwm2m_send; # endif // ANJAY_WITH_SEND @@ -1320,4 +1339,47 @@ int anjay_fw_update_set_result(anjay_t *anjay_locked, return retval; } +# ifdef ANJAY_WITH_DOWNLOADER +void anjay_fw_update_pull_suspend(anjay_t *anjay_locked) { + assert(anjay_locked); + ANJAY_MUTEX_LOCK(anjay, anjay_locked); + const anjay_dm_installed_object_t *obj = + _anjay_dm_find_object_by_oid(anjay, ANJAY_DM_OID_FIRMWARE_UPDATE); + if (!obj) { + fw_log(WARNING, _("Firmware Update object not installed")); + } else { + fw_repr_t *fw = get_fw(*obj); + assert(fw); + if (fw->download_handle) { + _anjay_download_suspend_unlocked(anjay, fw->download_handle); + } + fw->downloads_suspended = true; + } + ANJAY_MUTEX_UNLOCK(anjay_locked); +} + +int anjay_fw_update_pull_reconnect(anjay_t *anjay_locked) { + assert(anjay_locked); + int result = -1; + ANJAY_MUTEX_LOCK(anjay, anjay_locked); + const anjay_dm_installed_object_t *obj = + _anjay_dm_find_object_by_oid(anjay, ANJAY_DM_OID_FIRMWARE_UPDATE); + if (!obj) { + fw_log(WARNING, _("Firmware Update object not installed")); + } else { + 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); + } else { + result = 0; + } + } + ANJAY_MUTEX_UNLOCK(anjay_locked); + return result; +} +# endif // ANJAY_WITH_DOWNLOADER + #endif // ANJAY_WITH_MODULE_FW_UPDATE diff --git a/src/modules/ipso/anjay_ipso_3d_sensor.c b/src/modules/ipso/anjay_ipso_3d_sensor.c index 1f87a534..b80ff17f 100644 --- a/src/modules/ipso/anjay_ipso_3d_sensor.c +++ b/src/modules/ipso/anjay_ipso_3d_sensor.c @@ -100,7 +100,6 @@ ipso_3d_sensor_list_instances(anjay_unlocked_t *anjay, (void) anjay; anjay_ipso_3d_sensor_t *obj = get_obj(&obj_ptr); - assert(obj); for (anjay_iid_t iid = 0; iid < obj->num_instances; iid++) { if (obj->instances[iid].initialized) { @@ -119,7 +118,6 @@ ipso_3d_sensor_list_resources(anjay_unlocked_t *anjay, (void) anjay; anjay_ipso_3d_sensor_t *obj = get_obj(&obj_ptr); - assert(obj); assert(iid < obj->num_instances); anjay_ipso_3d_sensor_instance_t *inst = &obj->instances[iid]; assert(inst->initialized); @@ -192,7 +190,6 @@ ipso_3d_sensor_resource_read(anjay_unlocked_t *anjay, (void) riid; anjay_ipso_3d_sensor_t *obj = get_obj(&obj_ptr); - assert(obj); assert(iid < obj->num_instances); anjay_ipso_3d_sensor_instance_t *inst = &obj->instances[iid]; assert(inst->initialized); diff --git a/src/modules/ipso/anjay_ipso_basic_sensor.c b/src/modules/ipso/anjay_ipso_basic_sensor.c index 4820b5e6..47feb382 100644 --- a/src/modules/ipso/anjay_ipso_basic_sensor.c +++ b/src/modules/ipso/anjay_ipso_basic_sensor.c @@ -108,7 +108,6 @@ basic_sensor_list_instances(anjay_unlocked_t *anjay, (void) anjay; anjay_ipso_basic_sensor_t *obj = get_obj(&obj_ptr); - assert(obj); for (anjay_iid_t iid = 0; iid < obj->num_instances; iid++) { if (obj->instances[iid].initialized) { @@ -127,7 +126,6 @@ basic_sensor_list_resources(anjay_unlocked_t *anjay, (void) anjay; anjay_ipso_basic_sensor_t *obj = get_obj(&obj_ptr); - assert(obj); assert(iid < obj->num_instances); anjay_ipso_basic_sensor_instance_t *inst = &obj->instances[iid]; assert(inst->initialized); @@ -212,7 +210,6 @@ static int basic_sensor_resource_read(anjay_unlocked_t *anjay, (void) riid; anjay_ipso_basic_sensor_t *obj = get_obj(&obj_ptr); - assert(obj); assert(iid < obj->num_instances); anjay_ipso_basic_sensor_instance_t *inst = &obj->instances[iid]; assert(inst->initialized); @@ -268,7 +265,6 @@ basic_sensor_resource_execute(anjay_unlocked_t *anjay, (void) anjay; anjay_ipso_basic_sensor_t *obj = get_obj(&obj_ptr); - assert(obj); assert(iid < obj->num_instances); anjay_ipso_basic_sensor_instance_t *inst = &obj->instances[iid]; assert(inst->initialized); diff --git a/src/modules/ipso/anjay_ipso_button.c b/src/modules/ipso/anjay_ipso_button.c index b6de2231..3694f45a 100644 --- a/src/modules/ipso/anjay_ipso_button.c +++ b/src/modules/ipso/anjay_ipso_button.c @@ -82,7 +82,6 @@ static int ipso_button_list_instances(anjay_unlocked_t *anjay, (void) anjay; anjay_ipso_button_t *obj = get_obj(&obj_ptr); - assert(obj); for (anjay_iid_t iid = 0; iid < obj->num_instances; iid++) { if (obj->instances[iid].initialized) { @@ -122,7 +121,6 @@ static int ipso_button_resource_read(anjay_unlocked_t *anjay, (void) riid; anjay_ipso_button_t *obj = get_obj(&obj_ptr); - assert(obj); assert(iid < obj->num_instances); anjay_ipso_button_instance_t *inst = &obj->instances[iid]; assert(inst->initialized); @@ -155,7 +153,6 @@ static int ipso_button_resource_write(anjay_unlocked_t *anjay, (void) riid; anjay_ipso_button_t *obj = get_obj(&obj_ptr); - assert(obj); assert(iid < obj->num_instances); anjay_ipso_button_instance_t *inst = &obj->instances[iid]; assert(inst->initialized); diff --git a/src/modules/server/anjay_mod_server.c b/src/modules/server/anjay_mod_server.c index b7febc41..185f2bdb 100644 --- a/src/modules/server/anjay_mod_server.c +++ b/src/modules/server/anjay_mod_server.c @@ -700,10 +700,10 @@ void anjay_server_object_purge(anjay_t *anjay_locked) { ANJAY_MUTEX_UNLOCK(anjay_locked); } -AVS_LIST(const anjay_ssid_t) -_anjay_server_get_ssids_unlocked(anjay_unlocked_t *anjay) { - assert(anjay); +AVS_LIST(const anjay_ssid_t) anjay_server_get_ssids(anjay_t *anjay_locked) { + assert(anjay_locked); AVS_LIST(server_instance_t) source = NULL; + ANJAY_MUTEX_LOCK(anjay, anjay_locked); const anjay_dm_installed_object_t *server_obj = _anjay_dm_find_object_by_oid(anjay, SERVER.oid); server_repr_t *repr = _anjay_serv_get(*server_obj); @@ -712,6 +712,7 @@ _anjay_server_get_ssids_unlocked(anjay_unlocked_t *anjay) { } else { source = repr->instances; } + ANJAY_MUTEX_UNLOCK(anjay_locked); // We rely on the fact that the "ssid" field is first in server_instance_t, // which means that both "source" and "&source->ssid" point to exactly the // same memory location. The "next" pointer location in AVS_LIST is @@ -721,15 +722,6 @@ _anjay_server_get_ssids_unlocked(anjay_unlocked_t *anjay) { return &source->ssid; } -AVS_LIST(const anjay_ssid_t) anjay_server_get_ssids(anjay_t *anjay_locked) { - assert(anjay_locked); - AVS_LIST(const anjay_ssid_t) source = NULL; - ANJAY_MUTEX_LOCK(anjay, anjay_locked); - source = _anjay_server_get_ssids_unlocked(anjay); - ANJAY_MUTEX_UNLOCK(anjay_locked); - return source; -} - bool anjay_server_object_is_modified(anjay_t *anjay_locked) { assert(anjay_locked); bool result = false; diff --git a/standalone/security/standalone_mod_security.c b/standalone/security/standalone_mod_security.c new file mode 100644 index 00000000..d6430602 --- /dev/null +++ b/standalone/security/standalone_mod_security.c @@ -0,0 +1,1098 @@ +#include +#include +#include + +#include "standalone_mod_security.h" +#include "standalone_security_transaction.h" +#include "standalone_security_utils.h" + +#ifdef ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT +# if !defined(AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE) \ + && !defined(AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE) +# error "At least one of AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE or AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE is required for ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT" +# endif /* !defined(AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE) && \ + !defined(AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE) */ +#endif // ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT + +static const security_rid_t SECURITY_RESOURCE_ID[] = { + SEC_RES_LWM2M_SERVER_URI, + SEC_RES_BOOTSTRAP_SERVER, + SEC_RES_SECURITY_MODE, + SEC_RES_PK_OR_IDENTITY, + SEC_RES_SERVER_PK, + SEC_RES_SECRET_KEY, +#ifdef ANJAY_WITH_SMS + SEC_RES_SMS_SECURITY_MODE, + SEC_RES_SMS_BINDING_KEY_PARAMS, + SEC_RES_SMS_BINDING_SECRET_KEYS, + SEC_RES_SERVER_SMS_NUMBER, +#endif // ANJAY_WITH_SMS + SEC_RES_SHORT_SERVER_ID, + SEC_RES_CLIENT_HOLD_OFF_TIME, + SEC_RES_BOOTSTRAP_TIMEOUT, +#ifdef ANJAY_WITH_LWM2M11 + SEC_RES_MATCHING_TYPE, + SEC_RES_SNI, + SEC_RES_CERTIFICATE_USAGE, + SEC_RES_DTLS_TLS_CIPHERSUITE, +#endif // ANJAY_WITH_LWM2M11 +#ifdef ANJAY_WITH_COAP_OSCORE + SEC_RES_OSCORE_SECURITY_MODE +#endif // ANJAY_WITH_COAP_OSCORE +}; + +void _standalone_sec_instance_update_resource_presence(sec_instance_t *inst) { + // Sets presence of mandatory resources and updates presence of resources + // which presence is not persisted and depends on resource value + inst->present_resources[SEC_RES_LWM2M_SERVER_URI] = true; + inst->present_resources[SEC_RES_BOOTSTRAP_SERVER] = true; + inst->present_resources[SEC_RES_SECURITY_MODE] = true; + inst->present_resources[SEC_RES_PK_OR_IDENTITY] = true; + inst->present_resources[SEC_RES_SERVER_PK] = true; + inst->present_resources[SEC_RES_SECRET_KEY] = true; +#ifdef ANJAY_WITH_SMS + inst->present_resources[SEC_RES_SERVER_SMS_NUMBER] = !!inst->sms_number; +#endif // ANJAY_WITH_SMS + inst->present_resources[SEC_RES_CLIENT_HOLD_OFF_TIME] = + (inst->holdoff_s >= 0); + inst->present_resources[SEC_RES_BOOTSTRAP_TIMEOUT] = + (inst->bs_timeout_s >= 0); +#ifdef ANJAY_WITH_LWM2M11 + inst->present_resources[SEC_RES_MATCHING_TYPE] = (inst->matching_type >= 0); + inst->present_resources[SEC_RES_SNI] = !!inst->server_name_indication; + inst->present_resources[SEC_RES_CERTIFICATE_USAGE] = + (inst->certificate_usage >= 0); + inst->present_resources[SEC_RES_DTLS_TLS_CIPHERSUITE] = true; +#endif // ANJAY_WITH_LWM2M11 +} + +static inline sec_instance_t *find_instance(sec_repr_t *repr, anjay_iid_t iid) { + if (!repr) { + return NULL; + } + AVS_LIST(sec_instance_t) it; + AVS_LIST_FOREACH(it, repr->instances) { + if (it->iid == iid) { + return it; + } else if (it->iid > iid) { + break; + } + } + return NULL; +} + +static anjay_iid_t get_new_iid(AVS_LIST(sec_instance_t) instances) { + anjay_iid_t iid = 0; + AVS_LIST(sec_instance_t) it; + AVS_LIST_FOREACH(it, instances) { + if (it->iid == iid) { + ++iid; + } else if (it->iid > iid) { + break; + } + } + return iid; +} + +static int assign_iid(sec_repr_t *repr, anjay_iid_t *inout_iid) { + *inout_iid = get_new_iid(repr->instances); + if (*inout_iid == ANJAY_ID_INVALID) { + return -1; + } + return 0; +} + +static void init_instance(sec_instance_t *instance, anjay_iid_t iid) { + memset(instance, 0, sizeof(sec_instance_t)); + instance->iid = iid; +#ifdef ANJAY_WITH_LWM2M11 + instance->matching_type = -1; + instance->certificate_usage = -1; +#endif // ANJAY_WITH_LWM2M11 + _standalone_sec_instance_update_resource_presence(instance); +} + +static int add_instance(sec_repr_t *repr, + const standalone_security_instance_t *instance, + anjay_iid_t *inout_iid) { + if (*inout_iid == ANJAY_ID_INVALID) { + if (assign_iid(repr, inout_iid)) { + return -1; + } + } else if (find_instance(repr, *inout_iid)) { + return -1; + } + AVS_LIST(sec_instance_t) new_instance = + AVS_LIST_NEW_ELEMENT(sec_instance_t); + if (!new_instance) { + security_log(ERROR, _("out of memory")); + return -1; + } + init_instance(new_instance, *inout_iid); + if (instance->server_uri) { + new_instance->server_uri = avs_strdup(instance->server_uri); + if (!new_instance->server_uri) { + goto error; + } + } + new_instance->is_bootstrap = instance->bootstrap_server; + new_instance->security_mode = instance->security_mode; + new_instance->holdoff_s = instance->client_holdoff_s; + new_instance->bs_timeout_s = instance->bootstrap_timeout_s; + +#ifdef ANJAY_WITH_SECURITY_STRUCTURED + if ((instance->public_cert_or_psk_identity + || instance->public_cert_or_psk_identity_size) + + (instance->public_cert.desc.source + != AVS_CRYPTO_DATA_SOURCE_EMPTY) + + (instance->psk_identity.desc.source + != AVS_CRYPTO_DATA_SOURCE_EMPTY) + > 1) { + security_log(ERROR, _("more than one variant of the Public Key Or " + "Identity field specified at the same time")); + goto error; + } + if (instance->public_cert.desc.source != AVS_CRYPTO_DATA_SOURCE_EMPTY) { + if (_standalone_sec_init_certificate_chain_resource( + &new_instance->public_cert_or_psk_identity, + SEC_KEY_AS_KEY_EXTERNAL, &instance->public_cert)) { + goto error; + } + } else if (instance->psk_identity.desc.source + != AVS_CRYPTO_DATA_SOURCE_EMPTY) { + if (_standalone_sec_init_psk_identity_resource( + &new_instance->public_cert_or_psk_identity, + SEC_KEY_AS_KEY_EXTERNAL, &instance->psk_identity)) { + goto error; + } + } else +#endif // ANJAY_WITH_SECURITY_STRUCTURED + { + new_instance->public_cert_or_psk_identity.type = SEC_KEY_AS_DATA; + if (_standalone_raw_buffer_clone( + &new_instance->public_cert_or_psk_identity.value.data, + &(const standalone_raw_buffer_t) { + .data = (void *) (intptr_t) + instance->public_cert_or_psk_identity, + .size = instance->public_cert_or_psk_identity_size + })) { + goto error; + } + } + +#ifdef ANJAY_WITH_SECURITY_STRUCTURED + if ((instance->private_cert_or_psk_key + || instance->private_cert_or_psk_key_size) + + (instance->private_key.desc.source + != AVS_CRYPTO_DATA_SOURCE_EMPTY) + + (instance->psk_key.desc.source + != AVS_CRYPTO_DATA_SOURCE_EMPTY) + > 1) { + security_log(ERROR, _("more than one variant of the Secret Key field " + "specified at the same time")); + goto error; + } + if (instance->private_key.desc.source != AVS_CRYPTO_DATA_SOURCE_EMPTY) { + if (_standalone_sec_init_private_key_resource( + &new_instance->private_cert_or_psk_key, + SEC_KEY_AS_KEY_EXTERNAL, + &instance->private_key)) { + goto error; + } + } else if (instance->psk_key.desc.source != AVS_CRYPTO_DATA_SOURCE_EMPTY) { + if (_standalone_sec_init_psk_key_resource( + &new_instance->private_cert_or_psk_key, + SEC_KEY_AS_KEY_EXTERNAL, + &instance->psk_key)) { + goto error; + } + } else +#endif // ANJAY_WITH_SECURITY_STRUCTURED + { + new_instance->private_cert_or_psk_key.type = SEC_KEY_AS_DATA; + if (_standalone_raw_buffer_clone( + &new_instance->private_cert_or_psk_key.value.data, + &(const standalone_raw_buffer_t) { + .data = (void *) (intptr_t) + instance->private_cert_or_psk_key, + .size = instance->private_cert_or_psk_key_size + })) { + goto error; + } + } + + if (_standalone_raw_buffer_clone( + &new_instance->server_public_key, + &(const standalone_raw_buffer_t) { + .data = (void *) (intptr_t) instance->server_public_key, + .size = instance->server_public_key_size + })) { + goto error; + } + + if (!new_instance->is_bootstrap) { + new_instance->ssid = instance->ssid; + new_instance->present_resources[SEC_RES_SHORT_SERVER_ID] = true; + } + +#ifdef ANJAY_WITH_LWM2M11 + if (instance->matching_type) { + // values higher than INT8_MAX are invalid anyway, + // and validation will be done in _standalone_sec_object_validate(). + // This is simpler than adding another validation here. + new_instance->matching_type = + (int8_t) AVS_MIN(*instance->matching_type, INT8_MAX); + } + if (instance->server_name_indication + && !(new_instance->server_name_indication = + avs_strdup(instance->server_name_indication))) { + security_log(ERROR, _("Could not copy SNI: out of memory")); + goto error; + } + if (instance->certificate_usage) { + // same story as with Matching Type + new_instance->certificate_usage = + (int8_t) AVS_MIN(*instance->certificate_usage, INT8_MAX); + } + if (instance->ciphersuites.num_ids > ANJAY_ID_INVALID) { + security_log(ERROR, _("Too many ciphersuites specified")); + goto error; + } + for (int32_t i = (int32_t) instance->ciphersuites.num_ids - 1; i >= 0; + --i) { + AVS_LIST(sec_cipher_instance_t) cipher_instance = + AVS_LIST_NEW_ELEMENT(sec_cipher_instance_t); + if (!cipher_instance) { + security_log(ERROR, + _("Could not copy ciphersuites: out of memory")); + goto error; + } + cipher_instance->riid = (anjay_riid_t) i; + cipher_instance->cipher_id = instance->ciphersuites.ids[i]; + AVS_LIST_INSERT(&new_instance->enabled_ciphersuites, cipher_instance); + } +#endif // ANJAY_WITH_LWM2M11 +#ifdef ANJAY_WITH_COAP_OSCORE + if (instance->oscore_iid) { + new_instance->present_resources[SEC_RES_OSCORE_SECURITY_MODE] = true; + new_instance->oscore_iid = *instance->oscore_iid; + } +#endif // ANJAY_WITH_COAP_OSCORE + +#ifdef ANJAY_WITH_SMS + new_instance->sms_security_mode = instance->sms_security_mode; + new_instance->present_resources[SEC_RES_SMS_SECURITY_MODE] = + !_standalone_sec_validate_sms_security_mode( + (int32_t) instance->sms_security_mode); + +# ifdef ANJAY_WITH_SECURITY_STRUCTURED + if (instance->sms_psk_identity.desc.source + != AVS_CRYPTO_DATA_SOURCE_EMPTY) { + if (instance->sms_key_parameters || instance->sms_key_parameters_size) { + security_log(ERROR, + _("more than one variant of the SMS Binding Key " + "Parameters field specified at the same time")); + goto error; + } + if (_standalone_sec_init_psk_identity_resource( + &new_instance->sms_key_params, + SEC_KEY_AS_KEY_EXTERNAL, + &instance->sms_psk_identity)) { + goto error; + } + new_instance->present_resources[SEC_RES_SMS_BINDING_KEY_PARAMS] = true; + } else +# endif // ANJAY_WITH_SECURITY_STRUCTURED + { + new_instance->sms_key_params.type = SEC_KEY_AS_DATA; + if (_standalone_raw_buffer_clone( + &new_instance->sms_key_params.value.data, + &(const standalone_raw_buffer_t) { + .data = (void *) (intptr_t) + instance->sms_key_parameters, + .size = instance->sms_key_parameters_size + })) { + goto error; + } + new_instance->present_resources[SEC_RES_SMS_BINDING_KEY_PARAMS] = + !!instance->sms_key_parameters; + } + +# ifdef ANJAY_WITH_SECURITY_STRUCTURED + if (instance->sms_psk_key.desc.source != AVS_CRYPTO_DATA_SOURCE_EMPTY) { + if (instance->sms_secret_key || instance->sms_secret_key_size) { + security_log(ERROR, + _("more than one variant of the SMS Binding Secret " + "Key(s) field specified at the same time")); + goto error; + } + if (_standalone_sec_init_psk_key_resource(&new_instance->sms_secret_key, + SEC_KEY_AS_KEY_EXTERNAL, + &instance->sms_psk_key)) { + goto error; + } + new_instance->present_resources[SEC_RES_SMS_BINDING_SECRET_KEYS] = true; + } else +# endif // ANJAY_WITH_SECURITY_STRUCTURED + { + new_instance->sms_secret_key.type = SEC_KEY_AS_DATA; + if (_standalone_raw_buffer_clone( + &new_instance->sms_secret_key.value.data, + &(const standalone_raw_buffer_t) { + .data = (void *) (intptr_t) instance->sms_secret_key, + .size = instance->sms_secret_key_size + })) { + goto error; + } + new_instance->present_resources[SEC_RES_SMS_BINDING_SECRET_KEYS] = + !!instance->sms_secret_key; + } + + if (instance->server_sms_number) { + new_instance->sms_number = avs_strdup(instance->server_sms_number); + } +#endif // ANJAY_WITH_SMS + + _standalone_sec_instance_update_resource_presence(new_instance); + + AVS_LIST(sec_instance_t) *ptr; + AVS_LIST_FOREACH_PTR(ptr, &repr->instances) { + if ((*ptr)->iid > new_instance->iid) { + break; + } + } + AVS_LIST_INSERT(ptr, new_instance); + + if (instance->bootstrap_server) { + security_log(INFO, + _("Added instance ") "%u" _(" (bootstrap, URI: ") "%s" _( + ")"), + *inout_iid, instance->server_uri); + } else { + security_log(INFO, + _("Added instance ") "%u" _(" (SSID: ") "%u" _( + ", URI: ") "%s" _(")"), + *inout_iid, instance->ssid, instance->server_uri); + } + + _standalone_sec_mark_modified(repr); + return 0; + +error: + _standalone_sec_destroy_instances(&new_instance, true); + return -1; +} + +static int del_instance(sec_repr_t *repr, anjay_iid_t iid) { + AVS_LIST(sec_instance_t) *it; + AVS_LIST_FOREACH_PTR(it, &repr->instances) { + if ((*it)->iid == iid) { + AVS_LIST(sec_instance_t) element = AVS_LIST_DETACH(it); + _standalone_sec_destroy_instances(&element, true); + _standalone_sec_mark_modified(repr); + return 0; + } + } + + assert(0); + return ANJAY_ERR_NOT_FOUND; +} + +static int sec_list_resources(anjay_t *anjay, + const anjay_dm_object_def_t *const *obj_ptr, + anjay_iid_t iid, + anjay_dm_resource_list_ctx_t *ctx) { + (void) anjay; + const sec_instance_t *inst = + find_instance(_standalone_sec_get(obj_ptr), iid); + assert(inst); + + for (size_t resource = 0; resource < AVS_ARRAY_SIZE(SECURITY_RESOURCE_ID); + resource++) { + const anjay_rid_t rid = SECURITY_RESOURCE_ID[resource]; + anjay_dm_emit_res(ctx, rid, +#ifdef ANJAY_WITH_LWM2M11 + rid != SEC_RES_DTLS_TLS_CIPHERSUITE ? ANJAY_DM_RES_R + : ANJAY_DM_RES_RM, +#else + ANJAY_DM_RES_R, +#endif // ANJAY_WITH_LWM2M11 + inst->present_resources[rid] ? ANJAY_DM_RES_PRESENT + : ANJAY_DM_RES_ABSENT); + } + + return 0; +} + +#ifdef ANJAY_WITH_LWM2M11 +static int +sec_list_resource_instances(anjay_t *anjay, + const anjay_dm_object_def_t *const *obj_ptr, + anjay_iid_t iid, + anjay_rid_t rid, + anjay_dm_list_ctx_t *ctx) { + (void) anjay; + + assert(rid == SEC_RES_DTLS_TLS_CIPHERSUITE); + (void) rid; + + const sec_instance_t *inst = + find_instance(_standalone_sec_get(obj_ptr), iid); + assert(inst); + + AVS_LIST(sec_cipher_instance_t) it; + AVS_LIST_FOREACH(it, inst->enabled_ciphersuites) { + anjay_dm_emit(ctx, it->riid); + } + + return 0; +} + +static AVS_LIST(sec_cipher_instance_t) * +find_cipher_instance_insert_ptr(AVS_LIST(sec_cipher_instance_t) *instances, + anjay_riid_t riid) { + AVS_LIST(sec_cipher_instance_t) *it; + AVS_LIST_FOREACH_PTR(it, instances) { + if ((*it)->riid >= riid) { + break; + } + } + return it; +} + +static AVS_LIST(sec_cipher_instance_t) +find_cipher_instance(AVS_LIST(sec_cipher_instance_t) instances, + anjay_riid_t riid) { + AVS_LIST(sec_cipher_instance_t) *it = + find_cipher_instance_insert_ptr(&instances, riid); + if (it && (*it)->riid == riid) { + return *it; + } + return NULL; +} +#endif // ANJAY_WITH_LWM2M11 + +static int ret_sec_key_or_data(anjay_output_ctx_t *ctx, + const sec_key_or_data_t *res) { + switch (res->type) { + case SEC_KEY_AS_DATA: + return anjay_ret_bytes(ctx, res->value.data.data, res->value.data.size); +#if defined(ANJAY_WITH_SECURITY_STRUCTURED) \ + || defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) + case SEC_KEY_AS_KEY_EXTERNAL: + case SEC_KEY_AS_KEY_OWNED: + switch (res->value.key.info.type) { + case AVS_CRYPTO_SECURITY_INFO_CERTIFICATE_CHAIN: + return anjay_ret_certificate_chain_info( + ctx, (avs_crypto_certificate_chain_info_t) { + .desc = res->value.key.info + }); + case AVS_CRYPTO_SECURITY_INFO_PRIVATE_KEY: + return anjay_ret_private_key_info(ctx, + (avs_crypto_private_key_info_t) { + .desc = res->value.key.info + }); + case AVS_CRYPTO_SECURITY_INFO_CERT_REVOCATION_LIST: + AVS_UNREACHABLE("unsupported tag"); + return -1; + case AVS_CRYPTO_SECURITY_INFO_PSK_IDENTITY: + return anjay_ret_psk_identity_info( + ctx, (avs_crypto_psk_identity_info_t) { + .desc = res->value.key.info + }); + case AVS_CRYPTO_SECURITY_INFO_PSK_KEY: + return anjay_ret_psk_key_info(ctx, (avs_crypto_psk_key_info_t) { + .desc = res->value.key.info + }); + } +#endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) || \ + defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) */ + // fall-through + default: + AVS_UNREACHABLE("invalid value of sec_key_or_data_type_t"); + return ANJAY_ERR_INTERNAL; + } +} + +static int sec_read(anjay_t *anjay, + const anjay_dm_object_def_t *const *obj_ptr, + anjay_iid_t iid, + anjay_rid_t rid, + anjay_riid_t riid, + anjay_output_ctx_t *ctx) { + (void) anjay; + (void) riid; +#ifdef ANJAY_WITH_LWM2M11 + assert(riid == ANJAY_ID_INVALID || rid == SEC_RES_DTLS_TLS_CIPHERSUITE); +#else // ANJAY_WITH_LWM2M11 + assert(riid == ANJAY_ID_INVALID); +#endif // ANJAY_WITH_LWM2M11 + + const sec_instance_t *inst = + find_instance(_standalone_sec_get(obj_ptr), iid); + assert(inst); + + switch ((security_rid_t) rid) { + case SEC_RES_LWM2M_SERVER_URI: + return anjay_ret_string(ctx, inst->server_uri); + case SEC_RES_BOOTSTRAP_SERVER: + return anjay_ret_bool(ctx, inst->is_bootstrap); + case SEC_RES_SECURITY_MODE: + return anjay_ret_i64(ctx, (int32_t) inst->security_mode); + case SEC_RES_SERVER_PK: + return anjay_ret_bytes(ctx, inst->server_public_key.data, + inst->server_public_key.size); + case SEC_RES_PK_OR_IDENTITY: + return ret_sec_key_or_data(ctx, &inst->public_cert_or_psk_identity); + case SEC_RES_SECRET_KEY: + return ret_sec_key_or_data(ctx, &inst->private_cert_or_psk_key); + case SEC_RES_SHORT_SERVER_ID: + return anjay_ret_i64(ctx, (int32_t) inst->ssid); + case SEC_RES_CLIENT_HOLD_OFF_TIME: + return anjay_ret_i64(ctx, inst->holdoff_s); + case SEC_RES_BOOTSTRAP_TIMEOUT: + return anjay_ret_i64(ctx, inst->bs_timeout_s); +#ifdef ANJAY_WITH_SMS + case SEC_RES_SMS_SECURITY_MODE: + return anjay_ret_i64(ctx, (int32_t) inst->sms_security_mode); + case SEC_RES_SMS_BINDING_KEY_PARAMS: + return ret_sec_key_or_data(ctx, &inst->sms_key_params); + case SEC_RES_SMS_BINDING_SECRET_KEYS: + return ret_sec_key_or_data(ctx, &inst->sms_secret_key); + case SEC_RES_SERVER_SMS_NUMBER: + return anjay_ret_string(ctx, inst->sms_number); +#endif // ANJAY_WITH_SMS +#ifdef ANJAY_WITH_LWM2M11 + case SEC_RES_MATCHING_TYPE: + return anjay_ret_u64(ctx, (uint64_t) (uint32_t) inst->matching_type); + case SEC_RES_SNI: + assert(inst->server_name_indication); + return anjay_ret_string(ctx, inst->server_name_indication); + case SEC_RES_CERTIFICATE_USAGE: + return anjay_ret_u64(ctx, + (uint64_t) (uint32_t) inst->certificate_usage); + case SEC_RES_DTLS_TLS_CIPHERSUITE: { + AVS_LIST(const sec_cipher_instance_t) rinst = + find_cipher_instance(inst->enabled_ciphersuites, riid); + if (!rinst) { + return ANJAY_ERR_NOT_FOUND; + } + return anjay_ret_u64(ctx, rinst->cipher_id); + } +#endif // ANJAY_WITH_LWM2M11 +#ifdef ANJAY_WITH_COAP_OSCORE + case SEC_RES_OSCORE_SECURITY_MODE: + return anjay_ret_objlnk(ctx, ANJAY_DM_OID_OSCORE, inst->oscore_iid); +#endif // ANJAY_WITH_COAP_OSCORE + default: + AVS_UNREACHABLE("Read handler called on unknown Security resource"); + return ANJAY_ERR_NOT_IMPLEMENTED; + } +} + +#ifdef ANJAY_WITH_LWM2M11 +static AVS_LIST(sec_cipher_instance_t) +find_or_create_cipher_instance(AVS_LIST(sec_cipher_instance_t) *instances, + anjay_riid_t riid) { + AVS_LIST(sec_cipher_instance_t) *it = + find_cipher_instance_insert_ptr(instances, riid); + + AVS_LIST(sec_cipher_instance_t) cipher = + AVS_LIST_INSERT_NEW(sec_cipher_instance_t, it); + if (cipher) { + cipher->riid = riid; + } + return cipher; +} +#endif // ANJAY_WITH_LWM2M11 + +static int fetch_sec_key_or_data(anjay_input_ctx_t *ctx, + sec_key_or_data_t *res) { + _standalone_sec_key_or_data_cleanup(res, true); + assert(res->type == SEC_KEY_AS_DATA); + assert(!res->prev_ref); + assert(!res->next_ref); + return _standalone_io_fetch_bytes(ctx, &res->value.data); +} + +static int sec_write(anjay_t *anjay, + const anjay_dm_object_def_t *const *obj_ptr, + anjay_iid_t iid, + anjay_rid_t rid, + anjay_riid_t riid, + anjay_input_ctx_t *ctx) { + (void) anjay; + (void) riid; +#ifdef ANJAY_WITH_LWM2M11 + assert(riid == ANJAY_ID_INVALID || rid == SEC_RES_DTLS_TLS_CIPHERSUITE); +#else // ANJAY_WITH_LWM2M11 + assert(riid == ANJAY_ID_INVALID); +#endif // ANJAY_WITH_LWM2M11 + sec_repr_t *repr = _standalone_sec_get(obj_ptr); + sec_instance_t *inst = find_instance(repr, iid); + int retval; + assert(inst); + + _standalone_sec_mark_modified(repr); + + switch ((security_rid_t) rid) { + case SEC_RES_LWM2M_SERVER_URI: + retval = _standalone_io_fetch_string(ctx, &inst->server_uri); + break; + case SEC_RES_BOOTSTRAP_SERVER: + retval = anjay_get_bool(ctx, &inst->is_bootstrap); + break; + case SEC_RES_SECURITY_MODE: + retval = _standalone_sec_fetch_security_mode(ctx, &inst->security_mode); + break; + case SEC_RES_PK_OR_IDENTITY: + retval = fetch_sec_key_or_data(ctx, &inst->public_cert_or_psk_identity); + break; + case SEC_RES_SERVER_PK: + retval = _standalone_io_fetch_bytes(ctx, &inst->server_public_key); + break; + case SEC_RES_SECRET_KEY: + retval = fetch_sec_key_or_data(ctx, &inst->private_cert_or_psk_key); + break; + case SEC_RES_SHORT_SERVER_ID: + retval = _standalone_sec_fetch_short_server_id(ctx, &inst->ssid); + break; + case SEC_RES_CLIENT_HOLD_OFF_TIME: + retval = anjay_get_i32(ctx, &inst->holdoff_s); + break; + case SEC_RES_BOOTSTRAP_TIMEOUT: + retval = anjay_get_i32(ctx, &inst->bs_timeout_s); + break; +#ifdef ANJAY_WITH_SMS + case SEC_RES_SMS_SECURITY_MODE: + retval = _standalone_sec_fetch_sms_security_mode( + ctx, &inst->sms_security_mode); + break; + case SEC_RES_SMS_BINDING_KEY_PARAMS: + retval = fetch_sec_key_or_data(ctx, &inst->sms_key_params); + break; + case SEC_RES_SMS_BINDING_SECRET_KEYS: + retval = fetch_sec_key_or_data(ctx, &inst->sms_secret_key); + break; + case SEC_RES_SERVER_SMS_NUMBER: + retval = _standalone_io_fetch_string(ctx, &inst->sms_number); + break; +#endif // ANJAY_WITH_SMS +#ifdef ANJAY_WITH_LWM2M11 + case SEC_RES_MATCHING_TYPE: { + uint32_t matching_type; + if (!(retval = anjay_get_u32(ctx, &matching_type))) { + if (matching_type > 3) { + retval = ANJAY_ERR_BAD_REQUEST; + } else { + inst->matching_type = (int8_t) matching_type; + } + } + break; + } + case SEC_RES_SNI: + retval = + _standalone_io_fetch_string(ctx, &inst->server_name_indication); + break; + case SEC_RES_CERTIFICATE_USAGE: { + uint32_t certificate_usage; + if (!(retval = anjay_get_u32(ctx, &certificate_usage))) { + if (certificate_usage > 3) { + retval = ANJAY_ERR_BAD_REQUEST; + } else { + inst->certificate_usage = (int8_t) certificate_usage; + } + } + break; + } +# ifdef ANJAY_WITH_COAP_OSCORE + case SEC_RES_OSCORE_SECURITY_MODE: { + anjay_oid_t oid; + if (!(retval = anjay_get_objlnk(ctx, &oid, &inst->oscore_iid)) + && oid != ANJAY_DM_OID_OSCORE) { + retval = ANJAY_ERR_BAD_REQUEST; + } + break; + } +# endif // ANJAY_WITH_COAP_OSCORE + case SEC_RES_DTLS_TLS_CIPHERSUITE: { + uint32_t cipher_id; + if (!(retval = anjay_get_u32(ctx, &cipher_id))) { + if (cipher_id == 0) { + security_log( + WARNING, + _("TLS-NULL-WITH-NULL-NULL cipher is not allowed")); + retval = ANJAY_ERR_BAD_REQUEST; + } else if (cipher_id > UINT16_MAX) { + security_log(WARNING, + _("Ciphersuite ID > 65535 is not allowed")); + retval = ANJAY_ERR_BAD_REQUEST; + } else { + AVS_LIST(sec_cipher_instance_t) cipher = + find_or_create_cipher_instance( + &inst->enabled_ciphersuites, riid); + if (!cipher) { + retval = ANJAY_ERR_INTERNAL; + } else { + cipher->cipher_id = cipher_id; + } + } + } + break; + } +#endif // ANJAY_WITH_LWM2M11 + default: + AVS_UNREACHABLE("Write handler called on unknown Security resource"); + return ANJAY_ERR_NOT_FOUND; + } + + if (!retval) { + inst->present_resources[rid] = true; + } + + return retval; +} + +#ifdef ANJAY_WITH_LWM2M11 +static int sec_resource_reset(anjay_t *anjay, + const anjay_dm_object_def_t *const *obj_ptr, + anjay_iid_t iid, + anjay_rid_t rid) { + (void) anjay; + + assert(rid == SEC_RES_DTLS_TLS_CIPHERSUITE); + (void) rid; + + const sec_instance_t *inst = + find_instance(_standalone_sec_get(obj_ptr), iid); + assert(inst); + + AVS_LIST_CLEAR(&inst->enabled_ciphersuites); + return 0; +} + +# ifdef ANJAY_WITH_LWM2M12 +static int +sec_resource_instance_remove(anjay_t *anjay, + const anjay_dm_object_def_t *const *obj_ptr, + anjay_iid_t iid, + anjay_rid_t rid, + anjay_riid_t riid) { + (void) anjay; + + assert(rid == SEC_RES_DTLS_TLS_CIPHERSUITE); + (void) rid; + + sec_instance_t *inst = find_instance(_standalone_sec_get(obj_ptr), iid); + assert(inst); + AVS_LIST(sec_cipher_instance_t) *rinst_ptr = + find_cipher_instance_insert_ptr(&inst->enabled_ciphersuites, riid); + assert(rinst_ptr && *rinst_ptr && (*rinst_ptr)->riid); + AVS_LIST_DELETE(rinst_ptr); + return 0; +} +# endif // ANJAY_WITH_LWM2M12 +#endif // ANJAY_WITH_LWM2M11 + +static int sec_list_instances(anjay_t *anjay, + const anjay_dm_object_def_t *const *obj_ptr, + anjay_dm_list_ctx_t *ctx) { + (void) anjay; + + sec_repr_t *repr = _standalone_sec_get(obj_ptr); + AVS_LIST(sec_instance_t) it; + AVS_LIST_FOREACH(it, repr->instances) { + anjay_dm_emit(ctx, it->iid); + } + return 0; +} + +static int sec_instance_create(anjay_t *anjay, + const anjay_dm_object_def_t *const *obj_ptr, + anjay_iid_t iid) { + (void) anjay; + sec_repr_t *repr = _standalone_sec_get(obj_ptr); + assert(iid != ANJAY_ID_INVALID); + + AVS_LIST(sec_instance_t) created = AVS_LIST_NEW_ELEMENT(sec_instance_t); + if (!created) { + return ANJAY_ERR_INTERNAL; + } + + init_instance(created, iid); + + AVS_LIST(sec_instance_t) *ptr; + AVS_LIST_FOREACH_PTR(ptr, &repr->instances) { + if ((*ptr)->iid > created->iid) { + break; + } + } + + AVS_LIST_INSERT(ptr, created); + _standalone_sec_mark_modified(repr); + return 0; +} + +static int sec_instance_remove(anjay_t *anjay, + const anjay_dm_object_def_t *const *obj_ptr, + anjay_iid_t iid) { + (void) anjay; + return del_instance(_standalone_sec_get(obj_ptr), iid); +} + +static int sec_transaction_begin(anjay_t *anjay, + const anjay_dm_object_def_t *const *obj_ptr) { + (void) anjay; + return _standalone_sec_transaction_begin_impl(_standalone_sec_get(obj_ptr)); +} + +static int sec_transaction_commit(anjay_t *anjay, + const anjay_dm_object_def_t *const *obj_ptr) { + (void) anjay; + return _standalone_sec_transaction_commit_impl( + _standalone_sec_get(obj_ptr)); +} + +static int +sec_transaction_validate(anjay_t *anjay, + const anjay_dm_object_def_t *const *obj_ptr) { + (void) anjay; + return _standalone_sec_transaction_validate_impl(anjay, _standalone_sec_get( + obj_ptr)); +} + +static int +sec_transaction_rollback(anjay_t *anjay, + const anjay_dm_object_def_t *const *obj_ptr) { + (void) anjay; + return _standalone_sec_transaction_rollback_impl( + _standalone_sec_get(obj_ptr)); +} + +static int sec_instance_reset(anjay_t *anjay, + const anjay_dm_object_def_t *const *obj_ptr, + anjay_iid_t iid) { + (void) anjay; + sec_instance_t *inst = find_instance(_standalone_sec_get(obj_ptr), iid); + assert(inst); + + _standalone_sec_destroy_instance_fields(inst, true); + init_instance(inst, iid); + return 0; +} + +static const anjay_dm_object_def_t SECURITY = { + .oid = ANJAY_DM_OID_SECURITY, + .handlers = { + .list_instances = sec_list_instances, + .instance_create = sec_instance_create, + .instance_remove = sec_instance_remove, + .instance_reset = sec_instance_reset, + .list_resources = sec_list_resources, +#ifdef ANJAY_WITH_LWM2M11 + .list_resource_instances = sec_list_resource_instances, +#endif // ANJAY_WITH_LWM2M11 + .resource_read = sec_read, + .resource_write = sec_write, +#ifdef ANJAY_WITH_LWM2M11 + .resource_reset = sec_resource_reset, +#endif // ANJAY_WITH_LWM2M11 + .transaction_begin = sec_transaction_begin, + .transaction_commit = sec_transaction_commit, + .transaction_validate = sec_transaction_validate, + .transaction_rollback = sec_transaction_rollback +#ifdef ANJAY_WITH_LWM2M12 + , + .resource_instance_remove = sec_resource_instance_remove +#endif // ANJAY_WITH_LWM2M12 + } +}; + +sec_repr_t *_standalone_sec_get(const anjay_dm_object_def_t *const *obj_ptr) { + assert(obj_ptr && *obj_ptr == &SECURITY); + return AVS_CONTAINER_OF(obj_ptr, sec_repr_t, def); +} + +int standalone_security_object_add_instance( + const anjay_dm_object_def_t *const *obj_ptr, + const standalone_security_instance_t *instance, + anjay_iid_t *inout_iid) { + int retval = -1; + sec_repr_t *repr = _standalone_sec_get(obj_ptr); + if (!repr) { + security_log(ERROR, _("Security object is not registered")); + retval = -1; + } else { + const bool modified_since_persist = repr->modified_since_persist; + if (!(retval = add_instance(repr, instance, inout_iid)) + && (retval = _standalone_sec_object_validate_and_process_keys( + repr->anjay, repr))) { + (void) del_instance(repr, *inout_iid); + if (!modified_since_persist) { + /* validation failed and so in the end no instace is added */ + _standalone_sec_clear_modified(repr); + } + } + + if (!retval) { + if (anjay_notify_instances_changed(repr->anjay, SECURITY.oid)) { + security_log(WARNING, _("Could not schedule socket reload")); + } + } + } + return retval; +} + +void standalone_security_object_cleanup( + const anjay_dm_object_def_t *const *obj_ptr) { + sec_repr_t *repr = _standalone_sec_get(obj_ptr); + if (repr->in_transaction) { + _standalone_sec_destroy_instances(&repr->instances, true); + _standalone_sec_destroy_instances(&repr->saved_instances, + repr->saved_modified_since_persist); + } else { + assert(!repr->saved_instances); + _standalone_sec_destroy_instances(&repr->instances, + repr->modified_since_persist); + } +#ifdef ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT + if (repr->prng_ctx && !repr->prng_allocated_by_user) { + avs_crypto_prng_free(&repr->prng_ctx); + } +#endif // ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT + avs_free(repr); +} + +void standalone_security_object_purge( + const anjay_dm_object_def_t *const *obj_ptr) { + sec_repr_t *repr = _standalone_sec_get(obj_ptr); + + if (!repr) { + security_log(ERROR, _("Security object is not registered")); + } else { + if (repr->instances) { + _standalone_sec_mark_modified(repr); + } + _standalone_sec_destroy_instances(&repr->saved_instances, true); + _standalone_sec_destroy_instances(&repr->instances, true); + if (anjay_notify_instances_changed(repr->anjay, SECURITY.oid)) { + security_log(WARNING, _("Could not schedule socket reload")); + } + } +} + +bool standalone_security_object_is_modified( + const anjay_dm_object_def_t *const *obj_ptr) { + bool result = false; + sec_repr_t *repr = _standalone_sec_get(obj_ptr); + if (!repr) { + security_log(ERROR, _("Security object is not registered")); + } else { + if (repr->in_transaction) { + result = repr->saved_modified_since_persist; + } else { + result = repr->modified_since_persist; + } + } + return result; +} + +const anjay_dm_object_def_t ** +standalone_security_object_install(anjay_t *anjay) { + sec_repr_t *repr = (sec_repr_t *) avs_calloc(1, sizeof(sec_repr_t)); + if (!repr) { + security_log(ERROR, _("out of memory")); + return NULL; + } + repr->def = &SECURITY; + repr->anjay = anjay; + AVS_STATIC_ASSERT(offsetof(sec_repr_t, def) == 0, def_is_first_field); + if (anjay_register_object(anjay, &repr->def)) { + avs_free(repr); + return NULL; + } + return &repr->def; +} + +#ifdef ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT +const anjay_dm_object_def_t **standalone_security_object_install_with_hsm( + anjay_t *anjay, + const standalone_security_hsm_configuration_t *hsm_config, + avs_crypto_prng_ctx_t *prng_ctx) { + assert(anjay); + bool prng_allocated_by_user = !!prng_ctx; + if (!prng_allocated_by_user) { + prng_ctx = avs_crypto_prng_new(NULL, NULL); + if (!prng_ctx) { + security_log(ERROR, _("Could not create PRNG context")); + return NULL; + } + } + const anjay_dm_object_def_t **result = + standalone_security_object_install(anjay); + if (result && hsm_config) { + sec_repr_t *repr = _standalone_sec_get(result); + repr->hsm_config = *hsm_config; + repr->prng_ctx = prng_ctx; + repr->prng_allocated_by_user = prng_allocated_by_user; + } + return result; +} + +static void +mark_hsm_sec_key_or_data_permanent(sec_repr_t *repr, + sec_key_or_data_t *sec_key_or_data) { + if (sec_key_or_data->type == SEC_KEY_AS_KEY_OWNED) { + sec_key_or_data->type = SEC_KEY_AS_KEY_EXTERNAL; + repr->modified_since_persist = true; + for (sec_key_or_data_t *it = sec_key_or_data->prev_ref; it; + it = it->prev_ref) { + it->type = SEC_KEY_AS_KEY_EXTERNAL; + if (repr->in_transaction) { + repr->saved_modified_since_persist = true; + } + } + for (sec_key_or_data_t *it = sec_key_or_data->next_ref; it; + it = it->next_ref) { + it->type = SEC_KEY_AS_KEY_EXTERNAL; + if (repr->in_transaction) { + repr->saved_modified_since_persist = true; + } + } + } +} + +static void mark_hsm_instance_permanent(sec_repr_t *repr, + sec_instance_t *instance) { + mark_hsm_sec_key_or_data_permanent(repr, + &instance->public_cert_or_psk_identity); + mark_hsm_sec_key_or_data_permanent(repr, + &instance->private_cert_or_psk_key); +# ifdef ANJAY_WITH_SMS + mark_hsm_sec_key_or_data_permanent(repr, &instance->sms_key_params); + mark_hsm_sec_key_or_data_permanent(repr, &instance->sms_secret_key); +# endif // ANJAY_WITH_SMS +} + +void standalone_security_mark_hsm_permanent( + const anjay_dm_object_def_t *const *obj_ptr, anjay_ssid_t ssid) { + sec_repr_t *repr = _standalone_sec_get(obj_ptr); + if (!repr) { + security_log(ERROR, _("Security object is not registered")); + } else { + AVS_LIST(sec_instance_t) instance; + AVS_LIST_FOREACH(instance, repr->instances) { + if (ssid == ANJAY_SSID_ANY + || (ssid == ANJAY_SSID_BOOTSTRAP + && instance->present_resources[SEC_RES_BOOTSTRAP_SERVER] + && instance->is_bootstrap) + || ((!instance->present_resources[SEC_RES_BOOTSTRAP_SERVER] + || !instance->is_bootstrap) + && instance->present_resources[SEC_RES_SHORT_SERVER_ID] + && ssid == instance->ssid)) { + mark_hsm_instance_permanent(repr, instance); + } + } + } +} +#endif // ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT diff --git a/standalone/security/standalone_mod_security.h b/standalone/security/standalone_mod_security.h new file mode 100644 index 00000000..7136b7f2 --- /dev/null +++ b/standalone/security/standalone_mod_security.h @@ -0,0 +1,160 @@ +#ifndef ANJAY_STANDALONE_SECURITY_SECURITY_H +#define ANJAY_STANDALONE_SECURITY_SECURITY_H + +#include + +#include "standalone_security.h" + +typedef enum { + SEC_RES_LWM2M_SERVER_URI = 0, + SEC_RES_BOOTSTRAP_SERVER = 1, + SEC_RES_SECURITY_MODE = 2, + SEC_RES_PK_OR_IDENTITY = 3, + SEC_RES_SERVER_PK = 4, + SEC_RES_SECRET_KEY = 5, +#ifdef ANJAY_WITH_SMS + SEC_RES_SMS_SECURITY_MODE = 6, + SEC_RES_SMS_BINDING_KEY_PARAMS = 7, + SEC_RES_SMS_BINDING_SECRET_KEYS = 8, + SEC_RES_SERVER_SMS_NUMBER = 9, +#endif // ANJAY_WITH_SMS + SEC_RES_SHORT_SERVER_ID = 10, + SEC_RES_CLIENT_HOLD_OFF_TIME = 11, + SEC_RES_BOOTSTRAP_TIMEOUT = 12, +#ifdef ANJAY_WITH_LWM2M11 + SEC_RES_MATCHING_TYPE = 13, + SEC_RES_SNI = 14, + SEC_RES_CERTIFICATE_USAGE = 15, + SEC_RES_DTLS_TLS_CIPHERSUITE = 16, +#endif // ANJAY_WITH_LWM2M11 +#ifdef ANJAY_WITH_COAP_OSCORE + SEC_RES_OSCORE_SECURITY_MODE = 17, +#endif // ANJAY_WITH_COAP_OSCORE + _SEC_RES_COUNT +} security_rid_t; + +typedef struct { + anjay_riid_t riid; + uint32_t cipher_id; +} sec_cipher_instance_t; + +typedef enum { + SEC_KEY_AS_DATA, + SEC_KEY_AS_KEY_EXTERNAL, + SEC_KEY_AS_KEY_OWNED +} sec_key_or_data_type_t; + +typedef struct { + void *data; + /** Amount of bytes currently stored in the buffer. */ + size_t size; + /** Amount of bytes that might be stored in the buffer. */ + size_t capacity; +} standalone_raw_buffer_t; + +typedef struct sec_key_or_data_struct sec_key_or_data_t; +struct sec_key_or_data_struct { + sec_key_or_data_type_t type; + union { + standalone_raw_buffer_t data; +#if defined(ANJAY_WITH_SECURITY_STRUCTURED) \ + || defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) + struct { + avs_crypto_security_info_union_t info; + void *heap_buf; + } key; +#endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) || \ + defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) */ + } value; + + // HERE GOES MAGIC. + // + // sec_key_or_data_t is, in a way, semantically something like a + // shared_ptr>. + // Note that the instances of sec_key_or_data_t itself are NOT individually + // allocated on the heap, as they are fields in sec_instance_t. + // + // These two fields organize multiple instances of sec_key_or_data_t that + // refer to the same heap buffer (either via value.data.data or + // value.key.heap_buf) in a doubly linked list. That way, when multiple + // instances referring to the same buffer exist, and one of them is to be + // cleaned up, that cleaned up instance can be removed from the list without + // needing any other pointers (which wouldn't work if that was a singly + // linked list). + // + // When the last (or only) instance referring to a given buffer is being + // cleaned up, both prev_ref and next_ref will be NULL, which is a signal + // to actually free the resources. + // + // These pointers are manipulated in _standalone_sec_key_or_data_cleanup() + // and sec_key_or_data_create_ref(), so see there for the actual + // implementation. Also note that in practice, it is not expected for more + // than two references (one in instances and one in saved_instances) to the + // same buffer to exist, but a generic solution isn't more complicated, + // so... + sec_key_or_data_t *prev_ref; + sec_key_or_data_t *next_ref; +}; + +typedef struct { + anjay_iid_t iid; + char *server_uri; + bool is_bootstrap; + anjay_security_mode_t security_mode; + sec_key_or_data_t public_cert_or_psk_identity; + sec_key_or_data_t private_cert_or_psk_key; + standalone_raw_buffer_t server_public_key; + + anjay_ssid_t ssid; + int32_t holdoff_s; + int32_t bs_timeout_s; + +#ifdef ANJAY_WITH_SMS + anjay_sms_security_mode_t sms_security_mode; + sec_key_or_data_t sms_key_params; + sec_key_or_data_t sms_secret_key; + char *sms_number; +#endif // ANJAY_WITH_SMS + +#ifdef ANJAY_WITH_LWM2M11 + int8_t matching_type; + char *server_name_indication; + int8_t certificate_usage; + AVS_LIST(sec_cipher_instance_t) enabled_ciphersuites; +# ifdef ANJAY_WITH_COAP_OSCORE + anjay_iid_t oscore_iid; +# endif // ANJAY_WITH_COAP_OSCORE +#endif // ANJAY_WITH_LWM2M11 + + bool present_resources[_SEC_RES_COUNT]; +} sec_instance_t; + +typedef struct { + const anjay_dm_object_def_t *def; + anjay_t *anjay; + AVS_LIST(sec_instance_t) instances; + AVS_LIST(sec_instance_t) saved_instances; + bool modified_since_persist; + bool saved_modified_since_persist; + bool in_transaction; +#ifdef ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT + standalone_security_hsm_configuration_t hsm_config; + avs_crypto_prng_ctx_t *prng_ctx; + bool prng_allocated_by_user; +#endif // ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT +} sec_repr_t; + +static inline void _standalone_sec_mark_modified(sec_repr_t *repr) { + repr->modified_since_persist = true; +} + +static inline void _standalone_sec_clear_modified(sec_repr_t *repr) { + repr->modified_since_persist = false; +} + +void _standalone_sec_instance_update_resource_presence(sec_instance_t *inst); + +#define security_log(level, ...) avs_log(security, level, __VA_ARGS__) +#define _(Arg) AVS_DISPOSABLE_LOG(Arg) + +#endif /* ANJAY_STANDALONE_SECURITY_SECURITY_H */ diff --git a/standalone/security/standalone_security.h b/standalone/security/standalone_security.h new file mode 100644 index 00000000..47ef2a08 --- /dev/null +++ b/standalone/security/standalone_security.h @@ -0,0 +1,447 @@ +#ifndef ANJAY_STANDALONE_ANJAY_SECURITY_H +#define ANJAY_STANDALONE_ANJAY_SECURITY_H + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + /** Resource: Short Server ID */ + anjay_ssid_t ssid; + /** Resource: LwM2M Server URI */ + const char *server_uri; + /** Resource: Bootstrap Server */ + bool bootstrap_server; + /** Resource: Security Mode */ + anjay_security_mode_t security_mode; + /** Resource: Client Hold Off Time */ + int32_t client_holdoff_s; + /** Resource: Bootstrap Server Account Timeout */ + int32_t bootstrap_timeout_s; + /** Resource: Public Key Or Identity */ + const uint8_t *public_cert_or_psk_identity; + size_t public_cert_or_psk_identity_size; + /** Resource: Secret Key */ + const uint8_t *private_cert_or_psk_key; + size_t private_cert_or_psk_key_size; + /** Resource: Server Public Key */ + const uint8_t *server_public_key; + size_t server_public_key_size; +#ifdef ANJAY_WITH_SMS + /** Resource: SMS Security Mode */ + anjay_sms_security_mode_t sms_security_mode; + /** Resource: SMS Binding Key Parameters */ + const uint8_t *sms_key_parameters; + size_t sms_key_parameters_size; + /** Resource: SMS Binding Secret Key(s) */ + const uint8_t *sms_secret_key; + size_t sms_secret_key_size; + /** Resource: LwM2M Server SMS Number */ + const char *server_sms_number; +#endif // ANJAY_WITH_SMS +#ifdef ANJAY_WITH_LWM2M11 + /** Resource: Matching Type (NULL for not present) */ + const uint8_t *matching_type; + /** Resource: SNI */ + const char *server_name_indication; + /** Resource: Certificate Usage (NULL for not present) */ + const uint8_t *certificate_usage; + /** Resource: DTLS/TLS Ciphersuite; + * Note: Passing a value with num_ids == 0 (default) will cause the + * resource to be absent, resulting in a fallback to defaults. */ + avs_net_socket_tls_ciphersuites_t ciphersuites; +# ifdef ANJAY_WITH_COAP_OSCORE + /** Resource: OSCORE Security Mode (NULL for not present) */ + const anjay_iid_t *oscore_iid; +# endif // ANJAY_WITH_COAP_OSCORE +#endif // ANJAY_WITH_LWM2M11 +#ifdef ANJAY_WITH_SECURITY_STRUCTURED + /** Resource: Public Key Or Identity; + * This is an alternative to the @p public_cert_or_psk_identity and + * @p psk_identity fields that may be used only if @p security_mode is + * either @ref ANJAY_SECURITY_CERTIFICATE or @ref ANJAY_SECURITY_EST; it is + * also an error to specify non-empty values for more than one of these + * fields at the same time. */ + avs_crypto_certificate_chain_info_t public_cert; + /** Resource: Secret Key; + * This is an alternative to the @p private_cert_or_psk_key and @ref psk_key + * fields that may be used only if @p security_mode is either + * @ref ANJAY_SECURITY_CERTIFICATE or @ref ANJAY_SECURITY_EST; it is also an + * error to specify non-empty values for more than one of these fields at + * the same time. */ + avs_crypto_private_key_info_t private_key; + /** Resource: Public Key Or Identity; + * This is an alternative to the @p public_cert_or_psk_identity and + * @ref public_cert fields that may be used only if @p security_mode is + * @ref ANJAY_SECURITY_PSK; it is also an error to specify non-empty values + * for more than one of these fields at the same time. */ + avs_crypto_psk_identity_info_t psk_identity; + /** Resource: Secret Key; + * This is an alternative to the @p private_cert_or_psk_key and + * @ref private_key fields that may be used only if @p security_mode is + * @ref ANJAY_SECURITY_PSK; it is also an error to specify non-empty values + * for more than one of these fields at the same time. */ + avs_crypto_psk_key_info_t psk_key; +# ifdef ANJAY_WITH_SMS + /** Resource: SMS Binding Key Parameters; + * This is an alternative to the @p sms_key_parameters field that may be + * used only if @p sms_security_mode is @ref ANJAY_SMS_SECURITY_DTLS_PSK; it + * is also an error to specify non-empty values for both fields at the same + * time. */ + avs_crypto_psk_identity_info_t sms_psk_identity; + /** Resource: SMS Binding Secret Key(s); + * This is an alternative to the @p sms_secret_key field that may be used + * only if @p sms_security_mode is @ref ANJAY_SMS_SECURITY_DTLS_PSK; it is + * also an error to specify non-empty values for both fields at the same + * time. */ + avs_crypto_psk_key_info_t sms_psk_key; +# endif // ANJAY_WITH_SMS +#endif // ANJAY_WITH_SECURITY_STRUCTURED +} standalone_security_instance_t; + +/** + * Adds new Instance of Security Object and returns newly created Instance id + * via @p inout_iid . + * + * Note: if @p *inout_iid is set to @ref ANJAY_ID_INVALID then the Instance id + * is generated automatically, otherwise value of @p *inout_iid is used as a + * new Security Instance Id. + * + * Note: @p instance may be safely freed by the user code after this function + * finishes (internally a deep copy of @ref standalone_security_instance_t is + * performed). + * + * Warning: calling this function during active communication with Bootstrap + * Server may yield undefined behavior and unexpected failures may occur. + * + * @param obj_ptr Installed Security Object to operate on. + * @param instance Security Instance to insert. + * @param inout_iid Security Instance id to use or @ref ANJAY_ID_INVALID . + * + * @return 0 on success, negative value in case of an error or if the instance + * of specified id already exists. + */ +int standalone_security_object_add_instance( + const anjay_dm_object_def_t *const *obj_ptr, + const standalone_security_instance_t *instance, + anjay_iid_t *inout_iid); + +/** + * Purges instances of Security Object leaving it in an empty state. + * + * @param obj_ptr Installed Security Object to purge. + */ +void standalone_security_object_purge( + const anjay_dm_object_def_t *const *obj_ptr); + +/** + * Dumps Security Object Instances to the @p out_stream. + * + * @param obj_ptr Installed Security Object to operate on. + * @param out_stream Stream to write to. + * @returns AVS_OK in case of success, or an error code. + */ +avs_error_t +standalone_security_object_persist(const anjay_dm_object_def_t *const *obj_ptr, + avs_stream_t *out_stream); + +/** + * Attempts to restore Security Object Instances from specified @p in_stream. + * + * Note: if restore fails, then Security Object will be left untouched, on + * success though all Instances stored within the Object will be purged. + * + * @param obj_ptr Installed Security Object to operate on. + * @param in_stream Stream to read from. + * @returns AVS_OK in case of success, or an error code. + */ +avs_error_t +standalone_security_object_restore(const anjay_dm_object_def_t *const *obj_ptr, + avs_stream_t *in_stream); + +/** + * Checks whether the Security Object has been modified since + * last successful call to @ref standalone_security_object_persist or @ref + * standalone_security_object_restore. + */ +bool standalone_security_object_is_modified( + const anjay_dm_object_def_t *const *obj_ptr); + +/** + * Creates the Security Object and installs it in an Anjay instance using + * @ref anjay_register_object. + * + * Do NOT attempt to call @ref anjay_register_object with this object manually, + * and do NOT try to use the same instance of the Security object with another + * Anjay instance. + * + * @param anjay Anjay instance for which the Security Object is installed. + * + * @returns Handle to the created object that can be passed to other functions + * declared in this file, or NULL in case of error. + */ +const anjay_dm_object_def_t ** +standalone_security_object_install(anjay_t *anjay); + +/** + * Frees all system resources allocated by the Security Object. + * + * NOTE: Attempting to call this function before deregistering + * the object using @ref anjay_unregister_object, @ref anjay_delete or + * @ref anjay_delete_with_core_persistence is undefined behavior. + * + * @param obj_ptr Server Object to operate on. + */ +void standalone_security_object_cleanup( + const anjay_dm_object_def_t *const *obj_ptr); + +#ifdef ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT +/** + * Type for a callback function that will be called by the Security object + * implementation whenever a query string for storing a new security credential + * (provisioned by means other than EST) in external security engine is + * necessary. See also the fields of the @ref + * standalone_security_hsm_configuration_t structure. + * + * @param iid ID of the Security object Instance for which the credential + * is to be stored. + * + * @param ssid Short Server ID of the server account for which the + * credential is to be stored (@ref ANJAY_SSID_BOOTSTRAP in + * case of the Bootstrap Server). + * + * @param data Pointer to a buffer containing the credential that will be + * stored. + * + * @param data_size Size in bytes of the data located at @p data. + * + * @param arg Opaque argument configured through a corresponding + * *_arg field of + * @ref standalone_security_hsm_configuration_t. + * + * @returns String that will be used as a query string for the provisioned + * security credential. The string will be copied shortly after this + * function returns, so it is safe to deallocate or overwrite it when + * control is returned to user code, or during the next call to this + * function, whichever happens first. Security Object code will never + * attempt to modify or deallocate the returned value. + * + * @attention The @p data and @p data_size are provided only as a + * hint for users who want the query strings to depend on + * the credential contents in any way. This callback shall + * not attempt to store the certificate itself. This will be + * performed by Anjay afterwards. + */ +typedef const char *standalone_security_hsm_query_cb_t(anjay_iid_t iid, + anjay_ssid_t ssid, + const void *data, + size_t data_size, + void *arg); + +/** + * Configuration of the callbacks for generating the query string addresses + * under which different kinds of security credentials will be stored on the + * hardware security engine. + */ +typedef struct { + /** + * Callback function that will be called whenever a public client + * certificate needs to be stored in an external security engine. + * + * If NULL, public client certificates will be stored in main system memory + * unless explicitly requested via either EST or the public_cert + * field in @ref standalone_security_instance_t. + */ + standalone_security_hsm_query_cb_t *public_cert_cb; + + /** + * Opaque argument that will be passed to the function configured in the + * public_cert_cb field. + * + * If public_cert_cb is NULL, this field is ignored. + */ + void *public_cert_cb_arg; + + /** + * Callback function that will be called whenever a client private key needs + * to be stored in an external security engine. + * + * If NULL, client private keys will be stored in main system memory unless + * explicitly requested via either EST or the private_key field in + * @ref standalone_security_instance_t. + */ + standalone_security_hsm_query_cb_t *private_key_cb; + + /** + * Opaque argument that will be passed to the function configured in the + * private_key_cb field. + * + * If private_key_cb is NULL, this field is ignored. + */ + void *private_key_cb_arg; + + /** + * Callback function that will be called whenever a PSK identity for use + * with the main connection needs to be stored in an external security + * engine. + * + * If NULL, PSK identities for use with the main connection will be stored + * in main system memory unless explicitly requested via the + * psk_identity field in @ref standalone_security_instance_t. + */ + standalone_security_hsm_query_cb_t *psk_identity_cb; + + /** + * Opaque argument that will be passed to the function configured in the + * psk_identity_cb field. + * + * If psk_identity_cb is NULL, this field is ignored. + */ + void *psk_identity_cb_arg; + + /** + * Callback function that will be called whenever a PSK key for use with the + * main connection needs to be stored in an external security engine. + * + * If NULL, PSK keys for use with the main connection will be stored in main + * system memory unless explicitly requested via the psk_key field in + * @ref standalone_security_instance_t. + */ + standalone_security_hsm_query_cb_t *psk_key_cb; + + /** + * Opaque argument that will be passed to the function configured in the + * psk_key_cb field. + * + * If psk_key_cb is NULL, this field is ignored. + */ + void *psk_key_cb_arg; +# ifdef ANJAY_WITH_SMS + /** + * Callback function that will be called whenever a PSK identity for use + * with SMS binding needs to be stored in an external security engine. + * + * If NULL, PSK identities for use with SMS binding will be stored in main + * system memory unless explicitly requested via the sms_psk_identity + * field in @ref standalone_security_instance_t. + */ + standalone_security_hsm_query_cb_t *sms_psk_identity_cb; + + /** + * Opaque argument that will be passed to the function configured in the + * sms_psk_identity_cb field. + * + * If sms_psk_identity_cb is NULL, this field is ignored. + */ + void *sms_psk_identity_cb_arg; + + /** + * Callback function that will be called whenever a PSK key for use with SMS + * binding needs to be stored in an external security engine. + * + * If NULL, PSK keys for use with SMS binding will be stored in main system + * memory unless explicitly requested via the sms_psk_key field in + * @ref standalone_security_instance_t. + */ + standalone_security_hsm_query_cb_t *sms_psk_key_cb; + + /** + * Opaque argument that will be passed to the function configured in the + * sms_psk_key_cb field. + * + * If sms_psk_key_cb is NULL, this field is ignored. + */ + void *sms_psk_key_cb_arg; +# endif // ANJAY_WITH_SMS +} standalone_security_hsm_configuration_t; + +/** + * Creates the Security Object in an Anjay instance, with support for moving + * security credentials to a hardware security module, and installs it in an + * Anjay instance using @ref anjay_register_object. + * + * Do NOT attempt to call @ref anjay_register_object with this object manually, + * and do NOT try to use the same instance of the Security object with another + * Anjay instance. + * + * For each of the security credential type for which the query string + * generation callback is provided, any credentials provisioned either using + * @ref standalone_security_object_add_instance or by the Bootstrap Server, will + * be stored in the hardware security module and wiped from the main system + * memory. These credentials will be managed by Anjay and automatically deleted + * when removed from the data model (either by the Bootstrap Server or + * @ref standalone_security_object_purge) or when the object is cleaned up + * without having been properly persisted (see the next paragraph for details). + * + * A call to @ref standalone_security_object_cleanup will also cause the removal + * of all the keys moved into the hardware security module, unless unchanged + * since the last call to @ref standalone_security_object_persist or @ref + * standalone_security_object_restore, or marked permanent using @ref + * standalone_security_mark_hsm_permanent. + * + * @param anjay Anjay instance for which the Security Object is installed. + * + * @param hsm_config Configuration of the mechanism that moves security + * credentials to the hardware security module. When the + * pointer is NULL or all of the callback fields are + * NULL, this functions is equivalent to + * @ref standalone_security_object_install. + * + * @param prng_ctx Custom PRNG context to use. If @c NULL , a default one is + * used, with entropy source specific to selected cryptograpic + * backend. + * + * NOTE: @p prng_ctx is used when moving security credentials into the HSM, + * which may happen in one of three scenarios: + * + * - During the transaction_validate operation performed by Anjay, + * typically while processing a Bootstrap Finish message + * - As part of @ref standalone_security_object_add_instance + * - As part of @ref standalone_security_object_restore + * + * @returns Handle to the created object that can be passed to other functions + * declared in this file, or NULL in case of error. + */ +const anjay_dm_object_def_t **standalone_security_object_install_with_hsm( + anjay_t *anjay, + const standalone_security_hsm_configuration_t *hsm_config, + avs_crypto_prng_ctx_t *prng_ctx); + +/** + * Marks security credential for a given Server Account as "permanent", + * preventing them from being removed from the hardware security module. + * + * The credentials that are moved into hardware security module according to the + * logic described for @ref standalone_security_object_install_with_hsm are, by + * default, automatically removed whenever they are deleted from the data model + * (e.g. by the Bootstrap Server or using @ref + * standalone_security_object_purge). + * + * This function causes such credentials to be marked as "permanent", equivalent + * to credentials provisioned using the avs_crypto_*_from_engine APIs + * passed into the fields of the @ref standalone_security_instance_t structure. + * This will prevent them from being automatically erased from the hardware even + * if they are removed from the data model, or if the object is cleaned up + * without up-to-date persistence status. + * + * @param obj_ptr Installed Security Object to operate on. + * + * @param ssid Short Server ID of the Server Account whose credentials to mark + * as permanent. @ref ANJAY_SSID_BOOTSTRAP may be used to refer to + * the Bootstrap Server (if present), and @ref ANJAY_SSID_ANY may + * be used to mark all the Server Account + * credentials as permanent. + */ +void standalone_security_mark_hsm_permanent( + const anjay_dm_object_def_t *const *obj_ptr, anjay_ssid_t ssid); +#endif // ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT + +#ifdef __cplusplus +} +#endif + +#endif /* ANJAY_STANDALONE_ANJAY_SECURITY_H */ diff --git a/standalone/security/standalone_security_persistence.c b/standalone/security/standalone_security_persistence.c new file mode 100644 index 00000000..dc3092cc --- /dev/null +++ b/standalone/security/standalone_security_persistence.c @@ -0,0 +1,613 @@ +#include +#include + +#include "standalone_mod_security.h" +#include "standalone_security_transaction.h" +#include "standalone_security_utils.h" + +#ifdef AVS_COMMONS_WITH_AVS_PERSISTENCE +# include +#endif // AVS_COMMONS_WITH_AVS_PERSISTENCE + +#define persistence_log(level, ...) \ + avs_log(security_persistence, level, __VA_ARGS__) + +#ifdef AVS_COMMONS_WITH_AVS_PERSISTENCE + +static const char MAGIC_V0[] = { 'S', 'E', 'C', '\0' }; +static const char MAGIC_V1[] = { 'S', 'E', 'C', '\1' }; +static const char MAGIC_V2[] = { 'S', 'E', 'C', '\2' }; +static const char MAGIC_V3[] = { 'S', 'E', 'C', '\3' }; +static const char MAGIC_V4[] = { 'S', 'E', 'C', '\4' }; +static const char MAGIC_V5[] = { 'S', 'E', 'C', '\5' }; + +static avs_error_t handle_sized_v0_fields(avs_persistence_context_t *ctx, + sec_instance_t *element) { + avs_error_t err; + (void) (avs_is_err((err = avs_persistence_u16(ctx, &element->iid))) + || avs_is_err((err = avs_persistence_bool( + ctx, &element->present_resources + [SEC_RES_BOOTSTRAP_SERVER]))) + || avs_is_err((err = avs_persistence_bool( + ctx, &element->present_resources + [SEC_RES_SECURITY_MODE]))) + || avs_is_err((err = avs_persistence_bool( + ctx, &element->present_resources + [SEC_RES_SHORT_SERVER_ID]))) + || avs_is_err(( + err = avs_persistence_bool(ctx, &element->is_bootstrap))) + || avs_is_err((err = avs_persistence_u16(ctx, &element->ssid))) + || avs_is_err((err = avs_persistence_u32( + ctx, (uint32_t *) &element->holdoff_s))) + || avs_is_err((err = avs_persistence_u32( + ctx, (uint32_t *) &element->bs_timeout_s)))); + return err; +} + +static avs_error_t handle_sized_v1_fields(avs_persistence_context_t *ctx, + sec_instance_t *element) { + avs_error_t err; +# ifdef ANJAY_WITH_SMS + (void) (avs_is_err((err = avs_persistence_bool( + ctx, &element->present_resources + [SEC_RES_SMS_SECURITY_MODE]))) + || avs_is_err((err = avs_persistence_bool( + ctx, + &element->present_resources + [SEC_RES_SMS_BINDING_KEY_PARAMS]))) + || avs_is_err( + (err = avs_persistence_bool( + ctx, + &element->present_resources + [SEC_RES_SMS_BINDING_SECRET_KEYS])))); +# else // ANJAY_WITH_SMS + (void) element; + (void) (avs_is_err((err = avs_persistence_bool(ctx, &(bool) { false }))) + || avs_is_err((err = avs_persistence_bool(ctx, &(bool) { false }))) + || avs_is_err( + (err = avs_persistence_bool(ctx, &(bool) { false })))); +# endif // ANJAY_WITH_SMS + return err; +} + +static avs_error_t handle_ciphersuite_entry(avs_persistence_context_t *ctx, + void *element, + void *user_data) { + (void) user_data; + + sec_cipher_instance_t *inst = (sec_cipher_instance_t *) element; + avs_error_t err; + (void) (avs_is_err((err = avs_persistence_u16(ctx, &inst->riid))) + || avs_is_err((err = avs_persistence_u32(ctx, &inst->cipher_id)))); + if (avs_is_ok(err) && inst->cipher_id == 0) { + return avs_errno(AVS_EBADMSG); + } + return err; +} + +static avs_error_t handle_sized_v2_fields(avs_persistence_context_t *ctx, + sec_instance_t *element) { + AVS_LIST(sec_cipher_instance_t) enabled_ciphersuites = NULL; + char *server_name_indication = NULL; +# ifdef ANJAY_WITH_LWM2M11 + enabled_ciphersuites = element->enabled_ciphersuites; + server_name_indication = element->server_name_indication; +# endif // ANJAY_WITH_LWM2M11 + avs_error_t err; + (void) (avs_is_err((err = avs_persistence_list( + ctx, (void **) &enabled_ciphersuites, + sizeof(*enabled_ciphersuites), + handle_ciphersuite_entry, NULL, avs_free))) + || avs_is_err((err = avs_persistence_string( + ctx, &server_name_indication))) +# ifdef ANJAY_WITH_COAP_OSCORE + || avs_is_err((err = avs_persistence_bool( + ctx, + &element->present_resources + [SEC_RES_OSCORE_SECURITY_MODE]))) + || avs_is_err( + (err = avs_persistence_u16(ctx, &element->oscore_iid))) +# else // ANJAY_WITH_COAP_OSCORE + || avs_is_err((err = avs_persistence_bool( + ctx, (bool *) &(bool) { false }))) + || avs_is_err((err = avs_persistence_u16( + ctx, (uint16_t *) &(uint16_t) { 0 }))) +# endif // ANJAY_WITH_COAP_OSCORE + ); +# ifdef ANJAY_WITH_LWM2M11 + element->enabled_ciphersuites = enabled_ciphersuites; + element->server_name_indication = server_name_indication; +# else // ANJAY_WITH_LWM2M11 + (void) element; + AVS_LIST_CLEAR(&enabled_ciphersuites); + avs_free(server_name_indication); +# endif // ANJAY_WITH_LWM2M11 + return err; +} + +static avs_error_t handle_sized_v3_fields(avs_persistence_context_t *ctx, + sec_instance_t *element) { +# ifndef ANJAY_WITH_LWM2M11 + (void) element; +# endif // ANJAY_WITH_LWM2M11 + avs_error_t err; + (void) (avs_is_err((err = avs_persistence_i8(ctx, +# ifdef ANJAY_WITH_LWM2M11 + &element->matching_type +# else // ANJAY_WITH_LWM2M11 + &(int8_t) { -1 } +# endif // ANJAY_WITH_LWM2M11 + ))) + || avs_is_err((err = avs_persistence_i8(ctx, +# ifdef ANJAY_WITH_LWM2M11 + &element->certificate_usage +# else // ANJAY_WITH_LWM2M11 + &(int8_t) { -1 } +# endif // ANJAY_WITH_LWM2M11 + )))); + return err; +} + +# ifdef ANJAY_WITH_LWM2M11 +static void reset_v3_fields(sec_instance_t *element) { + element->matching_type = -1; + element->certificate_usage = -1; +} +# endif // ANJAY_WITH_LWM2M11 + +# if defined(ANJAY_WITH_SECURITY_STRUCTURED) \ + || defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) +static avs_error_t handle_sec_key_or_data_type(avs_persistence_context_t *ctx, + sec_key_or_data_type_t *type) { + avs_persistence_direction_t direction = avs_persistence_direction(ctx); + int8_t type_ch; + if (direction == AVS_PERSISTENCE_STORE) { + switch (*type) { + case SEC_KEY_AS_DATA: + type_ch = 'D'; + break; + case SEC_KEY_AS_KEY_EXTERNAL: + type_ch = 'K'; + break; + case SEC_KEY_AS_KEY_OWNED: + type_ch = 'O'; + break; + default: + AVS_UNREACHABLE("invalid value of sec_key_or_data_type_t"); + return avs_errno(AVS_EINVAL); + } + } + + avs_error_t err = avs_persistence_i8(ctx, &type_ch); + if (avs_is_err(err)) { + return err; + } + + if (direction == AVS_PERSISTENCE_RESTORE) { + switch (type_ch) { + case 'D': + *type = SEC_KEY_AS_DATA; + break; + case 'K': + *type = SEC_KEY_AS_KEY_EXTERNAL; + break; + case 'O': + *type = SEC_KEY_AS_KEY_OWNED; + break; + default: + return avs_errno(AVS_EIO); + } + } + return AVS_OK; +} + +static avs_error_t handle_sec_key_tag(avs_persistence_context_t *ctx, + avs_crypto_security_info_tag_t *tag) { + avs_persistence_direction_t direction = avs_persistence_direction(ctx); + int8_t tag_ch; + if (direction == AVS_PERSISTENCE_STORE) { + switch (*tag) { + case AVS_CRYPTO_SECURITY_INFO_CERTIFICATE_CHAIN: + tag_ch = 'C'; + break; + case AVS_CRYPTO_SECURITY_INFO_PRIVATE_KEY: + tag_ch = 'K'; + break; + case AVS_CRYPTO_SECURITY_INFO_PSK_IDENTITY: + tag_ch = 'I'; + break; + case AVS_CRYPTO_SECURITY_INFO_PSK_KEY: + tag_ch = 'P'; + break; + default: + AVS_UNREACHABLE("invalid value of avs_crypto_security_info_tag_t"); + return avs_errno(AVS_EINVAL); + } + } + + avs_error_t err = avs_persistence_i8(ctx, &tag_ch); + if (avs_is_err(err)) { + return err; + } + + if (direction == AVS_PERSISTENCE_RESTORE) { + switch (tag_ch) { + case 'C': + *tag = AVS_CRYPTO_SECURITY_INFO_CERTIFICATE_CHAIN; + break; + case 'K': + *tag = AVS_CRYPTO_SECURITY_INFO_PRIVATE_KEY; + break; + case 'I': + *tag = AVS_CRYPTO_SECURITY_INFO_PSK_IDENTITY; + break; + case 'P': + *tag = AVS_CRYPTO_SECURITY_INFO_PSK_KEY; + break; + default: + return avs_errno(AVS_EIO); + } + } + return AVS_OK; +} + +static avs_error_t +handle_sec_key_certificate_chain(avs_persistence_context_t *ctx, + sec_key_or_data_t *value) { + assert(value->type == SEC_KEY_AS_KEY_EXTERNAL + || value->type == SEC_KEY_AS_KEY_OWNED); + if (avs_persistence_direction(ctx) == AVS_PERSISTENCE_STORE) { + return avs_crypto_certificate_chain_info_persist( + ctx, (avs_crypto_certificate_chain_info_t) { + .desc = value->value.key.info + }); + } else { + avs_crypto_certificate_chain_info_t *array = NULL; + size_t element_count; + avs_error_t err = avs_crypto_certificate_chain_info_array_persistence( + ctx, &array, &element_count); + if (avs_is_ok(err)) { + assert(!value->value.key.heap_buf); + assert(!value->prev_ref); + assert(!value->next_ref); + value->value.key.info = + avs_crypto_certificate_chain_info_from_array(array, + element_count) + .desc; + value->value.key.heap_buf = array; + } + return err; + } +} + +static avs_error_t handle_sec_key_private_key(avs_persistence_context_t *ctx, + sec_key_or_data_t *value) { + assert(value->type == SEC_KEY_AS_KEY_EXTERNAL + || value->type == SEC_KEY_AS_KEY_OWNED); + avs_crypto_private_key_info_t *key_info = NULL; + if (avs_persistence_direction(ctx) == AVS_PERSISTENCE_STORE) { + key_info = AVS_CONTAINER_OF(&value->value.key.info, + avs_crypto_private_key_info_t, desc); + } + avs_error_t err = avs_crypto_private_key_info_persistence(ctx, &key_info); + if (avs_is_ok(err) + && avs_persistence_direction(ctx) == AVS_PERSISTENCE_RESTORE) { + assert(!value->value.key.heap_buf); + assert(!value->prev_ref); + assert(!value->next_ref); + value->value.key.info = key_info->desc; + value->value.key.heap_buf = key_info; + } + return err; +} + +static avs_error_t handle_sec_key_psk_identity(avs_persistence_context_t *ctx, + sec_key_or_data_t *value) { + assert(value->type == SEC_KEY_AS_KEY_EXTERNAL + || value->type == SEC_KEY_AS_KEY_OWNED); + avs_crypto_psk_identity_info_t *key_info = NULL; + if (avs_persistence_direction(ctx) == AVS_PERSISTENCE_STORE) { + key_info = AVS_CONTAINER_OF(&value->value.key.info, + avs_crypto_psk_identity_info_t, desc); + } + avs_error_t err = avs_crypto_psk_identity_info_persistence(ctx, &key_info); + if (avs_is_ok(err) + && avs_persistence_direction(ctx) == AVS_PERSISTENCE_RESTORE) { + assert(!value->value.key.heap_buf); + assert(!value->prev_ref); + assert(!value->next_ref); + value->value.key.info = key_info->desc; + value->value.key.heap_buf = key_info; + } + return err; +} + +static avs_error_t handle_sec_key_psk_key(avs_persistence_context_t *ctx, + sec_key_or_data_t *value) { + assert(value->type == SEC_KEY_AS_KEY_EXTERNAL + || value->type == SEC_KEY_AS_KEY_OWNED); + avs_crypto_psk_key_info_t *key_info = NULL; + if (avs_persistence_direction(ctx) == AVS_PERSISTENCE_STORE) { + key_info = AVS_CONTAINER_OF(&value->value.key.info, + avs_crypto_psk_key_info_t, desc); + } + avs_error_t err = avs_crypto_psk_key_info_persistence(ctx, &key_info); + if (avs_is_ok(err) + && avs_persistence_direction(ctx) == AVS_PERSISTENCE_RESTORE) { + assert(!value->value.key.heap_buf); + assert(!value->prev_ref); + assert(!value->next_ref); + value->value.key.info = key_info->desc; + value->value.key.heap_buf = key_info; + } + return err; +} +# endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) || \ + defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) */ + +static avs_error_t handle_raw_buffer(avs_persistence_context_t *ctx, + standalone_raw_buffer_t *buffer) { + avs_error_t err = + avs_persistence_sized_buffer(ctx, &buffer->data, &buffer->size); + if (!buffer->capacity) { + buffer->capacity = buffer->size; + } + return err; +} + +static avs_error_t +handle_sec_key_or_data(avs_persistence_context_t *ctx, + sec_key_or_data_t *value, + intptr_t stream_version, + intptr_t min_version_for_key, + avs_crypto_security_info_tag_t default_tag) { +# if defined(ANJAY_WITH_SECURITY_STRUCTURED) \ + || defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) + if (stream_version >= min_version_for_key) { + avs_error_t err = handle_sec_key_or_data_type(ctx, &value->type); + if (avs_is_err(err)) { + return err; + } + + if (value->type == SEC_KEY_AS_KEY_EXTERNAL + || value->type == SEC_KEY_AS_KEY_OWNED) { + avs_crypto_security_info_tag_t tag = default_tag; + if (stream_version >= 5) { + if (avs_persistence_direction(ctx) == AVS_PERSISTENCE_STORE) { + tag = value->value.key.info.type; + } + if (avs_is_err((err = handle_sec_key_tag(ctx, &tag)))) { + return err; + } + } + + switch (tag) { + case AVS_CRYPTO_SECURITY_INFO_CERTIFICATE_CHAIN: + return handle_sec_key_certificate_chain(ctx, value); + case AVS_CRYPTO_SECURITY_INFO_PRIVATE_KEY: + return handle_sec_key_private_key(ctx, value); + case AVS_CRYPTO_SECURITY_INFO_PSK_IDENTITY: + return handle_sec_key_psk_identity(ctx, value); + case AVS_CRYPTO_SECURITY_INFO_PSK_KEY: + return handle_sec_key_psk_key(ctx, value); + default: + AVS_UNREACHABLE( + "invalid value of avs_crypto_security_info_tag_t"); + return avs_errno(AVS_EINVAL); + } + } + } +# endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) || \ + defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) */ + (void) stream_version; + (void) min_version_for_key; + (void) default_tag; + assert(value->type == SEC_KEY_AS_DATA); + avs_error_t err = handle_raw_buffer(ctx, &value->value.data); + assert(avs_is_err(err) + || avs_persistence_direction(ctx) != AVS_PERSISTENCE_RESTORE + || (!value->prev_ref && !value->next_ref)); + return err; +} + +static avs_error_t handle_instance(avs_persistence_context_t *ctx, + void *element_, + void *stream_version_) { + sec_instance_t *element = (sec_instance_t *) element_; + const intptr_t stream_version = (intptr_t) stream_version_; + + avs_error_t err = AVS_OK; + uint16_t security_mode = (uint16_t) element->security_mode; + if (avs_is_err((err = handle_sized_v0_fields(ctx, element))) + || avs_is_err((err = avs_persistence_u16(ctx, &security_mode))) + || avs_is_err(( + err = avs_persistence_string(ctx, &element->server_uri))) + || avs_is_err((err = handle_sec_key_or_data( + ctx, &element->public_cert_or_psk_identity, + stream_version, + /* min_version_for_key = */ 4, + AVS_CRYPTO_SECURITY_INFO_CERTIFICATE_CHAIN))) + || avs_is_err((err = handle_sec_key_or_data( + ctx, &element->private_cert_or_psk_key, + stream_version, + /* min_version_for_key = */ 4, + AVS_CRYPTO_SECURITY_INFO_PRIVATE_KEY))) + || avs_is_err((err = handle_raw_buffer( + ctx, &element->server_public_key)))) { + return err; + } + element->security_mode = (anjay_security_mode_t) security_mode; + if (stream_version >= 1) { +# ifdef ANJAY_WITH_SMS + uint16_t sms_security_mode = (uint16_t) element->sms_security_mode; + sec_key_or_data_t *sms_key_params_ptr = &element->sms_key_params; + sec_key_or_data_t *sms_secret_key_ptr = &element->sms_secret_key; + char *sms_number = element->sms_number; +# else // ANJAY_WITH_SMS + uint16_t sms_security_mode = 3; // NoSec + sec_key_or_data_t *sms_key_params_ptr = + &(sec_key_or_data_t) { SEC_KEY_AS_DATA }; + sec_key_or_data_t *sms_secret_key_ptr = + &(sec_key_or_data_t) { SEC_KEY_AS_DATA }; + char *sms_number = NULL; +# endif // ANJAY_WITH_SMS + if (avs_is_err((err = handle_sized_v1_fields(ctx, element))) + || avs_is_err( + (err = avs_persistence_u16(ctx, &sms_security_mode))) + || avs_is_err((err = handle_sec_key_or_data( + ctx, sms_key_params_ptr, stream_version, + /* min_version_for_key = */ 5, + AVS_CRYPTO_SECURITY_INFO_PSK_IDENTITY))) + || avs_is_err((err = handle_sec_key_or_data( + ctx, sms_secret_key_ptr, stream_version, + /* min_version_for_key = */ 5, + AVS_CRYPTO_SECURITY_INFO_PSK_KEY))) + || avs_is_err( + (err = avs_persistence_string(ctx, &sms_number)))) { + return err; + } +# ifdef ANJAY_WITH_SMS + element->sms_security_mode = + (anjay_sms_security_mode_t) sms_security_mode; + element->sms_number = sms_number; +# else // ANJAY_WITH_SMS + _standalone_sec_key_or_data_cleanup(sms_key_params_ptr, false); + _standalone_sec_key_or_data_cleanup(sms_secret_key_ptr, false); + avs_free(sms_number); +# endif // ANJAY_WITH_SMS + } + if (stream_version >= 2) { + err = handle_sized_v2_fields(ctx, element); + } + if (avs_is_ok(err)) { + if (stream_version >= 3) { + err = handle_sized_v3_fields(ctx, element); + } +# ifdef ANJAY_WITH_LWM2M11 + else if (avs_persistence_direction(ctx) == AVS_PERSISTENCE_RESTORE) { + reset_v3_fields(element); + } +# endif // ANJAY_WITH_LWM2M11 + } + + if (avs_persistence_direction(ctx) == AVS_PERSISTENCE_RESTORE) { + _standalone_sec_instance_update_resource_presence(element); + } + + return err; +} + +avs_error_t +standalone_security_object_persist(const anjay_dm_object_def_t *const *obj_ptr, + avs_stream_t *out_stream) { + avs_error_t err = avs_errno(AVS_EINVAL); + sec_repr_t *repr = _standalone_sec_get(obj_ptr); + if (!repr) { + err = avs_errno(AVS_EBADF); + } else if (avs_is_ok((err = avs_stream_write(out_stream, MAGIC_V5, + sizeof(MAGIC_V5))))) { + avs_persistence_context_t ctx = + avs_persistence_store_context_create(out_stream); + err = avs_persistence_list( + &ctx, + (AVS_LIST(void) *) (repr->in_transaction + ? &repr->saved_instances + : &repr->instances), + sizeof(sec_instance_t), handle_instance, (void *) (intptr_t) 5, + NULL); + if (avs_is_ok(err)) { + _standalone_sec_clear_modified(repr); + persistence_log(INFO, _("Security Object state persisted")); + } + } + return err; +} + +avs_error_t +standalone_security_object_restore(const anjay_dm_object_def_t *const *obj_ptr, + avs_stream_t *in_stream) { + avs_error_t err = avs_errno(AVS_EINVAL); + sec_repr_t *repr = _standalone_sec_get(obj_ptr); + if (!repr || repr->in_transaction) { + err = avs_errno(AVS_EBADF); + } else { + sec_repr_t backup = *repr; + + AVS_STATIC_ASSERT(sizeof(MAGIC_V0) == sizeof(MAGIC_V1), + magic_size_v0_v1); + AVS_STATIC_ASSERT(sizeof(MAGIC_V1) == sizeof(MAGIC_V2), + magic_size_v1_v2); + AVS_STATIC_ASSERT(sizeof(MAGIC_V2) == sizeof(MAGIC_V3), + magic_size_v2_v3); + AVS_STATIC_ASSERT(sizeof(MAGIC_V3) == sizeof(MAGIC_V4), + magic_size_v3_v4); + AVS_STATIC_ASSERT(sizeof(MAGIC_V4) == sizeof(MAGIC_V5), + magic_size_v4_v5); + char magic_header[sizeof(MAGIC_V0)]; + int version = -1; + if (avs_is_err( + (err = avs_stream_read_reliably(in_stream, magic_header, + sizeof(magic_header))))) { + persistence_log(WARNING, + _("Could not read Security Object header")); + } else if (!memcmp(magic_header, MAGIC_V0, sizeof(MAGIC_V0))) { + version = 0; + } else if (!memcmp(magic_header, MAGIC_V1, sizeof(MAGIC_V1))) { + version = 1; + } else if (!memcmp(magic_header, MAGIC_V2, sizeof(MAGIC_V2))) { + version = 2; + } else if (!memcmp(magic_header, MAGIC_V3, sizeof(MAGIC_V3))) { + version = 3; + } else if (!memcmp(magic_header, MAGIC_V4, sizeof(MAGIC_V4))) { + version = 4; + } else if (!memcmp(magic_header, MAGIC_V5, sizeof(MAGIC_V5))) { + version = 5; + } else { + persistence_log(WARNING, _("Header magic constant mismatch")); + err = avs_errno(AVS_EBADMSG); + } + if (avs_is_ok(err)) { + avs_persistence_context_t restore_ctx = + avs_persistence_restore_context_create(in_stream); + repr->instances = NULL; + err = avs_persistence_list(&restore_ctx, + (AVS_LIST(void) *) &repr->instances, + sizeof(sec_instance_t), handle_instance, + (void *) (intptr_t) version, NULL); + if (avs_is_ok(err) + && _standalone_sec_object_validate_and_process_keys( + repr->anjay, repr)) { + err = avs_errno(AVS_EPROTO); + } + if (avs_is_err(err)) { + _standalone_sec_destroy_instances(&repr->instances, true); + repr->instances = backup.instances; + } else { + _standalone_sec_destroy_instances(&backup.instances, true); + _standalone_sec_clear_modified(repr); + persistence_log(INFO, _("Security Object state restored")); + } + } + } + return err; +} + +#else // AVS_COMMONS_WITH_AVS_PERSISTENCE + +avs_error_t standalone_security_object_persist(anjay_t *anjay, + avs_stream_t *out_stream) { + (void) anjay; + (void) out_stream; + persistence_log(ERROR, _("Persistence not compiled in")); + return avs_errno(AVS_ENOTSUP); +} + +avs_error_t standalone_security_object_restore(anjay_t *anjay, + avs_stream_t *in_stream) { + (void) anjay; + (void) in_stream; + persistence_log(ERROR, _("Persistence not compiled in")); + return avs_errno(AVS_ENOTSUP); +} + +#endif // AVS_COMMONS_WITH_AVS_PERSISTENCE diff --git a/standalone/security/standalone_security_transaction.c b/standalone/security/standalone_security_transaction.c new file mode 100644 index 00000000..d73f7520 --- /dev/null +++ b/standalone/security/standalone_security_transaction.c @@ -0,0 +1,575 @@ +#include +#include + +#include "standalone_security_transaction.h" +#include "standalone_security_utils.h" + +typedef struct { + anjay_ssid_t ssid; + anjay_socket_transport_t transport; +} ssid_transport_pair_t; + +static int +ssid_transport_pair_cmp(const void *a_, const void *b_, size_t element_size) { + assert(element_size == sizeof(ssid_transport_pair_t)); + (void) element_size; + const ssid_transport_pair_t *a = (const ssid_transport_pair_t *) a_; + const ssid_transport_pair_t *b = (const ssid_transport_pair_t *) b_; + if (a->ssid != b->ssid) { + return a->ssid - b->ssid; + } + return (int) a->transport - (int) b->transport; +} + +typedef enum { + STANDALONE_TRANSPORT_SECURITY_UNDEFINED, + STANDALONE_TRANSPORT_NOSEC, + STANDALONE_TRANSPORT_ENCRYPTED +} standalone_transport_security_t; + +typedef struct { + const char *uri_scheme; + anjay_socket_transport_t transport; + standalone_transport_security_t security; +} standalone_transport_info_t; + +static const standalone_transport_info_t TRANSPORTS[] = { + { + .transport = ANJAY_SOCKET_TRANSPORT_UDP, + .uri_scheme = "coap", + .security = STANDALONE_TRANSPORT_NOSEC + }, + { + .transport = ANJAY_SOCKET_TRANSPORT_UDP, + .uri_scheme = "coaps", + .security = STANDALONE_TRANSPORT_ENCRYPTED + }, + { + .transport = ANJAY_SOCKET_TRANSPORT_TCP, + .uri_scheme = "coap+tcp", + .security = STANDALONE_TRANSPORT_NOSEC + }, + { + .transport = ANJAY_SOCKET_TRANSPORT_TCP, + .uri_scheme = "coaps+tcp", + .security = STANDALONE_TRANSPORT_ENCRYPTED + }, + { + .transport = ANJAY_SOCKET_TRANSPORT_SMS, + .uri_scheme = "tel", + .security = STANDALONE_TRANSPORT_SECURITY_UNDEFINED + }, +#ifdef ANJAY_WITH_LWM2M11 + { + .transport = ANJAY_SOCKET_TRANSPORT_NIDD, + .uri_scheme = "coap+nidd", + .security = STANDALONE_TRANSPORT_NOSEC + }, + { + .transport = ANJAY_SOCKET_TRANSPORT_NIDD, + .uri_scheme = "coaps+nidd", + .security = STANDALONE_TRANSPORT_ENCRYPTED + } +#endif // ANJAY_WITH_LWM2M11 +}; + +static const standalone_transport_info_t * +_standalone_transport_info_by_uri_scheme(const char *uri_or_scheme) { + if (!uri_or_scheme) { + security_log(ERROR, _("URL scheme not specified")); + return NULL; + } + + for (size_t i = 0; i < AVS_ARRAY_SIZE(TRANSPORTS); ++i) { + size_t scheme_size = strlen(TRANSPORTS[i].uri_scheme); + if (avs_strncasecmp(uri_or_scheme, TRANSPORTS[i].uri_scheme, + scheme_size) + == 0 + && (uri_or_scheme[scheme_size] == '\0' + || uri_or_scheme[scheme_size] == ':')) { + return &TRANSPORTS[i]; + } + } + + security_log(WARNING, _("unsupported URI scheme: ") "%s", uri_or_scheme); + return NULL; +} + +static bool uri_protocol_matching(anjay_security_mode_t security_mode, + const char *uri) { + const standalone_transport_info_t *transport_info = + _standalone_transport_info_by_uri_scheme(uri); + if (!transport_info) { + return false; + } + if (transport_info->security == STANDALONE_TRANSPORT_SECURITY_UNDEFINED) { + // URI scheme does not specify security, + // so it is valid for all security modes + return true; + } + + const bool is_secure_uri = + (transport_info->security == STANDALONE_TRANSPORT_ENCRYPTED); + const bool needs_secure_uri = (security_mode != ANJAY_SECURITY_NOSEC); + return is_secure_uri == needs_secure_uri; +} + +static bool +sec_key_or_data_valid(const sec_key_or_data_t *value, + const avs_crypto_security_info_tag_t *expected_tag) { + (void) expected_tag; + switch (value->type) { + case SEC_KEY_AS_DATA: + return value->value.data.data; +#if defined(ANJAY_WITH_SECURITY_STRUCTURED) \ + || defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) + case SEC_KEY_AS_KEY_EXTERNAL: + case SEC_KEY_AS_KEY_OWNED: + return expected_tag + && value->value.key.info.source != AVS_CRYPTO_DATA_SOURCE_EMPTY + && value->value.key.info.type == *expected_tag; +#endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) || \ + defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) */ + default: + AVS_UNREACHABLE("invalid value of sec_key_or_data_type_t"); + return false; + } +} + +#define LOG_VALIDATION_FAILED(SecInstance, ...) \ + security_log(WARNING, "/%u/%u: " AVS_VARARG0(__VA_ARGS__), \ + ANJAY_DM_OID_SECURITY, \ + (unsigned) (SecInstance)->iid AVS_VARARG_REST(__VA_ARGS__)) + +static int validate_instance(sec_instance_t *it) { + if (!it->server_uri) { + LOG_VALIDATION_FAILED(it, + "missing mandatory 'Server URI' resource value"); + return -1; + } + if (!it->present_resources[SEC_RES_BOOTSTRAP_SERVER]) { + LOG_VALIDATION_FAILED( + it, "missing mandatory 'Bootstrap Server' resource value"); + return -1; + } + if (!it->present_resources[SEC_RES_SECURITY_MODE]) { + LOG_VALIDATION_FAILED( + it, "missing mandatory 'Security Mode' resource value"); + return -1; + } + if (!it->is_bootstrap && !it->present_resources[SEC_RES_SHORT_SERVER_ID]) { + LOG_VALIDATION_FAILED( + it, "missing mandatory 'Short Server ID' resource value"); + return -1; + } + if (_standalone_sec_validate_security_mode((int32_t) it->security_mode)) { + LOG_VALIDATION_FAILED(it, "Security mode %d not supported", + (int) it->security_mode); + return -1; + } + if (!uri_protocol_matching(it->security_mode, it->server_uri)) { + LOG_VALIDATION_FAILED( + it, + "Incorrect protocol in Server Uri '%s' due to security " + "configuration (coap:// instead of coaps:// or vice versa?)", + it->server_uri); + return -1; + } + if (it->security_mode != ANJAY_SECURITY_NOSEC + && it->security_mode != ANJAY_SECURITY_EST) { + if (!sec_key_or_data_valid( + &it->public_cert_or_psk_identity, + &(const avs_crypto_security_info_tag_t) { + it->security_mode == ANJAY_SECURITY_PSK + ? AVS_CRYPTO_SECURITY_INFO_PSK_IDENTITY + : AVS_CRYPTO_SECURITY_INFO_CERTIFICATE_CHAIN }) + || !sec_key_or_data_valid( + &it->private_cert_or_psk_key, + &(const avs_crypto_security_info_tag_t) { + it->security_mode == ANJAY_SECURITY_PSK + ? AVS_CRYPTO_SECURITY_INFO_PSK_KEY + : AVS_CRYPTO_SECURITY_INFO_PRIVATE_KEY })) { + LOG_VALIDATION_FAILED(it, + "security credentials not fully configured"); + return -1; + } + } +#ifdef ANJAY_WITH_SMS + if (it->present_resources[SEC_RES_SMS_SECURITY_MODE]) { + if (_standalone_sec_validate_sms_security_mode( + (int32_t) it->sms_security_mode)) { + LOG_VALIDATION_FAILED(it, "SMS Security mode %d not supported", + (int) it->sms_security_mode); + return -1; + } + if ((it->sms_security_mode == ANJAY_SMS_SECURITY_DTLS_PSK + || it->sms_security_mode == ANJAY_SMS_SECURITY_SECURE_PACKET) + && (!it->present_resources[SEC_RES_SMS_BINDING_KEY_PARAMS] + || !it->present_resources[SEC_RES_SMS_BINDING_SECRET_KEYS] + || !sec_key_or_data_valid( + &it->sms_key_params, + it->sms_security_mode + == ANJAY_SMS_SECURITY_DTLS_PSK + ? &(const avs_crypto_security_info_tag_t) { AVS_CRYPTO_SECURITY_INFO_PSK_IDENTITY } + : NULL) + || !sec_key_or_data_valid( + &it->sms_secret_key, + it->sms_security_mode + == ANJAY_SMS_SECURITY_DTLS_PSK + ? &(const avs_crypto_security_info_tag_t) { AVS_CRYPTO_SECURITY_INFO_PSK_KEY } + : NULL))) { + LOG_VALIDATION_FAILED( + it, "SMS security credentials not fully configured"); + return -1; + } + } +#endif // ANJAY_WITH_SMS +#ifdef ANJAY_WITH_LWM2M11 + if (it->matching_type > 3) { + LOG_VALIDATION_FAILED(it, "Matching Type set to an invalid value"); + return -1; + } + if (it->matching_type == 2) { + LOG_VALIDATION_FAILED(it, "SHA-384 Matching Type is not supported"); + return -1; + } + if (it->certificate_usage > 3) { + LOG_VALIDATION_FAILED(it, "Certificate Usage set to an invalid value"); + return -1; + } +#endif // ANJAY_WITH_LWM2M11 + return 0; +} + +static int sec_object_validate(anjay_t *anjay, sec_repr_t *repr) { + AVS_LIST(ssid_transport_pair_t) seen_ssid_transport_pairs = NULL; + AVS_LIST(sec_instance_t) it; + int result = 0; + bool bootstrap_server_present = false; + (void) anjay; + + AVS_LIST_FOREACH(it, repr->instances) { + /* Assume something will go wrong */ + result = ANJAY_ERR_BAD_REQUEST; + if (validate_instance(it)) { + goto finish; + } + + if (it->is_bootstrap) { + if (bootstrap_server_present) { + goto finish; + } + bootstrap_server_present = true; + } else { + const standalone_transport_info_t *transport_info = + _standalone_transport_info_by_uri_scheme(it->server_uri); + if (!transport_info + || !AVS_LIST_INSERT_NEW(ssid_transport_pair_t, + &seen_ssid_transport_pairs)) { + result = ANJAY_ERR_INTERNAL; + goto finish; + } + seen_ssid_transport_pairs->ssid = it->ssid; + seen_ssid_transport_pairs->transport = transport_info->transport; + } + + /* We are still there - nothing went wrong, continue */ + result = 0; + } + + if (!result && seen_ssid_transport_pairs) { + AVS_LIST_SORT(&seen_ssid_transport_pairs, ssid_transport_pair_cmp); + AVS_LIST(ssid_transport_pair_t) prev = seen_ssid_transport_pairs; + AVS_LIST(ssid_transport_pair_t) next = + AVS_LIST_NEXT(seen_ssid_transport_pairs); + while (next) { + if (prev->ssid == next->ssid + && prev->transport == next->transport) { + /* Duplicate found */ + result = ANJAY_ERR_BAD_REQUEST; + break; + } + prev = next; + next = AVS_LIST_NEXT(next); + } + } +finish: + AVS_LIST_CLEAR(&seen_ssid_transport_pairs); + return result; +} + +#ifdef ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT +static avs_error_t sec_key_store(sec_key_or_data_t *sec_key, + avs_crypto_security_info_tag_t tag, + avs_crypto_prng_ctx_t *prng_ctx, + const char *query) { + avs_crypto_security_info_union_t src_desc = { + .type = tag, + .source = AVS_CRYPTO_DATA_SOURCE_BUFFER, + .info = { + .buffer = { + .buffer = sec_key->value.data.data, + .buffer_size = sec_key->value.data.size + } + } + }; + switch (tag) { +# ifdef AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE + case AVS_CRYPTO_SECURITY_INFO_CERTIFICATE_CHAIN: + return avs_crypto_pki_engine_certificate_store( + query, + AVS_CONTAINER_OF(&src_desc, avs_crypto_certificate_chain_info_t, + desc)); + case AVS_CRYPTO_SECURITY_INFO_PRIVATE_KEY: + return avs_crypto_pki_engine_key_store( + query, + AVS_CONTAINER_OF(&src_desc, avs_crypto_private_key_info_t, + desc), + prng_ctx); +# endif // AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE +# ifdef AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE + case AVS_CRYPTO_SECURITY_INFO_PSK_IDENTITY: + return avs_crypto_psk_engine_identity_store( + query, AVS_CONTAINER_OF(&src_desc, + avs_crypto_psk_identity_info_t, desc)); + case AVS_CRYPTO_SECURITY_INFO_PSK_KEY: + return avs_crypto_psk_engine_key_store( + query, + AVS_CONTAINER_OF(&src_desc, avs_crypto_psk_key_info_t, desc)); +# endif // AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE + default: + AVS_UNREACHABLE("Unexpected tag value"); + return avs_errno(AVS_EINVAL); + } +} + +static int +maybe_move_sec_key_to_hsm(sec_instance_t *instance, + sec_key_or_data_t *sec_key, + avs_crypto_security_info_tag_t tag, + const char *tag_str, + avs_crypto_prng_ctx_t *prng_ctx, + standalone_security_hsm_query_cb_t *query_cb, + void *query_cb_arg) { + if (sec_key->type != SEC_KEY_AS_DATA || !query_cb) { + return 0; + } + const char *query = + query_cb(instance->iid, + instance->present_resources[SEC_RES_SHORT_SERVER_ID] + ? instance->ssid + : ANJAY_SSID_BOOTSTRAP, + sec_key->value.data.data, sec_key->value.data.size, + query_cb_arg); + if (!query) { + security_log(ERROR, + _("Generating HSM query string for ") "%s" _(" failed"), + tag_str); + return -1; + } + if (avs_is_err(sec_key_store(sec_key, tag, prng_ctx, query))) { + security_log(ERROR, _("Could not store ") "%s" _(" in HSM"), tag_str); + return -1; + } + sec_key_or_data_t new_sec_key; + memset(&new_sec_key, 0, sizeof(new_sec_key)); + avs_crypto_security_info_union_t dst_desc = { + .type = tag, + .source = AVS_CRYPTO_DATA_SOURCE_ENGINE, + .info = { + .engine = { + .query = query + } + } + }; + int result; + switch (tag) { +# ifdef AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE + case AVS_CRYPTO_SECURITY_INFO_CERTIFICATE_CHAIN: + if ((result = _standalone_sec_init_certificate_chain_resource( + &new_sec_key, SEC_KEY_AS_KEY_OWNED, + AVS_CONTAINER_OF(&dst_desc, + avs_crypto_certificate_chain_info_t, + desc)))) { + avs_crypto_pki_engine_certificate_rm(query); + } + break; + case AVS_CRYPTO_SECURITY_INFO_PRIVATE_KEY: + if ((result = _standalone_sec_init_private_key_resource( + &new_sec_key, SEC_KEY_AS_KEY_OWNED, + AVS_CONTAINER_OF(&dst_desc, avs_crypto_private_key_info_t, + desc)))) { + avs_crypto_pki_engine_key_rm(query); + } + break; +# endif // AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE +# ifdef AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE + case AVS_CRYPTO_SECURITY_INFO_PSK_IDENTITY: + if ((result = _standalone_sec_init_psk_identity_resource( + &new_sec_key, SEC_KEY_AS_KEY_OWNED, + AVS_CONTAINER_OF(&dst_desc, avs_crypto_psk_identity_info_t, + desc)))) { + avs_crypto_psk_engine_identity_rm(query); + } + break; + case AVS_CRYPTO_SECURITY_INFO_PSK_KEY: + if ((result = _standalone_sec_init_psk_key_resource( + &new_sec_key, SEC_KEY_AS_KEY_OWNED, + AVS_CONTAINER_OF(&dst_desc, avs_crypto_psk_key_info_t, + desc)))) { + avs_crypto_psk_engine_key_rm(query); + } + break; +# endif // AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE + default: + AVS_UNREACHABLE("Unexpected tag value"); + result = -1; + } + if (result) { + security_log(ERROR, + _("Could not allocate new sec_key_or_data_t object")); + return result; + } + _standalone_sec_key_or_data_cleanup(sec_key, true); + assert(!new_sec_key.prev_ref); + assert(!new_sec_key.next_ref); + *sec_key = new_sec_key; + return 0; +} + +static int sec_object_process_keys(sec_repr_t *repr, + avs_crypto_prng_ctx_t *prng_ctx) { + AVS_LIST(sec_instance_t) instance; + AVS_LIST_FOREACH(instance, repr->instances) { + switch (instance->security_mode) { +# ifdef AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE + case ANJAY_SECURITY_PSK: + if (maybe_move_sec_key_to_hsm( + instance, &instance->public_cert_or_psk_identity, + AVS_CRYPTO_SECURITY_INFO_PSK_IDENTITY, "PSK identity", + prng_ctx, repr->hsm_config.psk_identity_cb, + repr->hsm_config.psk_identity_cb_arg)) { + return -1; + } + if (maybe_move_sec_key_to_hsm( + instance, &instance->private_cert_or_psk_key, + AVS_CRYPTO_SECURITY_INFO_PSK_KEY, "PSK key", prng_ctx, + repr->hsm_config.psk_key_cb, + repr->hsm_config.psk_key_cb_arg)) { + return -1; + } + break; +# endif // AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE +# ifdef AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE + case ANJAY_SECURITY_CERTIFICATE: + case ANJAY_SECURITY_EST: + if (maybe_move_sec_key_to_hsm( + instance, &instance->public_cert_or_psk_identity, + AVS_CRYPTO_SECURITY_INFO_CERTIFICATE_CHAIN, + "public certificate", prng_ctx, + repr->hsm_config.public_cert_cb, + repr->hsm_config.public_cert_cb_arg)) { + return -1; + } + if (maybe_move_sec_key_to_hsm( + instance, &instance->private_cert_or_psk_key, + AVS_CRYPTO_SECURITY_INFO_PRIVATE_KEY, "private key", + prng_ctx, repr->hsm_config.private_key_cb, + repr->hsm_config.private_key_cb_arg)) { + return -1; + } + break; +# endif // AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE + default: + // Do nothing + break; + } +# if defined(ANJAY_WITH_SMS) \ + && defined(AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE) + if (instance->present_resources[SEC_RES_SMS_SECURITY_MODE] + && instance->sms_security_mode == ANJAY_SMS_SECURITY_DTLS_PSK) { + if (instance->present_resources[SEC_RES_SMS_BINDING_KEY_PARAMS] + && maybe_move_sec_key_to_hsm( + instance, &instance->sms_key_params, + AVS_CRYPTO_SECURITY_INFO_PSK_IDENTITY, + "SMS PSK identity", prng_ctx, + repr->hsm_config.sms_psk_identity_cb, + repr->hsm_config.sms_psk_identity_cb_arg)) { + return -1; + } + if (instance->present_resources[SEC_RES_SMS_BINDING_SECRET_KEYS] + && maybe_move_sec_key_to_hsm( + instance, &instance->sms_secret_key, + AVS_CRYPTO_SECURITY_INFO_PSK_KEY, "SMS PSK key", + prng_ctx, repr->hsm_config.sms_psk_key_cb, + repr->hsm_config.sms_psk_key_cb_arg)) { + return -1; + } + } +# endif /* defined(ANJAY_WITH_SMS) && \ + defined(AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE) */ + } + return 0; +} +#endif // ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT + +int _standalone_sec_object_validate_and_process_keys(anjay_t *anjay, + sec_repr_t *repr) { + int result = sec_object_validate(anjay, repr); +#ifdef ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT + if (!result) { + // NOTE: THIS IS A HACK. We move key material to HSM storage during the + // validation stage, because: + // - We cannot do it during the write stage, because we need to know + // what type of key we're dealing with, and the security mode might be + // written later. + // - We shouldn't really do it at the commit stage, because the commit + // operation is supposed to be as unlikely to fail as possible, and + // storing keys on HSM can fail easily. + // So we exploit the fact that the act of moving a key to HSM is + // "transparent" in terms of the semantic data model state - i.e., + // whether the operation is successful or not, the data model contains + // the same information as far as the LwM2M spec is concerned. In other + // words, what we do here does not change the data model, only its + // internal representation, so we should be safe doing that during the + // validation stage. + result = sec_object_process_keys(repr, repr->prng_ctx); + } +#endif // ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT + return result; +} + +int _standalone_sec_transaction_begin_impl(sec_repr_t *repr) { + assert(!repr->saved_instances); + assert(!repr->in_transaction); + repr->saved_instances = _standalone_sec_clone_instances(repr); + if (!repr->saved_instances && repr->instances) { + return ANJAY_ERR_INTERNAL; + } + repr->saved_modified_since_persist = repr->modified_since_persist; + repr->in_transaction = true; + return 0; +} + +int _standalone_sec_transaction_commit_impl(sec_repr_t *repr) { + assert(repr->in_transaction); + _standalone_sec_destroy_instances(&repr->saved_instances, true); + repr->in_transaction = false; + return 0; +} + +int _standalone_sec_transaction_validate_impl(anjay_t *anjay, + sec_repr_t *repr) { + assert(repr->in_transaction); + return _standalone_sec_object_validate_and_process_keys(anjay, repr); +} + +int _standalone_sec_transaction_rollback_impl(sec_repr_t *repr) { + assert(repr->in_transaction); + _standalone_sec_destroy_instances(&repr->instances, true); + repr->instances = repr->saved_instances; + repr->saved_instances = NULL; + repr->modified_since_persist = repr->saved_modified_since_persist; + repr->in_transaction = false; + return 0; +} diff --git a/standalone/security/standalone_security_transaction.h b/standalone/security/standalone_security_transaction.h new file mode 100644 index 00000000..38b2158e --- /dev/null +++ b/standalone/security/standalone_security_transaction.h @@ -0,0 +1,14 @@ +#ifndef ANJAY_STANDALONE_SECURITY_TRANSACTION_H +#define ANJAY_STANDALONE_SECURITY_TRANSACTION_H + +#include "standalone_mod_security.h" + +int _standalone_sec_object_validate_and_process_keys(anjay_t *anjay, + sec_repr_t *repr); + +int _standalone_sec_transaction_begin_impl(sec_repr_t *repr); +int _standalone_sec_transaction_commit_impl(sec_repr_t *repr); +int _standalone_sec_transaction_validate_impl(anjay_t *anjay, sec_repr_t *repr); +int _standalone_sec_transaction_rollback_impl(sec_repr_t *repr); + +#endif /* ANJAY_STANDALONE_SECURITY_TRANSACTION_H */ diff --git a/standalone/security/standalone_security_utils.c b/standalone/security/standalone_security_utils.c new file mode 100644 index 00000000..f757b81b --- /dev/null +++ b/standalone/security/standalone_security_utils.c @@ -0,0 +1,472 @@ +#include + +#include "standalone_security_utils.h" + +int _standalone_sec_validate_security_mode(int32_t security_mode) { + switch (security_mode) { + case ANJAY_SECURITY_NOSEC: + case ANJAY_SECURITY_PSK: + case ANJAY_SECURITY_CERTIFICATE: + case ANJAY_SECURITY_EST: + return 0; + case ANJAY_SECURITY_RPK: + security_log(ERROR, _("Raw Public Key mode not supported")); + return ANJAY_ERR_NOT_IMPLEMENTED; + default: + security_log(ERROR, _("Invalid Security Mode")); + return ANJAY_ERR_BAD_REQUEST; + } +} + +int _standalone_sec_fetch_security_mode(anjay_input_ctx_t *ctx, + anjay_security_mode_t *out) { + int32_t value; + int retval = anjay_get_i32(ctx, &value); + if (!retval) { + retval = _standalone_sec_validate_security_mode(value); + } + if (!retval) { + *out = (anjay_security_mode_t) value; + } + return retval; +} + +#ifdef ANJAY_WITH_SMS +int _standalone_sec_validate_sms_security_mode(int32_t security_mode) { + switch (security_mode) { + case ANJAY_SMS_SECURITY_DTLS_PSK: + case ANJAY_SMS_SECURITY_NOSEC: + return 0; + case ANJAY_SMS_SECURITY_SECURE_PACKET: + security_log(DEBUG, _("Secure Packet mode not supported")); + return ANJAY_ERR_NOT_IMPLEMENTED; + default: + security_log(DEBUG, _("Invalid SMS Security Mode")); + return ANJAY_ERR_BAD_REQUEST; + } +} + +int _standalone_sec_fetch_sms_security_mode(anjay_input_ctx_t *ctx, + anjay_sms_security_mode_t *out) { + int32_t value; + int retval = anjay_get_i32(ctx, &value); + if (!retval) { + retval = _standalone_sec_validate_sms_security_mode(value); + } + if (!retval) { + *out = (anjay_sms_security_mode_t) value; + } + return retval; +} +#endif // ANJAY_WITH_SMS + +static int _standalone_sec_validate_short_server_id(int32_t ssid) { + return ssid > 0 && ssid <= UINT16_MAX ? 0 : -1; +} + +int _standalone_sec_fetch_short_server_id(anjay_input_ctx_t *ctx, + anjay_ssid_t *out) { + int32_t value; + int retval = anjay_get_i32(ctx, &value); + if (!retval) { + retval = _standalone_sec_validate_short_server_id(value); + } + if (!retval) { + *out = (anjay_ssid_t) value; + } + return retval; +} + +#if defined(ANJAY_WITH_SECURITY_STRUCTURED) \ + || (defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) \ + && defined(AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE)) +int _standalone_sec_init_certificate_chain_resource( + sec_key_or_data_t *out_resource, + sec_key_or_data_type_t type, + const avs_crypto_certificate_chain_info_t *in_value) { + avs_crypto_certificate_chain_info_t *array = NULL; + size_t array_element_count; + if (avs_is_err(avs_crypto_certificate_chain_info_copy_as_array( + &array, &array_element_count, *in_value))) { + return -1; + } + assert(array || !array_element_count); + assert(!out_resource->prev_ref); + assert(!out_resource->next_ref); + assert(type == SEC_KEY_AS_KEY_EXTERNAL || type == SEC_KEY_AS_KEY_OWNED); + out_resource->type = type; + out_resource->value.key.info = + avs_crypto_certificate_chain_info_from_array(array, + array_element_count) + .desc; + out_resource->value.key.heap_buf = array; + return 0; +} + +int _standalone_sec_init_private_key_resource( + sec_key_or_data_t *out_resource, + sec_key_or_data_type_t type, + const avs_crypto_private_key_info_t *in_value) { + avs_crypto_private_key_info_t *private_key = NULL; + if (avs_is_err(avs_crypto_private_key_info_copy(&private_key, *in_value))) { + return -1; + } + assert(private_key); + assert(!out_resource->prev_ref); + assert(!out_resource->next_ref); + assert(type == SEC_KEY_AS_KEY_EXTERNAL || type == SEC_KEY_AS_KEY_OWNED); + out_resource->type = type; + out_resource->value.key.info = private_key->desc; + out_resource->value.key.heap_buf = private_key; + return 0; +} +#endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) || \ + (defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) && \ + defined(AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE)) */ + +#if defined(ANJAY_WITH_SECURITY_STRUCTURED) \ + || (defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) \ + && defined(AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE)) +int _standalone_sec_init_psk_identity_resource( + sec_key_or_data_t *out_resource, + sec_key_or_data_type_t type, + const avs_crypto_psk_identity_info_t *in_value) { + avs_crypto_psk_identity_info_t *psk_identity = NULL; + if (avs_is_err( + avs_crypto_psk_identity_info_copy(&psk_identity, *in_value))) { + return -1; + } + assert(psk_identity); + assert(!out_resource->prev_ref); + assert(!out_resource->next_ref); + assert(type == SEC_KEY_AS_KEY_EXTERNAL || type == SEC_KEY_AS_KEY_OWNED); + out_resource->type = type; + out_resource->value.key.info = psk_identity->desc; + out_resource->value.key.heap_buf = psk_identity; + return 0; +} + +int _standalone_sec_init_psk_key_resource( + sec_key_or_data_t *out_resource, + sec_key_or_data_type_t type, + const avs_crypto_psk_key_info_t *in_value) { + avs_crypto_psk_key_info_t *psk_key = NULL; + if (avs_is_err(avs_crypto_psk_key_info_copy(&psk_key, *in_value))) { + return -1; + } + assert(psk_key); + assert(!out_resource->prev_ref); + assert(!out_resource->next_ref); + assert(type == SEC_KEY_AS_KEY_EXTERNAL || type == SEC_KEY_AS_KEY_OWNED); + out_resource->type = type; + out_resource->value.key.info = psk_key->desc; + out_resource->value.key.heap_buf = psk_key; + return 0; +} +#endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) || \ + (defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) && \ + defined(AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE)) */ + +#ifdef ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT +static void +remove_sec_key_from_engine(const avs_crypto_security_info_union_t *desc) { + assert(desc->source != AVS_CRYPTO_DATA_SOURCE_LIST); + if (desc->source == AVS_CRYPTO_DATA_SOURCE_ENGINE) { + avs_error_t err; + switch (desc->type) { +# ifdef AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE + case AVS_CRYPTO_SECURITY_INFO_CERTIFICATE_CHAIN: + err = avs_crypto_pki_engine_certificate_rm(desc->info.engine.query); + break; + case AVS_CRYPTO_SECURITY_INFO_PRIVATE_KEY: + err = avs_crypto_pki_engine_key_rm(desc->info.engine.query); + break; +# endif // AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE +# ifdef AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE + case AVS_CRYPTO_SECURITY_INFO_PSK_IDENTITY: + err = avs_crypto_psk_engine_identity_rm(desc->info.engine.query); + break; + case AVS_CRYPTO_SECURITY_INFO_PSK_KEY: + err = avs_crypto_psk_engine_key_rm(desc->info.engine.query); + break; +# endif // AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE + default: + err = avs_errno(AVS_EINVAL); + } + if (avs_is_err(err)) { + security_log(WARNING, + _("could not remove ") "%s" _( + " from the engine storage"), + desc->info.engine.query); + } + } else if (desc->source == AVS_CRYPTO_DATA_SOURCE_ARRAY) { + for (size_t i = 0; i < desc->info.array.element_count; ++i) { + remove_sec_key_from_engine(&desc->info.array.array_ptr[i]); + } + } +} +#endif // ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT + +void _standalone_sec_key_or_data_cleanup(sec_key_or_data_t *value, + bool remove_from_engine) { + (void) remove_from_engine; + if (!value->prev_ref && !value->next_ref) { + switch (value->type) { + case SEC_KEY_AS_DATA: + memset(value->value.data.data, 0, value->value.data.capacity); + _standalone_raw_buffer_clear(&value->value.data); + break; +#if defined(ANJAY_WITH_SECURITY_STRUCTURED) \ + || defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) + case SEC_KEY_AS_KEY_OWNED: +# ifdef ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT + if (remove_from_engine) { + remove_sec_key_from_engine(&value->value.key.info); + } +# endif // ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT + // fall-through + case SEC_KEY_AS_KEY_EXTERNAL: + avs_free(value->value.key.heap_buf); + break; +#endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) || \ + defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) */ + default: + AVS_UNREACHABLE("invalid value of sec_key_or_data_type_t"); + } + } else { + if (value->prev_ref) { + value->prev_ref->next_ref = value->next_ref; + } + if (value->next_ref) { + value->next_ref->prev_ref = value->prev_ref; + } + } + memset(value, 0, sizeof(*value)); + assert(value->type == SEC_KEY_AS_DATA); +} + +void _standalone_sec_destroy_instance_fields(sec_instance_t *instance, + bool remove_from_engine) { + if (!instance) { + return; + } + avs_free((char *) (intptr_t) instance->server_uri); + _standalone_sec_key_or_data_cleanup(&instance->public_cert_or_psk_identity, + remove_from_engine); + _standalone_sec_key_or_data_cleanup(&instance->private_cert_or_psk_key, + remove_from_engine); + _standalone_raw_buffer_clear(&instance->server_public_key); +#ifdef ANJAY_WITH_LWM2M11 + AVS_LIST_CLEAR(&instance->enabled_ciphersuites); + avs_free(instance->server_name_indication); +#endif // ANJAY_WITH_LWM2M11 +#ifdef ANJAY_WITH_SMS + _standalone_sec_key_or_data_cleanup(&instance->sms_key_params, + remove_from_engine); + _standalone_sec_key_or_data_cleanup(&instance->sms_secret_key, + remove_from_engine); + avs_free((char *) (intptr_t) instance->sms_number); +#endif // ANJAY_WITH_SMS +} + +void _standalone_sec_destroy_instances(AVS_LIST(sec_instance_t) *instances_ptr, + bool remove_from_engine) { + AVS_LIST_CLEAR(instances_ptr) { + _standalone_sec_destroy_instance_fields(*instances_ptr, + remove_from_engine); + } +} + +static void sec_key_or_data_create_ref(sec_key_or_data_t *dest, + sec_key_or_data_t *src) { + *dest = *src; + dest->prev_ref = src; + dest->next_ref = src->next_ref; + if (src->next_ref) { + src->next_ref->prev_ref = dest; + } + src->next_ref = dest; +} + +static int _standalone_sec_clone_instance(sec_instance_t *dest, + sec_instance_t *src) { + *dest = *src; + + assert(src->server_uri); + dest->server_uri = avs_strdup(src->server_uri); + if (!dest->server_uri) { + security_log(ERROR, _("Cannot clone Server Uri resource")); + return -1; + } + + sec_key_or_data_create_ref(&dest->public_cert_or_psk_identity, + &src->public_cert_or_psk_identity); + sec_key_or_data_create_ref(&dest->private_cert_or_psk_key, + &src->private_cert_or_psk_key); + + memset(&dest->server_public_key, 0, sizeof(dest->server_public_key)); + if (_standalone_raw_buffer_clone(&dest->server_public_key, + &src->server_public_key)) { + security_log(ERROR, _("Cannot clone Server Public Key resource")); + return -1; + } + +#ifdef ANJAY_WITH_LWM2M11 + dest->server_name_indication = NULL; + if (src->server_name_indication + && !(dest->server_name_indication = + avs_strdup(src->server_name_indication))) { + security_log(ERROR, _("Cannot clone SNI resource")); + return -1; + } +#endif // ANJAY_WITH_LWM2M11 + +#ifdef ANJAY_WITH_SMS + sec_key_or_data_create_ref(&dest->sms_key_params, &src->sms_key_params); + sec_key_or_data_create_ref(&dest->sms_secret_key, &src->sms_secret_key); + + dest->sms_number = NULL; + if (src->sms_number) { + dest->sms_number = avs_strdup(src->sms_number); + if (!dest->sms_number) { + security_log(ERROR, _("Cannot clone Server SMS Number resource")); + return -1; + } + } +#endif // ANJAY_WITH_SMS + + return 0; +} + +AVS_LIST(sec_instance_t) +_standalone_sec_clone_instances(const sec_repr_t *repr) { + AVS_LIST(sec_instance_t) retval = NULL; + AVS_LIST(sec_instance_t) current; + AVS_LIST(sec_instance_t) *last; + last = &retval; + + AVS_LIST_FOREACH(current, repr->instances) { + if (AVS_LIST_INSERT_NEW(sec_instance_t, last)) { + if (_standalone_sec_clone_instance(*last, current)) { + security_log(ERROR, + _("Cannot clone Security Object Instances")); + _standalone_sec_destroy_instances(&retval, false); + return NULL; + } + AVS_LIST_ADVANCE_PTR(&last); + } + } + return retval; +} + +void _standalone_raw_buffer_clear(standalone_raw_buffer_t *buffer) { + avs_free(buffer->data); + buffer->data = NULL; + buffer->size = 0; + buffer->capacity = 0; +} + +int _standalone_raw_buffer_clone(standalone_raw_buffer_t *dst, + const standalone_raw_buffer_t *src) { + assert(!dst->data && !dst->size); + if (!src->size) { + return 0; + } + dst->data = avs_malloc(src->size); + if (!dst->data) { + return -1; + } + dst->capacity = src->size; + dst->size = src->size; + memcpy(dst->data, src->data, src->size); + return 0; +} + +typedef int chunk_getter_t(anjay_input_ctx_t *ctx, + char *out, + size_t out_size, + bool *out_finished, + size_t *out_bytes_read); + +static int bytes_getter(anjay_input_ctx_t *ctx, + char *out, + size_t size, + bool *out_finished, + size_t *out_bytes_read) { + return anjay_get_bytes(ctx, out_bytes_read, out_finished, out, size); +} + +static int string_getter(anjay_input_ctx_t *ctx, + char *out, + size_t size, + bool *out_finished, + size_t *out_bytes_read) { + int result = anjay_get_string(ctx, out, size); + if (result < 0) { + return result; + } + *out_finished = true; + *out_bytes_read = strlen(out) + 1; + if (result == ANJAY_BUFFER_TOO_SHORT) { + *out_finished = false; + /** + * We don't want null terminator, because we're still in the phase of + * string chunk concatenation (and null terminators in the middle of + * the string are rather bad). + */ + --*out_bytes_read; + } + return 0; +} + +static int generic_getter(anjay_input_ctx_t *ctx, + char **out, + size_t *out_bytes_read, + chunk_getter_t *getter) { + char tmp[128]; + bool finished = false; + char *buffer = NULL; + size_t buffer_size = 0; + int result; + do { + size_t chunk_bytes_read = 0; + if ((result = getter(ctx, tmp, sizeof(tmp), &finished, + &chunk_bytes_read))) { + goto error; + } + if (chunk_bytes_read > 0) { + char *bigger_buffer = + (char *) avs_realloc(buffer, + buffer_size + chunk_bytes_read); + if (!bigger_buffer) { + result = ANJAY_ERR_INTERNAL; + goto error; + } + memcpy(bigger_buffer + buffer_size, tmp, chunk_bytes_read); + buffer = bigger_buffer; + buffer_size += chunk_bytes_read; + } + } while (!finished); + *out = buffer; + *out_bytes_read = buffer_size; + return 0; +error: + avs_free(buffer); + return result; +} + +int _standalone_io_fetch_bytes(anjay_input_ctx_t *ctx, + standalone_raw_buffer_t *buffer) { + _standalone_raw_buffer_clear(buffer); + int retval = generic_getter(ctx, (char **) &buffer->data, &buffer->size, + bytes_getter); + buffer->capacity = buffer->size; + return retval; +} + +int _standalone_io_fetch_string(anjay_input_ctx_t *ctx, char **out) { + avs_free(*out); + *out = NULL; + size_t bytes_read = 0; + return generic_getter(ctx, out, &bytes_read, string_getter); +} diff --git a/standalone/security/standalone_security_utils.h b/standalone/security/standalone_security_utils.h new file mode 100644 index 00000000..584481d0 --- /dev/null +++ b/standalone/security/standalone_security_utils.h @@ -0,0 +1,112 @@ +#ifndef ANJAY_STANDALONE_SECURITY_UTILS_H +#define ANJAY_STANDALONE_SECURITY_UTILS_H + +#include + +#include "standalone_mod_security.h" + +sec_repr_t *_standalone_sec_get(const anjay_dm_object_def_t *const *obj_ptr); + +/** + * Fetches UDP Security Mode from @p ctx, performs validation and in case of + * success sets @p *out to one of @p anjay_security_mode_t enum value. + */ +int _standalone_sec_fetch_security_mode(anjay_input_ctx_t *ctx, + anjay_security_mode_t *out); + +int _standalone_sec_validate_security_mode(int32_t security_mode); + +#ifdef ANJAY_WITH_SMS +/** + * Fetches SMS Security Mode from @p ctx, performs validation and in case of + * success sets @p *out to one of @p anjay_sms_security_mode_t enum value. + */ +int _standalone_sec_fetch_sms_security_mode(anjay_input_ctx_t *ctx, + anjay_sms_security_mode_t *out); + +int _standalone_sec_validate_sms_security_mode(int32_t security_mode); +#endif // ANJAY_WITH_SMS + +/** + * Fetches SSID from @p ctx, performs validation and in case of success sets + * @p *out . + */ +int _standalone_sec_fetch_short_server_id(anjay_input_ctx_t *ctx, + anjay_ssid_t *out); + +#if defined(ANJAY_WITH_SECURITY_STRUCTURED) \ + || (defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) \ + && defined(AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE)) +int _standalone_sec_init_certificate_chain_resource( + sec_key_or_data_t *out_resource, + sec_key_or_data_type_t type, + const avs_crypto_certificate_chain_info_t *in_value); + +int _standalone_sec_init_private_key_resource( + sec_key_or_data_t *out_resource, + sec_key_or_data_type_t type, + const avs_crypto_private_key_info_t *in_value); +#endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) || \ + (defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) && \ + defined(AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE)) */ + +#if defined(ANJAY_WITH_SECURITY_STRUCTURED) \ + || (defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) \ + && defined(AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE)) +int _standalone_sec_init_psk_identity_resource( + sec_key_or_data_t *out_resource, + sec_key_or_data_type_t type, + const avs_crypto_psk_identity_info_t *in_value); + +int _standalone_sec_init_psk_key_resource( + sec_key_or_data_t *out_resource, + sec_key_or_data_type_t type, + const avs_crypto_psk_key_info_t *in_value); +#endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) || \ + (defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) && \ + defined(AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE)) */ + +void _standalone_sec_key_or_data_cleanup(sec_key_or_data_t *value, + bool remove_from_engine); + +/** + * Frees all resources held in the @p instance. + */ +void _standalone_sec_destroy_instance_fields(sec_instance_t *instance, + bool remove_from_engine); + +/** + * Frees all resources held in instances from the @p instances_ptr list, + * and the list itself. + */ +void _standalone_sec_destroy_instances(AVS_LIST(sec_instance_t) *instances_ptr, + bool remove_from_engine); + +/** + * Clones all instances of the given Security Object @p repr . Return NULL + * if either there was nothing to clone or an error has occurred. + */ +AVS_LIST(sec_instance_t) +_standalone_sec_clone_instances(const sec_repr_t *repr); + +void _standalone_raw_buffer_clear(standalone_raw_buffer_t *buffer); + +int _standalone_raw_buffer_clone(standalone_raw_buffer_t *dst, + const standalone_raw_buffer_t *src); + +/** + * Fetches bytes from @p ctx. On success it frees underlying @p buffer storage + * via @p _anjay_sec_raw_buffer_clear and reinitializes @p buffer properly with + * obtained data. + */ +int _standalone_io_fetch_bytes(anjay_input_ctx_t *ctx, + standalone_raw_buffer_t *buffer); + +/** + * Fetches string from @p ctx. It calls avs_free() on @p *out and, on success, + * reinitializes @p *out properly with a pointer to (heap allocated) obtained + * data. + */ +int _standalone_io_fetch_string(anjay_input_ctx_t *ctx, char **out); + +#endif /* ANJAY_STANDALONE_SECURITY_UTILS_H */ diff --git a/standalone/server/standalone_mod_server.c b/standalone/server/standalone_mod_server.c new file mode 100644 index 00000000..cb19cf7f --- /dev/null +++ b/standalone/server/standalone_mod_server.c @@ -0,0 +1,783 @@ +#include +#include + +#include "standalone_mod_server.h" +#include "standalone_server_transaction.h" +#include "standalone_server_utils.h" + +static const struct { + server_rid_t rid; + anjay_dm_resource_kind_t kind; +} SERVER_RESOURCE_INFO[] = { + { + .rid = SERV_RES_SSID, + .kind = ANJAY_DM_RES_R + }, + { + .rid = SERV_RES_LIFETIME, + .kind = ANJAY_DM_RES_RW + }, + { + .rid = SERV_RES_DEFAULT_MIN_PERIOD, + .kind = ANJAY_DM_RES_RW + }, + { + .rid = SERV_RES_DEFAULT_MAX_PERIOD, + .kind = ANJAY_DM_RES_RW + }, +#ifndef ANJAY_WITHOUT_DEREGISTER + { + .rid = SERV_RES_DISABLE, + .kind = ANJAY_DM_RES_E + }, + { + .rid = SERV_RES_DISABLE_TIMEOUT, + .kind = ANJAY_DM_RES_RW + }, +#endif // ANJAY_WITHOUT_DEREGISTER + { + .rid = SERV_RES_NOTIFICATION_STORING_WHEN_DISABLED_OR_OFFLINE, + .kind = ANJAY_DM_RES_RW + }, + { + .rid = SERV_RES_BINDING, + .kind = ANJAY_DM_RES_RW + }, + { + .rid = SERV_RES_REGISTRATION_UPDATE_TRIGGER, + .kind = ANJAY_DM_RES_E + }, +#ifdef ANJAY_WITH_LWM2M11 + { + .rid = SERV_RES_BOOTSTRAP_REQUEST_TRIGGER, + .kind = ANJAY_DM_RES_E + }, + { + .rid = SERV_RES_TLS_DTLS_ALERT_CODE, + .kind = ANJAY_DM_RES_R + }, + { + .rid = SERV_RES_LAST_BOOTSTRAPPED, + .kind = ANJAY_DM_RES_R + }, + { + .rid = SERV_RES_BOOTSTRAP_ON_REGISTRATION_FAILURE, + .kind = ANJAY_DM_RES_R + }, + { + .rid = SERV_RES_SERVER_COMMUNICATION_RETRY_COUNT, + .kind = ANJAY_DM_RES_RW + }, + { + .rid = SERV_RES_SERVER_COMMUNICATION_RETRY_TIMER, + .kind = ANJAY_DM_RES_RW + }, + { + .rid = SERV_RES_SERVER_COMMUNICATION_SEQUENCE_DELAY_TIMER, + .kind = ANJAY_DM_RES_RW + }, + { + .rid = SERV_RES_SERVER_COMMUNICATION_SEQUENCE_RETRY_COUNT, + .kind = ANJAY_DM_RES_RW + }, +# ifdef ANJAY_WITH_SMS + { + .rid = SERV_RES_TRIGGER, + .kind = ANJAY_DM_RES_RW + }, +# endif // ANJAY_WITH_SMS + { + .rid = SERV_RES_PREFERRED_TRANSPORT, + .kind = ANJAY_DM_RES_RW + }, +# ifdef ANJAY_WITH_SEND + { + .rid = SERV_RES_MUTE_SEND, + .kind = ANJAY_DM_RES_RW + }, +# endif // ANJAY_WITH_SEND +#endif // ANJAY_WITH_LWM2M11 +}; + +static inline server_instance_t *find_instance(server_repr_t *repr, + anjay_iid_t iid) { + if (!repr) { + return NULL; + } + AVS_LIST(server_instance_t) it; + AVS_LIST_FOREACH(it, repr->instances) { + if (it->iid == iid) { + return it; + } else if (it->iid > iid) { + break; + } + } + return NULL; +} + +static anjay_iid_t get_new_iid(AVS_LIST(server_instance_t) instances) { + anjay_iid_t iid = 0; + AVS_LIST(server_instance_t) it; + AVS_LIST_FOREACH(it, instances) { + if (it->iid == iid) { + ++iid; + } else if (it->iid > iid) { + break; + } + } + return iid; +} + +static int assign_iid(server_repr_t *repr, anjay_iid_t *inout_iid) { + *inout_iid = get_new_iid(repr->instances); + if (*inout_iid == ANJAY_ID_INVALID) { + return -1; + } + return 0; +} + +static void insert_created_instance(server_repr_t *repr, + AVS_LIST(server_instance_t) new_instance) { + AVS_LIST(server_instance_t) *ptr; + AVS_LIST_FOREACH_PTR(ptr, &repr->instances) { + assert((*ptr)->iid != new_instance->iid); + if ((*ptr)->iid > new_instance->iid) { + break; + } + } + _standalone_serv_mark_modified(repr); + AVS_LIST_INSERT(ptr, new_instance); +} + +static int add_instance(server_repr_t *repr, + const standalone_server_instance_t *instance, + anjay_iid_t *inout_iid) { + if (*inout_iid == ANJAY_ID_INVALID) { + if (assign_iid(repr, inout_iid)) { + return -1; + } + } else if (find_instance(repr, *inout_iid)) { + return -1; + } + AVS_LIST(server_instance_t) new_instance = + AVS_LIST_NEW_ELEMENT(server_instance_t); + if (!new_instance) { + server_log(ERROR, _("out of memory")); + return -1; + } + if (instance->binding) { + if (!anjay_binding_mode_valid(instance->binding) + || avs_simple_snprintf(new_instance->binding.data, + sizeof(new_instance->binding.data), "%s", + instance->binding) + < 0) { + server_log(ERROR, _("Unsupported binding mode: ") "%s", + instance->binding); + AVS_LIST_CLEAR(&new_instance); + return -1; + } + new_instance->present_resources[SERV_RES_BINDING] = true; + } + new_instance->iid = *inout_iid; + new_instance->present_resources[SERV_RES_SSID] = true; + new_instance->ssid = instance->ssid; + new_instance->present_resources[SERV_RES_LIFETIME] = true; + new_instance->lifetime = instance->lifetime; + if (instance->default_min_period >= 0) { + new_instance->present_resources[SERV_RES_DEFAULT_MIN_PERIOD] = true; + new_instance->default_min_period = instance->default_min_period; + } + + if (instance->default_max_period >= 0) { + new_instance->present_resources[SERV_RES_DEFAULT_MAX_PERIOD] = true; + new_instance->default_max_period = instance->default_max_period; + } +#ifndef ANJAY_WITHOUT_DEREGISTER + new_instance->present_resources[SERV_RES_DISABLE] = true; + if (instance->disable_timeout >= 0) { + new_instance->present_resources[SERV_RES_DISABLE_TIMEOUT] = true; + new_instance->disable_timeout = instance->disable_timeout; + } +#endif // ANJAY_WITHOUT_DEREGISTER + new_instance->present_resources + [SERV_RES_NOTIFICATION_STORING_WHEN_DISABLED_OR_OFFLINE] = true; + new_instance->notification_storing = instance->notification_storing; + new_instance->present_resources[SERV_RES_REGISTRATION_UPDATE_TRIGGER] = + true; +#ifdef ANJAY_WITH_LWM2M11 +# ifdef ANJAY_WITH_BOOTSTRAP + new_instance->present_resources[SERV_RES_BOOTSTRAP_REQUEST_TRIGGER] = true; + new_instance + ->present_resources[SERV_RES_BOOTSTRAP_ON_REGISTRATION_FAILURE] = + true; +# endif // ANJAY_WITH_BOOTSTRAP + new_instance->bootstrap_on_registration_failure = + instance->bootstrap_on_registration_failure + ? *instance->bootstrap_on_registration_failure + : true; + if (instance->communication_retry_count) { + new_instance + ->present_resources[SERV_RES_SERVER_COMMUNICATION_RETRY_COUNT] = + true; + new_instance->server_communication_retry_count = + *instance->communication_retry_count; + } + if (instance->communication_retry_timer) { + new_instance + ->present_resources[SERV_RES_SERVER_COMMUNICATION_RETRY_TIMER] = + true; + new_instance->server_communication_retry_timer = + *instance->communication_retry_timer; + } + if (instance->preferred_transport) { + new_instance->preferred_transport = instance->preferred_transport; + new_instance->present_resources[SERV_RES_PREFERRED_TRANSPORT] = true; + } + if (instance->communication_sequence_retry_count) { + new_instance->present_resources + [SERV_RES_SERVER_COMMUNICATION_SEQUENCE_RETRY_COUNT] = true; + new_instance->server_communication_sequence_retry_count = + *instance->communication_sequence_retry_count; + } +# ifdef ANJAY_WITH_SMS + if (instance->trigger) { + new_instance->present_resources[SERV_RES_TRIGGER] = true; + new_instance->trigger = *instance->trigger; + } +# endif // ANJAY_WITH_SMS + if (instance->communication_sequence_delay_timer) { + new_instance->present_resources + [SERV_RES_SERVER_COMMUNICATION_SEQUENCE_DELAY_TIMER] = true; + new_instance->server_communication_sequence_delay_timer = + *instance->communication_sequence_delay_timer; + } +# ifdef ANJAY_WITH_SEND + new_instance->present_resources[SERV_RES_MUTE_SEND] = true; + new_instance->mute_send = instance->mute_send; +# endif // ANJAY_WITH_SEND +#endif // ANJAY_WITH_LWM2M11 + + insert_created_instance(repr, new_instance); + server_log(INFO, _("Added instance ") "%u" _(" (SSID: ") "%u" _(")"), + *inout_iid, instance->ssid); + return 0; +} + +static int del_instance(server_repr_t *repr, anjay_iid_t iid) { + AVS_LIST(server_instance_t) *it; + AVS_LIST_FOREACH_PTR(it, &repr->instances) { + if ((*it)->iid == iid) { + AVS_LIST_DELETE(it); + _standalone_serv_mark_modified(repr); + return 0; + } else if ((*it)->iid > iid) { + break; + } + } + + assert(0); + return ANJAY_ERR_NOT_FOUND; +} + +static int serv_list_instances(anjay_t *anjay, + const anjay_dm_object_def_t *const *obj_ptr, + anjay_dm_list_ctx_t *ctx) { + (void) anjay; + + server_repr_t *repr = _standalone_serv_get(obj_ptr); + AVS_LIST(server_instance_t) it; + AVS_LIST_FOREACH(it, repr->instances) { + anjay_dm_emit(ctx, it->iid); + } + return 0; +} + +static int serv_instance_create(anjay_t *anjay, + const anjay_dm_object_def_t *const *obj_ptr, + anjay_iid_t iid) { + (void) anjay; + server_repr_t *repr = _standalone_serv_get(obj_ptr); + assert(iid != ANJAY_ID_INVALID); + AVS_LIST(server_instance_t) created = + AVS_LIST_NEW_ELEMENT(server_instance_t); + if (!created) { + return ANJAY_ERR_INTERNAL; + } + created->iid = iid; + _standalone_serv_reset_instance(created); + + insert_created_instance(repr, created); + return 0; +} + +static int serv_instance_remove(anjay_t *anjay, + const anjay_dm_object_def_t *const *obj_ptr, + anjay_iid_t iid) { + (void) anjay; + return del_instance(_standalone_serv_get(obj_ptr), iid); +} + +static int serv_instance_reset(anjay_t *anjay, + const anjay_dm_object_def_t *const *obj_ptr, + anjay_iid_t iid) { + (void) anjay; + server_instance_t *inst = find_instance(_standalone_serv_get(obj_ptr), iid); + assert(inst); + + anjay_ssid_t ssid = inst->ssid; + _standalone_serv_reset_instance(inst); + inst->present_resources[SERV_RES_SSID] = true; + inst->ssid = ssid; + return 0; +} + +static int serv_list_resources(anjay_t *anjay, + const anjay_dm_object_def_t *const *obj_ptr, + anjay_iid_t iid, + anjay_dm_resource_list_ctx_t *ctx) { + (void) anjay; + const server_instance_t *inst = + find_instance(_standalone_serv_get(obj_ptr), iid); + assert(inst); + + for (size_t resource = 0; resource < AVS_ARRAY_SIZE(SERVER_RESOURCE_INFO); + resource++) { + anjay_rid_t rid = SERVER_RESOURCE_INFO[resource].rid; + anjay_dm_emit_res(ctx, rid, SERVER_RESOURCE_INFO[resource].kind, + inst->present_resources[rid] ? ANJAY_DM_RES_PRESENT + : ANJAY_DM_RES_ABSENT); + } + + return 0; +} + +static int serv_read(anjay_t *anjay, + const anjay_dm_object_def_t *const *obj_ptr, + anjay_iid_t iid, + anjay_rid_t rid, + anjay_riid_t riid, + anjay_output_ctx_t *ctx) { + (void) anjay; + (void) riid; + assert(riid == ANJAY_ID_INVALID); + + const server_instance_t *inst = + find_instance(_standalone_serv_get(obj_ptr), iid); + assert(inst); + + switch ((server_rid_t) rid) { + case SERV_RES_SSID: + return anjay_ret_i64(ctx, inst->ssid); + case SERV_RES_LIFETIME: + return anjay_ret_i64(ctx, inst->lifetime); + case SERV_RES_DEFAULT_MIN_PERIOD: + return anjay_ret_i64(ctx, inst->default_min_period); + case SERV_RES_DEFAULT_MAX_PERIOD: + return anjay_ret_i64(ctx, inst->default_max_period); +#ifndef ANJAY_WITHOUT_DEREGISTER + case SERV_RES_DISABLE_TIMEOUT: + return anjay_ret_i64(ctx, inst->disable_timeout); +#endif // ANJAY_WITHOUT_DEREGISTER + case SERV_RES_NOTIFICATION_STORING_WHEN_DISABLED_OR_OFFLINE: + return anjay_ret_bool(ctx, inst->notification_storing); + case SERV_RES_BINDING: + return anjay_ret_string(ctx, inst->binding.data); +#ifdef ANJAY_WITH_LWM2M11 + case SERV_RES_TLS_DTLS_ALERT_CODE: + return anjay_ret_u64(ctx, inst->last_alert); + case SERV_RES_LAST_BOOTSTRAPPED: + return anjay_ret_i64(ctx, inst->last_bootstrapped_timestamp); +# ifdef ANJAY_WITH_BOOTSTRAP + case SERV_RES_BOOTSTRAP_ON_REGISTRATION_FAILURE: + return anjay_ret_bool(ctx, inst->bootstrap_on_registration_failure); +# endif // ANJAY_WITH_BOOTSTRAP + case SERV_RES_SERVER_COMMUNICATION_RETRY_COUNT: + return anjay_ret_u64(ctx, inst->server_communication_retry_count); + case SERV_RES_SERVER_COMMUNICATION_RETRY_TIMER: + return anjay_ret_u64(ctx, inst->server_communication_retry_timer); + case SERV_RES_SERVER_COMMUNICATION_SEQUENCE_RETRY_COUNT: + return anjay_ret_u64(ctx, + inst->server_communication_sequence_retry_count); + case SERV_RES_SERVER_COMMUNICATION_SEQUENCE_DELAY_TIMER: + return anjay_ret_u64(ctx, + inst->server_communication_sequence_delay_timer); +# ifdef ANJAY_WITH_SMS + case SERV_RES_TRIGGER: + return anjay_ret_bool(ctx, inst->trigger); +# endif // ANJAY_WITH_SMS + case SERV_RES_PREFERRED_TRANSPORT: { + char tmp[2] = { inst->preferred_transport, '\0' }; + return anjay_ret_string(ctx, tmp); + } +# ifdef ANJAY_WITH_SEND + case SERV_RES_MUTE_SEND: + return anjay_ret_bool(ctx, inst->mute_send); +# endif // ANJAY_WITH_SEND +#endif // ANJAY_WITH_LWM2M11 + default: + AVS_UNREACHABLE( + "Read called on unknown or non-readable Server resource"); + return ANJAY_ERR_METHOD_NOT_ALLOWED; + } +} + +static int serv_write(anjay_t *anjay, + const anjay_dm_object_def_t *const *obj_ptr, + anjay_iid_t iid, + anjay_rid_t rid, + anjay_riid_t riid, + anjay_input_ctx_t *ctx) { + (void) anjay; + (void) riid; + assert(riid == ANJAY_ID_INVALID); + + server_repr_t *repr = _standalone_serv_get(obj_ptr); + server_instance_t *inst = find_instance(repr, iid); + assert(inst); + int retval; + + _standalone_serv_mark_modified(repr); + + switch ((server_rid_t) rid) { + case SERV_RES_SSID: + retval = _standalone_serv_fetch_ssid(ctx, &inst->ssid); + break; + case SERV_RES_LIFETIME: + retval = anjay_get_i32(ctx, &inst->lifetime); + break; + case SERV_RES_DEFAULT_MIN_PERIOD: + retval = + _standalone_serv_fetch_validated_i32(ctx, 0, INT32_MAX, + &inst->default_min_period); + break; + case SERV_RES_DEFAULT_MAX_PERIOD: + retval = + _standalone_serv_fetch_validated_i32(ctx, 1, INT32_MAX, + &inst->default_max_period); + break; +#ifndef ANJAY_WITHOUT_DEREGISTER + case SERV_RES_DISABLE_TIMEOUT: + retval = _standalone_serv_fetch_validated_i32(ctx, 0, INT32_MAX, + &inst->disable_timeout); + break; +#endif // ANJAY_WITHOUT_DEREGISTER + case SERV_RES_BINDING: + retval = _standalone_serv_fetch_binding(ctx, &inst->binding); + break; + case SERV_RES_NOTIFICATION_STORING_WHEN_DISABLED_OR_OFFLINE: + retval = anjay_get_bool(ctx, &inst->notification_storing); + break; +#ifdef ANJAY_WITH_LWM2M11 + case SERV_RES_TLS_DTLS_ALERT_CODE: { + uint32_t last_alert; + if (!(retval = anjay_get_u32(ctx, &last_alert))) { + inst->last_alert = (uint8_t) last_alert; + } + break; + } + case SERV_RES_LAST_BOOTSTRAPPED: + retval = anjay_get_i64(ctx, &inst->last_bootstrapped_timestamp); + break; +# ifdef ANJAY_WITH_BOOTSTRAP + case SERV_RES_BOOTSTRAP_ON_REGISTRATION_FAILURE: + retval = anjay_get_bool(ctx, &inst->bootstrap_on_registration_failure); + break; +# endif // ANJAY_WITH_BOOTSTRAP + case SERV_RES_SERVER_COMMUNICATION_RETRY_COUNT: + if (!(retval = anjay_get_u32(ctx, + &inst->server_communication_retry_count)) + && inst->server_communication_retry_count == 0) { + server_log(ERROR, "Server Communication Retry Count cannot be 0"); + retval = ANJAY_ERR_BAD_REQUEST; + } + break; + case SERV_RES_SERVER_COMMUNICATION_RETRY_TIMER: + retval = anjay_get_u32(ctx, &inst->server_communication_retry_timer); + break; + case SERV_RES_SERVER_COMMUNICATION_SEQUENCE_RETRY_COUNT: + if (!(retval = anjay_get_u32( + ctx, &inst->server_communication_sequence_retry_count)) + && inst->server_communication_sequence_retry_count == 0) { + server_log(ERROR, + "Server Sequence Communication Retry Count cannot be 0"); + retval = ANJAY_ERR_BAD_REQUEST; + } + break; + case SERV_RES_SERVER_COMMUNICATION_SEQUENCE_DELAY_TIMER: + retval = + anjay_get_u32(ctx, + &inst->server_communication_sequence_delay_timer); + break; +# ifdef ANJAY_WITH_SMS + case SERV_RES_TRIGGER: + retval = anjay_get_bool(ctx, &inst->trigger); + break; +# endif // ANJAY_WITH_SMS + case SERV_RES_PREFERRED_TRANSPORT: { + char tmp[2]; + if (!(retval = anjay_get_string(ctx, tmp, sizeof(tmp)))) { + inst->preferred_transport = tmp[0]; + } else if (retval == ANJAY_BUFFER_TOO_SHORT) { + retval = ANJAY_ERR_BAD_REQUEST; + } + break; + } +# ifdef ANJAY_WITH_SEND + case SERV_RES_MUTE_SEND: + retval = anjay_get_bool(ctx, &inst->mute_send); + break; +# endif // ANJAY_WITH_SEND +#endif // ANJAY_WITH_LWM2M11 + default: + AVS_UNREACHABLE( + "Write called on unknown or non-read/writable Server resource"); + return ANJAY_ERR_METHOD_NOT_ALLOWED; + } + + if (!retval) { + inst->present_resources[rid] = true; + } + + return retval; +} + +static int serv_execute(anjay_t *anjay, + const anjay_dm_object_def_t *const *obj_ptr, + anjay_iid_t iid, + anjay_rid_t rid, + anjay_execute_ctx_t *ctx) { + (void) anjay; + (void) obj_ptr; + (void) ctx; + server_instance_t *inst = find_instance(_standalone_serv_get(obj_ptr), iid); + assert(inst); + + switch ((server_rid_t) rid) { +#ifndef ANJAY_WITHOUT_DEREGISTER + case SERV_RES_DISABLE: { + avs_time_duration_t disable_timeout = avs_time_duration_from_scalar( + inst->present_resources[SERV_RES_DISABLE_TIMEOUT] + ? inst->disable_timeout + : 86400, + AVS_TIME_S); + return anjay_disable_server_with_timeout(anjay, inst->ssid, + disable_timeout); + } +#endif // ANJAY_WITHOUT_DEREGISTER + case SERV_RES_REGISTRATION_UPDATE_TRIGGER: + return anjay_schedule_registration_update(anjay, inst->ssid) + ? ANJAY_ERR_BAD_REQUEST + : 0; +#if defined(ANJAY_WITH_LWM2M11) && defined(ANJAY_WITH_BOOTSTRAP) + case SERV_RES_BOOTSTRAP_REQUEST_TRIGGER: + return anjay_schedule_bootstrap_request(anjay) + ? ANJAY_ERR_METHOD_NOT_ALLOWED + : 0; +#endif // defined(ANJAY_WITH_LWM2M11) && defined(ANJAY_WITH_BOOTSTRAP) + default: + AVS_UNREACHABLE( + "Execute called on unknown or non-executable Server resource"); + return ANJAY_ERR_METHOD_NOT_ALLOWED; + } +} + +static int serv_transaction_begin(anjay_t *anjay, + const anjay_dm_object_def_t *const *obj_ptr) { + (void) anjay; + return _standalone_serv_transaction_begin_impl( + _standalone_serv_get(obj_ptr)); +} + +static int +serv_transaction_commit(anjay_t *anjay, + const anjay_dm_object_def_t *const *obj_ptr) { + (void) anjay; + return _standalone_serv_transaction_commit_impl( + _standalone_serv_get(obj_ptr)); +} + +static int +serv_transaction_validate(anjay_t *anjay, + const anjay_dm_object_def_t *const *obj_ptr) { + (void) anjay; + return _standalone_serv_transaction_validate_impl( + _standalone_serv_get(obj_ptr)); +} + +static int +serv_transaction_rollback(anjay_t *anjay, + const anjay_dm_object_def_t *const *obj_ptr) { + (void) anjay; + return _standalone_serv_transaction_rollback_impl( + _standalone_serv_get(obj_ptr)); +} + +static const anjay_dm_object_def_t SERVER = { + .oid = ANJAY_DM_OID_SERVER, + .handlers = { + .list_instances = serv_list_instances, + .instance_create = serv_instance_create, + .instance_remove = serv_instance_remove, + .instance_reset = serv_instance_reset, + .list_resources = serv_list_resources, + .resource_read = serv_read, + .resource_write = serv_write, + .resource_execute = serv_execute, + .transaction_begin = serv_transaction_begin, + .transaction_validate = serv_transaction_validate, + .transaction_commit = serv_transaction_commit, + .transaction_rollback = serv_transaction_rollback + } +}; + +server_repr_t * +_standalone_serv_get(const anjay_dm_object_def_t *const *obj_ptr) { + assert(obj_ptr && *obj_ptr == &SERVER); + return AVS_CONTAINER_OF(obj_ptr, server_repr_t, def); +} + +int standalone_server_object_add_instance( + const anjay_dm_object_def_t *const *obj_ptr, + const standalone_server_instance_t *instance, + anjay_iid_t *inout_iid) { + int retval = -1; + server_repr_t *repr = _standalone_serv_get(obj_ptr); + if (!repr) { + server_log(ERROR, _("Server object is not registered")); + retval = -1; + } else { + const bool modified_since_persist = repr->modified_since_persist; + if (!(retval = add_instance(repr, instance, inout_iid)) + && (retval = _standalone_serv_object_validate(repr))) { + (void) del_instance(repr, *inout_iid); + if (!modified_since_persist) { + /* validation failed and so in the end no instace is added */ + _standalone_serv_clear_modified(repr); + } + } + + if (!retval) { + if (anjay_notify_instances_changed(repr->anjay, SERVER.oid)) { + server_log(WARNING, _("Could not schedule socket reload")); + } + } + } + return retval; +} + +static void server_purge(server_repr_t *repr) { + if (repr->instances) { + _standalone_serv_mark_modified(repr); + } + _standalone_serv_destroy_instances(&repr->instances); + _standalone_serv_destroy_instances(&repr->saved_instances); +} + +void standalone_server_object_cleanup( + const anjay_dm_object_def_t *const *obj_ptr) { + server_repr_t *repr = _standalone_serv_get(obj_ptr); + server_purge(repr); + avs_free(repr); +} + +void standalone_server_object_purge( + const anjay_dm_object_def_t *const *obj_ptr) { + server_repr_t *repr = _standalone_serv_get(obj_ptr); + + if (!repr) { + server_log(ERROR, _("Server object is not registered")); + } else { + server_purge(repr); + if (anjay_notify_instances_changed(repr->anjay, SERVER.oid)) { + server_log(WARNING, _("Could not schedule socket reload")); + } + } +} + +AVS_LIST(const anjay_ssid_t) +standalone_server_get_ssids(const anjay_dm_object_def_t *const *obj_ptr) { + AVS_LIST(server_instance_t) source = NULL; + server_repr_t *repr = _standalone_serv_get(obj_ptr); + if (repr->in_transaction) { + source = repr->saved_instances; + } else { + source = repr->instances; + } + // We rely on the fact that the "ssid" field is first in server_instance_t, + // which means that both "source" and "&source->ssid" point to exactly the + // same memory location. The "next" pointer location in AVS_LIST is + // independent from the stored data type, so it's safe to do such "cast". + AVS_STATIC_ASSERT(offsetof(server_instance_t, ssid) == 0, + instance_ssid_is_first_field); + return &source->ssid; +} + +bool standalone_server_object_is_modified( + const anjay_dm_object_def_t *const *obj_ptr) { + bool result = false; + server_repr_t *repr = _standalone_serv_get(obj_ptr); + if (!repr) { + server_log(ERROR, _("Server object is not registered")); + } else { + if (repr->in_transaction) { + result = repr->saved_modified_since_persist; + } else { + result = repr->modified_since_persist; + } + } + return result; +} + +const anjay_dm_object_def_t **standalone_server_object_install(anjay_t *anjay) { + assert(anjay); + server_repr_t *repr = + (server_repr_t *) avs_calloc(1, sizeof(server_repr_t)); + if (!repr) { + server_log(ERROR, _("out of memory")); + return NULL; + } + repr->def = &SERVER; + repr->anjay = anjay; + AVS_STATIC_ASSERT(offsetof(server_repr_t, def) == 0, def_is_first_field); + if (anjay_register_object(anjay, &repr->def)) { + avs_free(repr); + return NULL; + } + return &repr->def; +} + +int standalone_server_object_set_lifetime( + const anjay_dm_object_def_t *const *obj_ptr, + anjay_iid_t iid, + int32_t lifetime) { + if (lifetime <= 0) { + server_log(ERROR, _("lifetime MUST BE strictly positive")); + return -1; + } + int result = -1; + server_repr_t *repr = _standalone_serv_get(obj_ptr); + if (repr->saved_instances) { + server_log(ERROR, _("cannot set Lifetime while some transaction is " + "started on the Server Object")); + } else { + AVS_LIST(server_instance_t) it; + AVS_LIST_FOREACH(it, repr->instances) { + if (it->iid >= iid) { + break; + } + } + + if (!it || it->iid != iid) { + server_log(ERROR, _("instance ") "%" PRIu16 _(" not found"), iid); + } else if (it->lifetime != lifetime) { + if (anjay_notify_changed(repr->anjay, ANJAY_DM_OID_SERVER, it->iid, + SERV_RES_LIFETIME)) { + server_log(WARNING, _("could not notify lifetime change")); + } + repr->modified_since_persist = true; + it->lifetime = lifetime; + result = 0; + } + } + return result; +} diff --git a/standalone/server/standalone_mod_server.h b/standalone/server/standalone_mod_server.h new file mode 100644 index 00000000..ad76d5ff --- /dev/null +++ b/standalone/server/standalone_mod_server.h @@ -0,0 +1,99 @@ +#ifndef ANJAY_STANDALONE_SERVER_MOD_SERVER_H +#define ANJAY_STANDALONE_SERVER_MOD_SERVER_H + +#include + +#include "standalone_server.h" + +typedef struct { + char data[8]; +} standalone_binding_mode_t; + +typedef enum { + SERV_RES_SSID = 0, + SERV_RES_LIFETIME = 1, + SERV_RES_DEFAULT_MIN_PERIOD = 2, + SERV_RES_DEFAULT_MAX_PERIOD = 3, +#ifndef ANJAY_WITHOUT_DEREGISTER + SERV_RES_DISABLE = 4, + SERV_RES_DISABLE_TIMEOUT = 5, +#endif // ANJAY_WITHOUT_DEREGISTER + SERV_RES_NOTIFICATION_STORING_WHEN_DISABLED_OR_OFFLINE = 6, + SERV_RES_BINDING = 7, + SERV_RES_REGISTRATION_UPDATE_TRIGGER = 8, +#ifdef ANJAY_WITH_LWM2M11 + SERV_RES_BOOTSTRAP_REQUEST_TRIGGER = 9, + SERV_RES_TLS_DTLS_ALERT_CODE = 11, + SERV_RES_LAST_BOOTSTRAPPED = 12, + SERV_RES_BOOTSTRAP_ON_REGISTRATION_FAILURE = 16, + SERV_RES_SERVER_COMMUNICATION_RETRY_COUNT = 17, + SERV_RES_SERVER_COMMUNICATION_RETRY_TIMER = 18, + SERV_RES_SERVER_COMMUNICATION_SEQUENCE_DELAY_TIMER = 19, + SERV_RES_SERVER_COMMUNICATION_SEQUENCE_RETRY_COUNT = 20, +# ifdef ANJAY_WITH_SMS + SERV_RES_TRIGGER = 21, +# endif // ANJAY_WITH_SMS + SERV_RES_PREFERRED_TRANSPORT = 22, +# ifdef ANJAY_WITH_SEND + SERV_RES_MUTE_SEND = 23, +# endif // ANJAY_WITH_SEND +#endif // ANJAY_WITH_LWM2M11 + _SERV_RES_COUNT +} server_rid_t; + +typedef struct { + /* mandatory resources */ + anjay_ssid_t ssid; + standalone_binding_mode_t binding; + int32_t lifetime; + int32_t default_min_period; + int32_t default_max_period; +#ifndef ANJAY_WITHOUT_DEREGISTER + int32_t disable_timeout; +#endif // ANJAY_WITHOUT_DEREGISTER + bool notification_storing; + + anjay_iid_t iid; + +#ifdef ANJAY_WITH_LWM2M11 + int64_t last_bootstrapped_timestamp; + uint8_t last_alert; + bool bootstrap_on_registration_failure; + uint32_t server_communication_retry_count; + uint32_t server_communication_retry_timer; + uint32_t server_communication_sequence_retry_count; + uint32_t server_communication_sequence_delay_timer; +# ifdef ANJAY_WITH_SMS + bool trigger; +# endif // ANJAY_WITH_SMS + char preferred_transport; +# ifdef ANJAY_WITH_SEND + bool mute_send; +# endif // ANJAY_WITH_SEND +#endif // ANJAY_WITH_LWM2M11 + + bool present_resources[_SERV_RES_COUNT]; +} server_instance_t; + +typedef struct { + const anjay_dm_object_def_t *def; + anjay_t *anjay; + AVS_LIST(server_instance_t) instances; + AVS_LIST(server_instance_t) saved_instances; + bool modified_since_persist; + bool saved_modified_since_persist; + bool in_transaction; +} server_repr_t; + +static inline void _standalone_serv_mark_modified(server_repr_t *repr) { + repr->modified_since_persist = true; +} + +static inline void _standalone_serv_clear_modified(server_repr_t *repr) { + repr->modified_since_persist = false; +} + +#define server_log(level, ...) avs_log(server, level, __VA_ARGS__) +#define _(Arg) AVS_DISPOSABLE_LOG(Arg) + +#endif /* ANJAY_STANDALONE_SERVER_MOD_SERVER_H */ diff --git a/standalone/server/standalone_server.h b/standalone/server/standalone_server.h new file mode 100644 index 00000000..8cc5c949 --- /dev/null +++ b/standalone/server/standalone_server.h @@ -0,0 +1,190 @@ +#ifndef ANJAY_STANDALONE_ANJAY_SERVER_H +#define ANJAY_STANDALONE_ANJAY_SERVER_H + +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + /** Resource: Short Server ID */ + anjay_ssid_t ssid; + /** Resource: Lifetime */ + int32_t lifetime; + /** Resource: Default Minimum Period - or a negative value to disable + * presence */ + int32_t default_min_period; + /** Resource: Default Maximum Period - or a negative value to disable + * presence */ + int32_t default_max_period; + /** Resource: Disable Timeout - or a negative value to disable presence */ + int32_t disable_timeout; + /** Resource: Binding */ + const char *binding; + /** Resource: Notification Storing When Disabled or Offline */ + bool notification_storing; +#ifdef ANJAY_WITH_LWM2M11 + /** Resource: Bootstrap on Registration Failure. True if not set. */ + const bool *bootstrap_on_registration_failure; + /** Resource: Preferred Transport */ + char preferred_transport; + /** Resource: Mute Send */ + bool mute_send; + /** Resource: Communication Retry Count. NULL if not set. */ + const uint32_t *communication_retry_count; + /** Resource: Communication Retry Timer. NULL if not set. */ + const uint32_t *communication_retry_timer; + /** Resource: Communication Sequence Retry Count. NULL if not set. */ + const uint32_t *communication_sequence_retry_count; + /** Resource: Communication Sequence Delay Timer (in seconds). NULL if not + * set. */ + const uint32_t *communication_sequence_delay_timer; +# ifdef ANJAY_WITH_SMS + /** Resource: Trigger */ + const bool *trigger; +# endif // ANJAY_WITH_SMS +#endif // ANJAY_WITH_LWM2M11 +} standalone_server_instance_t; + +/** + * Adds new Instance of Server Object and returns newly created Instance id + * via @p inout_iid . + * + * Note: if @p *inout_iid is set to @ref ANJAY_ID_INVALID then the Instance id + * is generated automatically, otherwise value of @p *inout_iid is used as a + * new Server Instance Id. + * + * Note: @p instance may be safely freed by the user code after this function + * finishes (internally a deep copy of @ref standalone_server_instance_t is + * performed). + * + * @param obj_ptr Installed Server Object to operate on. + * @param instance Server Instance to insert. + * @param inout_iid Server Instance id to use or @ref ANJAY_ID_INVALID . + * + * @return 0 on success, negative value in case of an error or if the instance + * of specified id already exists. + */ +int standalone_server_object_add_instance( + const anjay_dm_object_def_t *const *obj_ptr, + const standalone_server_instance_t *instance, + anjay_iid_t *inout_iid); + +/** + * Removes all instances of Server Object leaving it in an empty state. + * + * @param obj_ptr Installed Server Object to operate on. + */ +void standalone_server_object_purge( + const anjay_dm_object_def_t *const *obj_ptr); + +/** + * Retrieves a list of SSIDs currently present in the Server object. The SSIDs + * are NOT guaranteed to be returned in any particular order. Returned list may + * not be freed nor modified. + * + * The returned list pointer shall be considered invalidated by any call to @ref + * anjay_sched_run, @ref anjay_serve, @ref + * standalone_server_object_add_instance, + * @ref standalone_server_object_purge, @ref standalone_server_object_restore, + * or, if called from within some callback handler, on return from that handler. + * + * If a transaction on the Server object is currently ongoing (e.g., during + * Bootstrap), last known state from before the transaction will be returned. + * + * @param obj_ptr Installed Server Object to operate on. + * + * @returns A list of known SSIDs on success, NULL when the object is empty. + */ +AVS_LIST(const anjay_ssid_t) +standalone_server_get_ssids(const anjay_dm_object_def_t *const *obj_ptr); + +/** + * Dumps Server Object Instances into the @p out_stream . + * + * @param obj_ptr Installed Server Object to operate on. + * @param out_stream Stream to write to. + * @return AVS_OK in case of success, or an error code. + */ +avs_error_t +standalone_server_object_persist(const anjay_dm_object_def_t *const *obj_ptr, + avs_stream_t *out_stream); + +/** + * Attempts to restore Server Object Instances from specified @p in_stream . + * + * Note: if restore fails, then Server Object will be left untouched, on + * success though all Instances stored within the Object will be purged. + * + * @param obj_ptr Installed Server Object to operate on. + * @param in_stream Stream to read from. + * @return AVS_OK in case of success, or an error code. + */ +avs_error_t +standalone_server_object_restore(const anjay_dm_object_def_t *const *obj_ptr, + avs_stream_t *in_stream); + +/** + * Checks whether the Server Object has been modified since + * last successful call to @ref standalone_server_object_persist or @ref + * standalone_server_object_restore. + */ +bool standalone_server_object_is_modified( + const anjay_dm_object_def_t *const *obj_ptr); + +/** + * Creates a Server Object and installs it in an Anjay instance using + * @ref anjay_register_object. + * + * Do NOT attempt to call @ref anjay_register_object with this object manually, + * and do NOT try to use the same instance of the Server object with another + * Anjay instance. + * + * @param anjay Anjay instance for which the Server Object is installed. + * + * @returns Handle to the created object that can be passed to other functions + * declared in this file, or NULL in case of error. + */ +const anjay_dm_object_def_t **standalone_server_object_install(anjay_t *anjay); + +/** + * Frees all system resources allocated by the Server Object. + * + * NOTE: Attempting to call this function before deregistering + * the object using @ref anjay_unregister_object, @ref anjay_delete or + * @ref anjay_delete_with_core_persistence is undefined behavior. + * + * @param obj_ptr Server Object to operate on. + */ +void standalone_server_object_cleanup( + const anjay_dm_object_def_t *const *obj_ptr); + +/** + * Sets the Lifetime value for the specified Server Instance ID. + * + * NOTE: Calling this function MAY trigger sending LwM2M Update message to + * an associated LwM2M Server. + * + * @param obj_ptr Installed Server Object to operate on. + * @param iid Server Object Instance for which the Lifetime shall be + * altered. + * @param lifetime New value of the Lifetime Resource. MUST BE strictly + * positive. + * + * @returns 0 on success, negative value in case of an error. If an error + * is returned, the Lifetime value remains unchanged. + */ +int standalone_server_object_set_lifetime( + const anjay_dm_object_def_t *const *obj_ptr, + anjay_iid_t iid, + int32_t lifetime); + +#ifdef __cplusplus +} +#endif + +#endif /* ANJAY_STANDALONE_ANJAY_SERVER_H */ diff --git a/standalone/server/standalone_server_persistence.c b/standalone/server/standalone_server_persistence.c new file mode 100644 index 00000000..d63c2b74 --- /dev/null +++ b/standalone/server/standalone_server_persistence.c @@ -0,0 +1,521 @@ +#include +#include + +#include "standalone_server_transaction.h" +#include "standalone_server_utils.h" + +#ifdef AVS_COMMONS_WITH_AVS_PERSISTENCE +# include +#endif // AVS_COMMONS_WITH_AVS_PERSISTENCE +#include + +#define persistence_log(level, ...) \ + avs_log(server_persistence, level, __VA_ARGS__) + +#ifdef AVS_COMMONS_WITH_AVS_PERSISTENCE + +typedef enum { + PERSISTENCE_VERSION_0, + + /** Binding resource as string instead of enum */ + PERSISTENCE_VERSION_1, + + /** + * New resources: + * - 11: TLS-DTLS Alert Code + * - 12: Last Bootstrapped + * - 16: Bootstrap on Registration Failure + * - 17: Communication Retry Count + * - 18: Communication Retry Timer + * - 19: Communication Sequence Delay Timer + * - 20: Communication Sequence Retry Count + * - 22: Preferred Transport + * - 23: Mute Send + */ + PERSISTENCE_VERSION_2, + + /** + * New resource: Trigger + */ + PERSISTENCE_VERSION_3 +} server_persistence_version_t; + +typedef char magic_t[4]; +static const magic_t MAGIC_V0 = { 'S', 'R', 'V', PERSISTENCE_VERSION_0 }; +static const magic_t MAGIC_V1 = { 'S', 'R', 'V', PERSISTENCE_VERSION_1 }; +static const magic_t MAGIC_V2 = { 'S', 'R', 'V', PERSISTENCE_VERSION_2 }; +static const magic_t MAGIC_V3 = { 'S', 'R', 'V', PERSISTENCE_VERSION_3 }; + +static avs_error_t handle_v0_v1_sized_fields(avs_persistence_context_t *ctx, + server_instance_t *element) { + assert(avs_persistence_direction(ctx) == AVS_PERSISTENCE_RESTORE); + avs_error_t err; + (void) (avs_is_err((err = avs_persistence_u16(ctx, &element->iid))) + || avs_is_err((err = avs_persistence_bool( + ctx, + &element->present_resources[SERV_RES_SSID]))) + || avs_is_err( + (err = avs_persistence_bool( + ctx, + &element->present_resources[SERV_RES_BINDING]))) + || avs_is_err(( + err = avs_persistence_bool( + ctx, + &element->present_resources[SERV_RES_LIFETIME]))) + || avs_is_err(( + err = avs_persistence_bool( + ctx, + &element->present_resources + [SERV_RES_NOTIFICATION_STORING_WHEN_DISABLED_OR_OFFLINE]))) + || avs_is_err((err = avs_persistence_u16(ctx, &element->ssid))) + || avs_is_err((err = avs_persistence_u32( + ctx, (uint32_t *) &element->lifetime))) + || avs_is_err(( + err = avs_persistence_u32( + ctx, (uint32_t *) &element->default_min_period))) + || avs_is_err(( + err = avs_persistence_u32( + ctx, (uint32_t *) &element->default_max_period))) + || avs_is_err((err = avs_persistence_u32( + ctx, +# ifndef ANJAY_WITHOUT_DEREGISTER + (uint32_t *) &element->disable_timeout +# else // ANJAY_WITHOUT_DEREGISTER + (uint32_t *) &(int32_t) { -1 } +# endif // ANJAY_WITHOUT_DEREGISTER + ))) + || avs_is_err((err = avs_persistence_bool( + ctx, &element->notification_storing)))); + if (avs_is_ok(err)) { + element->present_resources[SERV_RES_DEFAULT_MIN_PERIOD] = + (element->default_min_period >= 0); + element->present_resources[SERV_RES_DEFAULT_MAX_PERIOD] = + (element->default_max_period >= 0); +# ifndef ANJAY_WITHOUT_DEREGISTER + element->present_resources[SERV_RES_DISABLE_TIMEOUT] = + (element->disable_timeout >= 0); +# endif // ANJAY_WITHOUT_DEREGISTER + element->present_resources + [SERV_RES_NOTIFICATION_STORING_WHEN_DISABLED_OR_OFFLINE] = true; + } + return err; +} + +static avs_error_t +handle_v2_lwm2m11_sized_fields(avs_persistence_context_t *ctx, + server_instance_t *element) { +# ifndef ANJAY_WITH_LWM2M11 + enum { + SERV_RES_TLS_DTLS_ALERT_CODE, + SERV_RES_LAST_BOOTSTRAPPED, + SERV_RES_SERVER_COMMUNICATION_RETRY_COUNT, + SERV_RES_SERVER_COMMUNICATION_RETRY_TIMER, + SERV_RES_SERVER_COMMUNICATION_SEQUENCE_RETRY_COUNT, + SERV_RES_SERVER_COMMUNICATION_SEQUENCE_DELAY_TIMER, + SERV_RES_PREFERRED_TRANSPORT, + _FAKE_RESOURCES_NUM + }; + + (void) element; + struct { + int64_t last_bootstrapped_timestamp; + uint8_t last_alert; + bool bootstrap_on_registration_failure; + uint32_t server_communication_retry_count; + uint32_t server_communication_retry_timer; + uint32_t server_communication_sequence_retry_count; + uint32_t server_communication_sequence_delay_timer; + char preferred_transport; + bool mute_send; + + bool present_resources[_FAKE_RESOURCES_NUM]; + } dummy_element = { + .bootstrap_on_registration_failure = true + }; +# define element (&dummy_element) +# endif // ANJAY_WITH_LWM2M11 + + avs_error_t err; + (void) (avs_is_err((err = avs_persistence_bool( + ctx, + &element->present_resources + [SERV_RES_TLS_DTLS_ALERT_CODE]))) + || avs_is_err((err = avs_persistence_u8(ctx, &element->last_alert))) + || avs_is_err((err = avs_persistence_bool( + ctx, + &element->present_resources + [SERV_RES_LAST_BOOTSTRAPPED]))) + || avs_is_err((err = avs_persistence_i64( + ctx, &element->last_bootstrapped_timestamp))) + || avs_is_err( + (err = avs_persistence_bool( + ctx, + &element->bootstrap_on_registration_failure))) + || avs_is_err(( + err = avs_persistence_bool( + ctx, + &element->present_resources + [SERV_RES_SERVER_COMMUNICATION_RETRY_COUNT]))) + || avs_is_err((err = avs_persistence_u32( + ctx, + &element->server_communication_retry_count))) + || avs_is_err(( + err = avs_persistence_bool( + ctx, + &element->present_resources + [SERV_RES_SERVER_COMMUNICATION_RETRY_TIMER]))) + || avs_is_err((err = avs_persistence_u32( + ctx, + + &element->server_communication_retry_timer))) + || avs_is_err(( + err = avs_persistence_bool( + ctx, + &element->present_resources + [SERV_RES_SERVER_COMMUNICATION_SEQUENCE_DELAY_TIMER]))) + || avs_is_err(( + err = avs_persistence_u32( + ctx, + &element->server_communication_sequence_delay_timer))) + || avs_is_err(( + err = avs_persistence_bool( + ctx, + &element->present_resources + [SERV_RES_SERVER_COMMUNICATION_SEQUENCE_RETRY_COUNT]))) + || avs_is_err(( + err = avs_persistence_u32( + ctx, + &element->server_communication_sequence_retry_count))) + || avs_is_err(( + err = avs_persistence_u8( + ctx, (uint8_t *) &element->preferred_transport))) + || avs_is_err((err = avs_persistence_bool(ctx, +# ifdef ANJAY_WITH_SEND + &element->mute_send +# else // ANJAY_WITH_SEND + &(bool) { false } +# endif // ANJAY_WITH_SEND + )))); + if (avs_is_ok(err)) { + element->present_resources[SERV_RES_PREFERRED_TRANSPORT] = + !!element->preferred_transport; + } + return err; +# undef element +} + +static avs_error_t handle_v2_sized_fields(avs_persistence_context_t *ctx, + server_instance_t *element) { + avs_error_t err; + (void) (avs_is_err((err = avs_persistence_u16(ctx, &element->iid))) + || avs_is_err((err = avs_persistence_bool( + ctx, + &element->present_resources[SERV_RES_SSID]))) + || avs_is_err( + (err = avs_persistence_bool( + ctx, + &element->present_resources[SERV_RES_BINDING]))) + || avs_is_err(( + err = avs_persistence_bool( + ctx, + &element->present_resources[SERV_RES_LIFETIME]))) + || avs_is_err((err = avs_persistence_bool( + ctx, + &element->present_resources + [SERV_RES_DEFAULT_MIN_PERIOD]))) + || avs_is_err((err = avs_persistence_bool( + ctx, + &element->present_resources + [SERV_RES_DEFAULT_MAX_PERIOD]))) + || avs_is_err((err = avs_persistence_bool( + ctx, +# ifndef ANJAY_WITHOUT_DEREGISTER + &element->present_resources + [SERV_RES_DISABLE_TIMEOUT] +# else // ANJAY_WITHOUT_DEREGISTER + &(bool) { false } +# endif // ANJAY_WITHOUT_DEREGISTER + ))) + || avs_is_err(( + err = avs_persistence_bool( + ctx, + &element->present_resources + [SERV_RES_NOTIFICATION_STORING_WHEN_DISABLED_OR_OFFLINE]))) + || avs_is_err((err = avs_persistence_u16(ctx, &element->ssid))) + || avs_is_err((err = avs_persistence_u32( + ctx, (uint32_t *) &element->lifetime))) + || avs_is_err(( + err = avs_persistence_u32( + ctx, (uint32_t *) &element->default_min_period))) + || avs_is_err(( + err = avs_persistence_u32( + ctx, (uint32_t *) &element->default_max_period))) + || avs_is_err((err = avs_persistence_u32( + ctx, +# ifndef ANJAY_WITHOUT_DEREGISTER + (uint32_t *) &element->disable_timeout +# else // ANJAY_WITHOUT_DEREGISTER + (uint32_t *) &(int32_t) { -1 } +# endif // ANJAY_WITHOUT_DEREGISTER + ))) + || avs_is_err((err = avs_persistence_bool( + ctx, &element->notification_storing))) + || avs_is_err( + (err = handle_v2_lwm2m11_sized_fields(ctx, element)))); + return err; +} + +static avs_error_t handle_v3_sized_fields(avs_persistence_context_t *ctx, + server_instance_t *element) { + avs_error_t err; + (void) (avs_is_err((err = handle_v2_sized_fields(ctx, element))) + || avs_is_err((err = avs_persistence_bool( + ctx, +# if defined(ANJAY_WITH_LWM2M11) && defined(ANJAY_WITH_SMS) + &element->present_resources[SERV_RES_TRIGGER] +# else // defined(ANJAY_WITH_LWM2M11) && defined(ANJAY_WITH_SMS) + &(bool) { false } +# endif // defined(ANJAY_WITH_LWM2M11) && defined(ANJAY_WITH_SMS) + ))) + || avs_is_err((err = avs_persistence_bool(ctx, +# if defined(ANJAY_WITH_LWM2M11) && defined(ANJAY_WITH_SMS) + &element->trigger +# else // defined(ANJAY_WITH_LWM2M11) && defined(ANJAY_WITH_SMS) + &(bool) { false } +# endif // defined(ANJAY_WITH_LWM2M11) && defined(ANJAY_WITH_SMS) + )))); + return err; +} + +static avs_error_t handle_v1_v2_v3_binding_mode(avs_persistence_context_t *ctx, + server_instance_t *element) { + avs_error_t err = avs_persistence_bytes(ctx, element->binding.data, + sizeof(element->binding.data)); + if (avs_is_err(err)) { + return err; + } + if (!memchr(element->binding.data, '\0', sizeof(element->binding.data)) + || !anjay_binding_mode_valid(element->binding.data)) { + return avs_errno(AVS_EBADMSG); + } + return AVS_OK; +} + +static avs_error_t restore_v0_binding_mode(avs_persistence_context_t *ctx, + server_instance_t *element) { + assert(avs_persistence_direction(ctx) == AVS_PERSISTENCE_RESTORE); + uint32_t binding; + avs_error_t err = avs_persistence_u32(ctx, &binding); + if (avs_is_err(err)) { + return err; + } + + enum { + V0_BINDING_NONE, + V0_BINDING_U, + V0_BINDING_UQ, + V0_BINDING_S, + V0_BINDING_SQ, + V0_BINDING_US, + V0_BINDING_UQS + }; + + const char *binding_str = ""; + switch (binding) { + case V0_BINDING_NONE: + binding_str = ""; + break; + case V0_BINDING_U: + binding_str = "U"; + break; + case V0_BINDING_UQ: + binding_str = "UQ"; + break; + case V0_BINDING_S: + binding_str = "S"; + break; + case V0_BINDING_SQ: + binding_str = "SQ"; + break; + case V0_BINDING_US: + binding_str = "US"; + break; + case V0_BINDING_UQS: + binding_str = "UQS"; + break; + default: + persistence_log(WARNING, _("Invalid binding mode: ") "%" PRIu32, + binding); + err = avs_errno(AVS_EBADMSG); + break; + } + if (avs_is_ok(err) + && avs_simple_snprintf(element->binding.data, + sizeof(element->binding.data), "%s", + binding_str) + < 0) { + persistence_log(WARNING, _("Could not restore binding: ") "%s", + binding_str); + err = avs_errno(AVS_EBADMSG); + } + return err; +} + +static avs_error_t server_instance_persistence_handler( + avs_persistence_context_t *ctx, void *element_, void *version_) { + server_instance_t *element = (server_instance_t *) element_; + server_persistence_version_t *version = + (server_persistence_version_t *) version_; + AVS_ASSERT(avs_persistence_direction(ctx) != AVS_PERSISTENCE_STORE + || *version == PERSISTENCE_VERSION_3, + "persistence storing is impossible in legacy mode"); + + // Ensure every field initialized regardless of persistence version + if (avs_persistence_direction(ctx) == AVS_PERSISTENCE_RESTORE) { + _standalone_serv_reset_instance(element); + } + + avs_error_t err; + switch (*version) { + case PERSISTENCE_VERSION_0: + (void) (avs_is_err((err = handle_v0_v1_sized_fields(ctx, element))) + || avs_is_err((err = restore_v0_binding_mode(ctx, element)))); + break; + case PERSISTENCE_VERSION_1: + (void) (avs_is_err((err = handle_v0_v1_sized_fields(ctx, element))) + || avs_is_err( + (err = handle_v1_v2_v3_binding_mode(ctx, element)))); + break; + case PERSISTENCE_VERSION_2: + (void) (avs_is_err((err = handle_v2_sized_fields(ctx, element))) + || avs_is_err( + (err = handle_v1_v2_v3_binding_mode(ctx, element)))); + break; + case PERSISTENCE_VERSION_3: + (void) (avs_is_err((err = handle_v3_sized_fields(ctx, element))) + || avs_is_err( + (err = handle_v1_v2_v3_binding_mode(ctx, element)))); + break; + default: + AVS_UNREACHABLE("invalid enum value"); + } + return err; +} + +avs_error_t +standalone_server_object_persist(const anjay_dm_object_def_t *const *obj_ptr, + avs_stream_t *out_stream) { + avs_error_t err = avs_errno(AVS_EINVAL); + server_repr_t *repr = _standalone_serv_get(obj_ptr); + if (!repr) { + err = avs_errno(AVS_EBADF); + } else { + avs_persistence_context_t persist_ctx = + avs_persistence_store_context_create(out_stream); + if (avs_is_ok((err = avs_persistence_bytes(&persist_ctx, + (void *) (intptr_t) MAGIC_V3, + sizeof(MAGIC_V3))))) { + server_persistence_version_t persistence_version = + PERSISTENCE_VERSION_3; + err = avs_persistence_list( + &persist_ctx, + (AVS_LIST(void) *) (repr->in_transaction + ? &repr->saved_instances + : &repr->instances), + sizeof(server_instance_t), + server_instance_persistence_handler, &persistence_version, + NULL); + if (avs_is_ok(err)) { + _standalone_serv_clear_modified(repr); + persistence_log(INFO, _("Server Object state persisted")); + } + } + } + return err; +} + +static int check_magic_header(magic_t magic_header, + server_persistence_version_t *out_version) { + if (!memcmp(magic_header, MAGIC_V0, sizeof(magic_t))) { + *out_version = PERSISTENCE_VERSION_0; + return 0; + } + if (!memcmp(magic_header, MAGIC_V1, sizeof(magic_t))) { + *out_version = PERSISTENCE_VERSION_1; + return 0; + } + if (!memcmp(magic_header, MAGIC_V2, sizeof(magic_t))) { + *out_version = PERSISTENCE_VERSION_2; + return 0; + } + if (!memcmp(magic_header, MAGIC_V3, sizeof(magic_t))) { + *out_version = PERSISTENCE_VERSION_3; + return 0; + } + return -1; +} + +avs_error_t +standalone_server_object_restore(const anjay_dm_object_def_t *const *obj_ptr, + avs_stream_t *in_stream) { + avs_error_t err = avs_errno(AVS_EINVAL); + server_repr_t *repr = _standalone_serv_get(obj_ptr); + if (!repr || repr->in_transaction) { + err = avs_errno(AVS_EBADF); + } else { + server_repr_t backup = *repr; + avs_persistence_context_t restore_ctx = + avs_persistence_restore_context_create(in_stream); + + magic_t magic_header; + if (avs_is_err((err = avs_persistence_bytes(&restore_ctx, magic_header, + sizeof(magic_header))))) { + persistence_log(WARNING, _("Could not read Server Object header")); + } else { + server_persistence_version_t persistence_version; + if (check_magic_header(magic_header, &persistence_version)) { + persistence_log(WARNING, _("Header magic constant mismatch")); + err = avs_errno(AVS_EBADMSG); + } else { + repr->instances = NULL; + err = avs_persistence_list(&restore_ctx, + (AVS_LIST(void) *) &repr->instances, + sizeof(server_instance_t), + server_instance_persistence_handler, + &persistence_version, NULL); + if (avs_is_ok(err) && _standalone_serv_object_validate(repr)) { + err = avs_errno(AVS_EBADMSG); + } + if (avs_is_err(err)) { + _standalone_serv_destroy_instances(&repr->instances); + repr->instances = backup.instances; + } else { + _standalone_serv_destroy_instances(&backup.instances); + _standalone_serv_clear_modified(repr); + persistence_log(INFO, _("Server Object state restored")); + } + } + } + } + return err; +} + +#else // AVS_COMMONS_WITH_AVS_PERSISTENCE + +avs_error_t standalone_server_object_persist(anjay_t *anjay, + avs_stream_t *out_stream) { + (void) anjay; + (void) out_stream; + persistence_log(ERROR, _("Persistence not compiled in")); + return avs_errno(AVS_ENOTSUP); +} + +avs_error_t standalone_server_object_restore(anjay_t *anjay, + avs_stream_t *in_stream) { + (void) anjay; + (void) in_stream; + persistence_log(ERROR, _("Persistence not compiled in")); + return avs_errno(AVS_ENOTSUP); +} + +#endif // AVS_COMMONS_WITH_AVS_PERSISTENCE diff --git a/standalone/server/standalone_server_transaction.c b/standalone/server/standalone_server_transaction.c new file mode 100644 index 00000000..bcaf19f5 --- /dev/null +++ b/standalone/server/standalone_server_transaction.c @@ -0,0 +1,179 @@ +#include +#include +#include + +#include "standalone_server_transaction.h" +#include "standalone_server_utils.h" + +static int ssid_cmp(const void *a, const void *b, size_t size) { + assert(size == sizeof(anjay_ssid_t)); + (void) size; + return *((const anjay_ssid_t *) a) - *((const anjay_ssid_t *) b); +} + +#define LOG_VALIDATION_FAILED(ServInstance, ...) \ + server_log(WARNING, "/%u/%u: " AVS_VARARG0(__VA_ARGS__), \ + ANJAY_DM_OID_SERVER, \ + (unsigned) (ServInstance)->iid AVS_VARARG_REST(__VA_ARGS__)) + +static int validate_instance(server_instance_t *it) { + if (!it->present_resources[SERV_RES_SSID]) { + LOG_VALIDATION_FAILED( + it, _("missing mandatory 'Short Server ID' resource value")); + return -1; + } + if (it->ssid < 1 || it->ssid >= UINT16_MAX) { + LOG_VALIDATION_FAILED( + it, _("invalid 'Short Server ID' resource value: ") "%" PRIu16, + it->ssid); + return -1; + } + if (!it->present_resources[SERV_RES_BINDING]) { + LOG_VALIDATION_FAILED(it, + _("missing mandatory 'Binding' resource value")); + return -1; + } + if (!it->present_resources[SERV_RES_LIFETIME]) { + LOG_VALIDATION_FAILED(it, + _("missing mandatory 'Lifetime' resource value")); + return -1; + } + if (!it->present_resources + [SERV_RES_NOTIFICATION_STORING_WHEN_DISABLED_OR_OFFLINE]) { + LOG_VALIDATION_FAILED(it, + _("missing mandatory 'Notification Storing " + "when disabled or offline' resource value")); + return -1; + } + + if (it->lifetime <= 0) { + LOG_VALIDATION_FAILED(it, + _("Lifetime value is non-positive: ") "%" PRId32, + it->lifetime); + return -1; + } + if (it->present_resources[SERV_RES_DEFAULT_MAX_PERIOD] + && it->default_max_period <= 0) { + LOG_VALIDATION_FAILED(it, _("Default Max Period is non-positive")); + return -1; + } + if (it->present_resources[SERV_RES_DEFAULT_MIN_PERIOD] + && it->default_min_period < 0) { + LOG_VALIDATION_FAILED(it, _("Default Min Period is negative")); + return -1; + } +#ifndef ANJAY_WITHOUT_DEREGISTER + if (it->present_resources[SERV_RES_DISABLE_TIMEOUT] + && it->disable_timeout < 0) { + LOG_VALIDATION_FAILED(it, _("Disable Timeout is negative")); + return -1; + } +#endif // ANJAY_WITHOUT_DEREGISTER + if (!anjay_binding_mode_valid(it->binding.data)) { + LOG_VALIDATION_FAILED(it, _("Incorrect binding mode ") "%s", + it->binding.data); + return -1; + } +#ifdef ANJAY_WITH_LWM2M11 + if (it->present_resources[SERV_RES_LAST_BOOTSTRAPPED] + && it->last_bootstrapped_timestamp < 0) { + LOG_VALIDATION_FAILED(it, _("Last Bootstrapped is negative")); + return -1; + } + if (it->present_resources[SERV_RES_PREFERRED_TRANSPORT] + && !strchr("USTN", it->preferred_transport)) { + LOG_VALIDATION_FAILED(it, _("Incorrect Preferred Transport: ") "%c", + it->preferred_transport); + return -1; + } + if (it->present_resources[SERV_RES_SERVER_COMMUNICATION_RETRY_COUNT] + && it->server_communication_retry_count == 0) { + LOG_VALIDATION_FAILED(it, + _("Communication Retry Count cannot be zero")); + return -1; + } + if (it->present_resources + [SERV_RES_SERVER_COMMUNICATION_SEQUENCE_RETRY_COUNT] + && it->server_communication_sequence_retry_count == 0) { + LOG_VALIDATION_FAILED( + it, _("Communication Sequence Retry Count cannot be zero")); + return -1; + } +#endif // ANJAY_WITH_LWM2M11 + + return 0; +} + +int _standalone_serv_object_validate(server_repr_t *repr) { + if (!repr->instances) { + return 0; + } + int result = 0; + + AVS_LIST(anjay_ssid_t) seen_ssids = NULL; + AVS_LIST(server_instance_t) it; + AVS_LIST_FOREACH(it, repr->instances) { + if (validate_instance(it)) { + result = ANJAY_ERR_BAD_REQUEST; + break; + } + + if (!AVS_LIST_INSERT_NEW(anjay_ssid_t, &seen_ssids)) { + result = ANJAY_ERR_INTERNAL; + break; + } + *seen_ssids = it->ssid; + } + + /* Test for SSID duplication */ + if (!result) { + AVS_LIST_SORT(&seen_ssids, ssid_cmp); + AVS_LIST(anjay_ssid_t) prev = seen_ssids; + AVS_LIST(anjay_ssid_t) next = AVS_LIST_NEXT(seen_ssids); + while (next) { + if (*prev == *next) { + /* Duplicate found */ + result = ANJAY_ERR_BAD_REQUEST; + break; + } + prev = next; + next = AVS_LIST_NEXT(next); + } + } + AVS_LIST_CLEAR(&seen_ssids); + return result; +} + +int _standalone_serv_transaction_begin_impl(server_repr_t *repr) { + assert(!repr->saved_instances); + assert(!repr->in_transaction); + repr->saved_instances = _standalone_serv_clone_instances(repr); + if (!repr->saved_instances && repr->instances) { + return ANJAY_ERR_INTERNAL; + } + repr->saved_modified_since_persist = repr->modified_since_persist; + repr->in_transaction = true; + return 0; +} + +int _standalone_serv_transaction_commit_impl(server_repr_t *repr) { + assert(repr->in_transaction); + _standalone_serv_destroy_instances(&repr->saved_instances); + repr->in_transaction = false; + return 0; +} + +int _standalone_serv_transaction_validate_impl(server_repr_t *repr) { + assert(repr->in_transaction); + return _standalone_serv_object_validate(repr); +} + +int _standalone_serv_transaction_rollback_impl(server_repr_t *repr) { + assert(repr->in_transaction); + _standalone_serv_destroy_instances(&repr->instances); + repr->modified_since_persist = repr->saved_modified_since_persist; + repr->instances = repr->saved_instances; + repr->saved_instances = NULL; + repr->in_transaction = false; + return 0; +} diff --git a/standalone/server/standalone_server_transaction.h b/standalone/server/standalone_server_transaction.h new file mode 100644 index 00000000..53cdfef8 --- /dev/null +++ b/standalone/server/standalone_server_transaction.h @@ -0,0 +1,13 @@ +#ifndef ANJAY_STANDALONE_SERVER_TRANSACTION_H +#define ANJAY_STANDALONE_SERVER_TRANSACTION_H + +#include "standalone_mod_server.h" + +int _standalone_serv_object_validate(server_repr_t *repr); + +int _standalone_serv_transaction_begin_impl(server_repr_t *repr); +int _standalone_serv_transaction_commit_impl(server_repr_t *repr); +int _standalone_serv_transaction_validate_impl(server_repr_t *repr); +int _standalone_serv_transaction_rollback_impl(server_repr_t *repr); + +#endif /* ANJAY_STANDALONE_SERVER_TRANSACTION_H */ diff --git a/standalone/server/standalone_server_utils.c b/standalone/server/standalone_server_utils.c new file mode 100644 index 00000000..1ba07c38 --- /dev/null +++ b/standalone/server/standalone_server_utils.c @@ -0,0 +1,67 @@ +#include "standalone_server_utils.h" + +#include + +int _standalone_serv_fetch_ssid(anjay_input_ctx_t *ctx, + anjay_ssid_t *out_ssid) { + int32_t ssid; + int retval = anjay_get_i32(ctx, &ssid); + if (retval) { + return retval; + } + *out_ssid = (anjay_ssid_t) ssid; + return 0; +} + +int _standalone_serv_fetch_validated_i32(anjay_input_ctx_t *ctx, + int32_t min_value, + int32_t max_value, + int32_t *out_value) { + int retval = anjay_get_i32(ctx, out_value); + if (!retval && (*out_value < min_value || *out_value > max_value)) { + return ANJAY_ERR_BAD_REQUEST; + } + return retval; +} + +int _standalone_serv_fetch_binding(anjay_input_ctx_t *ctx, + standalone_binding_mode_t *out_binding) { + int retval; + if ((retval = anjay_get_string( + ctx, out_binding->data, sizeof(out_binding->data)))) { + return retval; + } + return anjay_binding_mode_valid(out_binding->data) ? 0 + : ANJAY_ERR_BAD_REQUEST; +} + +AVS_LIST(server_instance_t) +_standalone_serv_clone_instances(const server_repr_t *repr) { + return AVS_LIST_SIMPLE_CLONE(repr->instances); +} + +void _standalone_serv_destroy_instances( + AVS_LIST(server_instance_t) *instances) { + AVS_LIST_CLEAR(instances); +} + +void _standalone_serv_reset_instance(server_instance_t *serv) { + const anjay_iid_t iid = serv->iid; + memset(serv, 0, sizeof(*serv)); + /* This is not a resource, therefore must be restored */ + serv->iid = iid; + serv->present_resources[SERV_RES_REGISTRATION_UPDATE_TRIGGER] = true; +#ifndef ANJAY_WITHOUT_DEREGISTER + serv->present_resources[SERV_RES_DISABLE] = true; +#endif // ANJAY_WITHOUT_DEREGISTER +#ifdef ANJAY_WITH_LWM2M11 + serv->bootstrap_on_registration_failure = true; + serv->present_resources[SERV_RES_BOOTSTRAP_ON_REGISTRATION_FAILURE] = true; +# ifdef ANJAY_WITH_BOOTSTRAP + serv->present_resources[SERV_RES_BOOTSTRAP_REQUEST_TRIGGER] = true; +# endif // ANJAY_WITH_BOOTSTRAP +# ifdef ANJAY_WITH_SEND + serv->present_resources[SERV_RES_MUTE_SEND] = true; +# endif // ANJAY_WITH_SEND +#endif // ANJAY_WITH_LWM2M11 +} diff --git a/standalone/server/standalone_server_utils.h b/standalone/server/standalone_server_utils.h new file mode 100644 index 00000000..70dc34c9 --- /dev/null +++ b/standalone/server/standalone_server_utils.h @@ -0,0 +1,24 @@ +#ifndef ANJAY_STANDALONE_SERVER_UTILS_H +#define ANJAY_STANDALONE_SERVER_UTILS_H + +#include + +#include "standalone_mod_server.h" + +server_repr_t * +_standalone_serv_get(const anjay_dm_object_def_t *const *obj_ptr); + +int _standalone_serv_fetch_ssid(anjay_input_ctx_t *ctx, anjay_ssid_t *out_ssid); +int _standalone_serv_fetch_validated_i32(anjay_input_ctx_t *ctx, + int32_t min_value, + int32_t max_value, + int32_t *out_value); +int _standalone_serv_fetch_binding(anjay_input_ctx_t *ctx, + standalone_binding_mode_t *out_binding); + +AVS_LIST(server_instance_t) +_standalone_serv_clone_instances(const server_repr_t *repr); +void _standalone_serv_destroy_instances(AVS_LIST(server_instance_t) *instances); +void _standalone_serv_reset_instance(server_instance_t *serv); + +#endif /* ANJAY_STANDALONE_SERVER_UTILS_H */ diff --git a/tests/core/downloader/downloader.c b/tests/core/downloader/downloader.c index 6cf59f01..d3b53980 100644 --- a/tests/core/downloader/downloader.c +++ b/tests/core/downloader/downloader.c @@ -347,21 +347,29 @@ AVS_UNIT_TEST(downloader, cannot_download_without_handlers) { "Despair is when you're debugging a kernel driver and you look " \ "at a memory dump and you see that a pointer has a value of 7." -AVS_UNIT_TEST(downloader, coap_download_single_block) { - setup_simple("coap://127.0.0.1:5683"); - - // expect packets +static void expect_download_single_block(avs_net_socket_t *socket, + void *dummy) { + assert(socket == SIMPLE_ENV.mocksock); + (void) dummy; const coap_test_msg_t *req = COAP_MSG(CON, GET, ID_TOKEN_RAW(0, nth_token(0)), NO_PAYLOAD); const coap_test_msg_t *res = COAP_MSG(ACK, CONTENT, ID_TOKEN_RAW(0, nth_token(0)), BLOCK2(0, 128, DESPAIR)); - avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, "127.0.0.1", "5683"); avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req->content, req->length); avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &res->content, res->length); expect_has_buffered_data_check(SIMPLE_ENV.mocksock, false); +} + +AVS_UNIT_TEST(downloader, coap_download_single_block) { + setup_simple("coap://127.0.0.1:5683"); + + avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock); + avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock); + avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, "127.0.0.1", "5683", + .and_then = expect_download_single_block); // expect handler calls expect_next_block(&SIMPLE_ENV.data, @@ -378,13 +386,12 @@ AVS_UNIT_TEST(downloader, coap_download_single_block) { teardown_simple(); } -AVS_UNIT_TEST(downloader, coap_download_multiple_blocks) { - static const size_t BLOCK_SIZE = 16; - - setup_simple("coap://127.0.0.1:5683"); +static void expect_download_multiple_blocks(avs_net_socket_t *socket, + void *dummy) { + assert(socket == SIMPLE_ENV.mocksock); + (void) dummy; - // setup expects - avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, "127.0.0.1", "5683"); + static const size_t BLOCK_SIZE = 16; size_t num_blocks = DIV_CEIL(sizeof(DESPAIR) - 1, BLOCK_SIZE); for (size_t i = 0; i < num_blocks; ++i) { @@ -419,6 +426,17 @@ AVS_UNIT_TEST(downloader, coap_download_multiple_blocks) { _anjay_download_status_success()); } } +} + +AVS_UNIT_TEST(downloader, coap_download_multiple_blocks) { + setup_simple("coap://127.0.0.1:5683"); + + // setup expects + avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock); + avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock); + avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, "127.0.0.1", "5683", + .and_then = + expect_download_multiple_blocks); perform_simple_download(); @@ -428,8 +446,6 @@ AVS_UNIT_TEST(downloader, coap_download_multiple_blocks) { AVS_UNIT_TEST(downloader, download_abort_on_cleanup) { setup_simple("coap://127.0.0.1:5683"); - avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, "127.0.0.1", "5683"); - anjay_download_handle_t handle = NULL; AVS_UNIT_ASSERT_SUCCESS( _anjay_downloader_download(&SIMPLE_ENV.base->anjay->downloader, @@ -445,19 +461,30 @@ AVS_UNIT_TEST(downloader, download_abort_on_cleanup) { teardown_simple(); } -AVS_UNIT_TEST(downloader, download_abort_on_reset_response) { - setup_simple("coap://127.0.0.1:5683"); +static void expect_download_abort_on_reset_response(avs_net_socket_t *socket, + void *dummy) { + assert(socket == SIMPLE_ENV.mocksock); + (void) dummy; // expect packets const coap_test_msg_t *req = COAP_MSG(CON, GET, ID_TOKEN_RAW(0, nth_token(0)), NO_PAYLOAD); const coap_test_msg_t *res = COAP_MSG(RST, EMPTY, ID(0), NO_PAYLOAD); - avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, "127.0.0.1", "5683"); avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req->content, req->length); avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &res->content, res->length); expect_has_buffered_data_check(SIMPLE_ENV.mocksock, false); +} + +AVS_UNIT_TEST(downloader, download_abort_on_reset_response) { + setup_simple("coap://127.0.0.1:5683"); + + avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock); + avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock); + avs_unit_mocksock_expect_connect( + SIMPLE_ENV.mocksock, "127.0.0.1", "5683", + .and_then = expect_download_abort_on_reset_response); // expect handler calls expect_download_finished(&SIMPLE_ENV.data, @@ -494,8 +521,10 @@ AVS_UNIT_TEST(downloader, unrelated_socket) { teardown(); } -AVS_UNIT_TEST(downloader, coap_download_separate_response) { - setup_simple("coap://127.0.0.1:5683"); +static void expect_download_separate_response(avs_net_socket_t *socket, + void *dummy) { + assert(socket == SIMPLE_ENV.mocksock); + (void) dummy; // expect packets const coap_test_msg_t *req = @@ -505,13 +534,22 @@ AVS_UNIT_TEST(downloader, coap_download_separate_response) { BLOCK2(0, 128, DESPAIR)); const coap_test_msg_t *res_res = COAP_MSG(ACK, EMPTY, ID(1), NO_PAYLOAD); - avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, "127.0.0.1", "5683"); avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req->content, req->length); avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &res->content, res->length); avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &res_res->content, res_res->length); expect_has_buffered_data_check(SIMPLE_ENV.mocksock, false); +} + +AVS_UNIT_TEST(downloader, coap_download_separate_response) { + setup_simple("coap://127.0.0.1:5683"); + + avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock); + avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock); + avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, "127.0.0.1", "5683", + .and_then = + expect_download_separate_response); // expect handler calls expect_next_block(&SIMPLE_ENV.data, @@ -528,8 +566,10 @@ AVS_UNIT_TEST(downloader, coap_download_separate_response) { teardown_simple(); } -AVS_UNIT_TEST(downloader, coap_download_unexpected_packet) { - setup_simple("coap://127.0.0.1:5683"); +static void expect_download_unexpected_packet(avs_net_socket_t *socket, + void *dummy) { + assert(socket == SIMPLE_ENV.mocksock); + (void) dummy; // expect packets const coap_test_msg_t *req = @@ -540,7 +580,6 @@ AVS_UNIT_TEST(downloader, coap_download_unexpected_packet) { COAP_MSG(ACK, CONTENT, ID_TOKEN_RAW(0, nth_token(0)), BLOCK2(0, 128, DESPAIR)); - avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, "127.0.0.1", "5683"); avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req->content, req->length); avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &unk1->content, unk1->length); @@ -549,6 +588,16 @@ AVS_UNIT_TEST(downloader, coap_download_unexpected_packet) { expect_has_buffered_data_check(SIMPLE_ENV.mocksock, true); avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &res->content, res->length); expect_has_buffered_data_check(SIMPLE_ENV.mocksock, false); +} + +AVS_UNIT_TEST(downloader, coap_download_unexpected_packet) { + setup_simple("coap://127.0.0.1:5683"); + + avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock); + avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock); + avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, "127.0.0.1", "5683", + .and_then = + expect_download_unexpected_packet); // expect handler calls expect_next_block(&SIMPLE_ENV.data, @@ -565,8 +614,10 @@ AVS_UNIT_TEST(downloader, coap_download_unexpected_packet) { teardown_simple(); } -AVS_UNIT_TEST(downloader, coap_download_abort_from_handler) { - setup_simple("coap://127.0.0.1:5683"); +static void expect_download_abort_from_handler(avs_net_socket_t *socket, + void *dummy) { + assert(socket == SIMPLE_ENV.mocksock); + (void) dummy; // expect packets const coap_test_msg_t *req = @@ -575,11 +626,20 @@ AVS_UNIT_TEST(downloader, coap_download_abort_from_handler) { COAP_MSG(ACK, CONTENT, ID_TOKEN_RAW(0, nth_token(0)), BLOCK2(0, 128, DESPAIR)); - avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, "127.0.0.1", "5683"); avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req->content, req->length); avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &res->content, res->length); expect_has_buffered_data_check(SIMPLE_ENV.mocksock, false); +} + +AVS_UNIT_TEST(downloader, coap_download_abort_from_handler) { + setup_simple("coap://127.0.0.1:5683"); + + avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock); + avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock); + avs_unit_mocksock_expect_connect( + SIMPLE_ENV.mocksock, "127.0.0.1", "5683", + .and_then = expect_download_abort_from_handler); // expect handler calls expect_next_block(&SIMPLE_ENV.data, @@ -597,8 +657,9 @@ AVS_UNIT_TEST(downloader, coap_download_abort_from_handler) { teardown_simple(); } -AVS_UNIT_TEST(downloader, coap_download_expired) { - setup_simple("coap://127.0.0.1:5683"); +static void expect_download_expired(avs_net_socket_t *socket, void *dummy) { + assert(socket == SIMPLE_ENV.mocksock); + (void) dummy; // expect packets const coap_test_msg_t *req1 = @@ -614,7 +675,6 @@ AVS_UNIT_TEST(downloader, coap_download_expired) { COAP_MSG(ACK, CONTENT, ID_TOKEN_RAW(1, nth_token(1)), ETAG("nje"), BLOCK2(1, 64, DESPAIR)); - avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, "127.0.0.1", "5683"); avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req1->content, req1->length); avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &res1->content, res1->length); @@ -623,6 +683,15 @@ AVS_UNIT_TEST(downloader, coap_download_expired) { req2->length); avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &res2->content, res2->length); expect_has_buffered_data_check(SIMPLE_ENV.mocksock, false); +} + +AVS_UNIT_TEST(downloader, coap_download_expired) { + setup_simple("coap://127.0.0.1:5683"); + + avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock); + avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock); + avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, "127.0.0.1", "5683", + .and_then = expect_download_expired); static const avs_coap_etag_t etag = { .size = 3, @@ -651,6 +720,8 @@ AVS_UNIT_TEST(downloader, buffer_too_small_to_download) { memcpy((void *) (intptr_t) &SIMPLE_ENV.base->anjay->out_shared_buffer ->capacity, &new_capacity, sizeof(new_capacity)); + avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock); + avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock); avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, "127.0.0.1", "5683"); anjay_download_handle_t handle = NULL; @@ -675,6 +746,13 @@ AVS_UNIT_TEST(downloader, buffer_too_small_to_download) { teardown_simple(); } +static void expect_single_req(avs_net_socket_t *socket, void *req_) { + assert(socket == SIMPLE_ENV.mocksock); + const coap_test_msg_t *req = (const coap_test_msg_t *) req_; + avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req->content, + req->length); +} + AVS_UNIT_TEST(downloader, retry) { setup_simple("coap://127.0.0.1:5683"); @@ -684,7 +762,11 @@ AVS_UNIT_TEST(downloader, retry) { COAP_MSG(ACK, CONTENT, ID_TOKEN_RAW(0, nth_token(0)), ETAG("tag"), BLOCK2(0, 128, DESPAIR)); - avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, "127.0.0.1", "5683"); + avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock); + avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock); + avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, "127.0.0.1", "5683", + .and_then = expect_single_req, + .and_then_arg = (void *) (intptr_t) req); anjay_download_handle_t handle = NULL; AVS_UNIT_ASSERT_SUCCESS( @@ -694,8 +776,6 @@ AVS_UNIT_TEST(downloader, retry) { // initial request ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, SIMPLE_ENV.base->anjay); - avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req->content, - req->length); while (avs_time_duration_equal(avs_sched_time_to_next( SIMPLE_ENV.base->anjay->sched), AVS_TIME_DURATION_ZERO)) { @@ -770,7 +850,11 @@ AVS_UNIT_TEST(downloader, missing_separate_response) { COAP_MSG(CON, GET, ID_TOKEN_RAW(0, nth_token(0)), NO_PAYLOAD); const coap_test_msg_t *req_ack = COAP_MSG(ACK, EMPTY, ID(0), NO_PAYLOAD); - avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, "127.0.0.1", "5683"); + avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock); + avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock); + avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, "127.0.0.1", "5683", + .and_then = expect_single_req, + .and_then_arg = (void *) (intptr_t) req); anjay_download_handle_t handle = NULL; AVS_UNIT_ASSERT_SUCCESS( @@ -779,8 +863,6 @@ AVS_UNIT_TEST(downloader, missing_separate_response) { AVS_UNIT_ASSERT_NOT_NULL(handle); // initial request - avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req->content, - req->length); ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, SIMPLE_ENV.base->anjay); while (avs_time_duration_equal(avs_sched_time_to_next( SIMPLE_ENV.base->anjay->sched), @@ -833,10 +915,34 @@ static size_t num_downloads_in_progress(void) { return result; } -AVS_UNIT_TEST(downloader, abort) { +AVS_UNIT_TEST(downloader, abort_coap) { setup_simple("coap://127.0.0.1:5683"); - avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, "127.0.0.1", "5683"); + anjay_download_handle_t handle = NULL; + AVS_UNIT_ASSERT_SUCCESS( + _anjay_downloader_download(&SIMPLE_ENV.base->anjay->downloader, + &handle, &SIMPLE_ENV.cfg, NULL, NULL)); + AVS_UNIT_ASSERT_NOT_NULL(handle); + + // start_download_job is scheduled + AVS_UNIT_ASSERT_TRUE(avs_time_duration_valid( + avs_sched_time_to_next(SIMPLE_ENV.base->anjay->sched))); + + expect_download_finished(&SIMPLE_ENV.data, + _anjay_download_status_aborted()); + _anjay_downloader_abort(&SIMPLE_ENV.base->anjay->downloader, handle); + + // start_download_job is canceled + AVS_UNIT_ASSERT_FALSE(avs_time_duration_valid( + avs_sched_time_to_next(SIMPLE_ENV.base->anjay->sched))); + AVS_UNIT_ASSERT_EQUAL(0, num_downloads_in_progress()); + + teardown_simple(); +} + +#ifdef ANJAY_WITH_HTTP_DOWNLOAD +AVS_UNIT_TEST(downloader, abort_http) { + setup_simple("http://127.0.0.1"); anjay_download_handle_t handle = NULL; AVS_UNIT_ASSERT_SUCCESS( @@ -847,15 +953,12 @@ AVS_UNIT_TEST(downloader, abort) { // start_download_job is scheduled AVS_UNIT_ASSERT_TRUE(avs_time_duration_valid( avs_sched_time_to_next(SIMPLE_ENV.base->anjay->sched))); - AVS_UNIT_ASSERT_EQUAL(1, num_downloads_in_progress()); expect_download_finished(&SIMPLE_ENV.data, _anjay_download_status_aborted()); _anjay_downloader_abort(&SIMPLE_ENV.base->anjay->downloader, handle); - // TODO: remove after T2217. - // CoAP context cleanup. It's a side effect of a hack in - // coap.c:cleanup_coap_transfer(). + // cleanup_http_stream is run through the scheduler ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, SIMPLE_ENV.base->anjay); avs_sched_run(SIMPLE_ENV.base->anjay->sched); ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked); @@ -867,9 +970,11 @@ AVS_UNIT_TEST(downloader, abort) { teardown_simple(); } +#endif // ANJAY_WITH_HTTP_DOWNLOAD -AVS_UNIT_TEST(downloader, uri_path_query) { - setup_simple("coap://127.0.0.1:5683/uri/path?query=string&another"); +static void expect_uri_path_query(avs_net_socket_t *socket, void *dummy) { + assert(socket == SIMPLE_ENV.mocksock); + (void) dummy; // expect packets const coap_test_msg_t *req = @@ -880,11 +985,19 @@ AVS_UNIT_TEST(downloader, uri_path_query) { COAP_MSG(ACK, CONTENT, ID_TOKEN_RAW(0, nth_token(0)), BLOCK2(0, 128, DESPAIR)); - avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, "127.0.0.1", "5683"); avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req->content, req->length); avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &res->content, res->length); expect_has_buffered_data_check(SIMPLE_ENV.mocksock, false); +} + +AVS_UNIT_TEST(downloader, uri_path_query) { + setup_simple("coap://127.0.0.1:5683/uri/path?query=string&another"); + + avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock); + avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock); + avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, "127.0.0.1", "5683", + .and_then = expect_uri_path_query); // expect handler calls expect_next_block(&SIMPLE_ENV.data, @@ -901,15 +1014,10 @@ AVS_UNIT_TEST(downloader, uri_path_query) { teardown_simple(); } -AVS_UNIT_TEST(downloader, in_buffer_size_enforces_smaller_initial_block_size) { - setup_simple("coap://127.0.0.1:5683"); - - // the downloader should realize it cannot hold blocks bigger than 128 - // bytes and request that size - size_t new_capacity = 256; - memcpy((void *) (intptr_t) &SIMPLE_ENV.base->anjay->in_shared_buffer - ->capacity, - &new_capacity, sizeof(new_capacity)); +static void expect_in_buffer_size_enforces_smaller_initial_block_size( + avs_net_socket_t *socket, void *dummy) { + assert(socket == SIMPLE_ENV.mocksock); + (void) dummy; // expect packets const coap_test_msg_t *req = @@ -918,11 +1026,28 @@ AVS_UNIT_TEST(downloader, in_buffer_size_enforces_smaller_initial_block_size) { COAP_MSG(ACK, CONTENT, ID_TOKEN_RAW(0, nth_token(0)), BLOCK2(0, 128, DESPAIR)); - avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, "127.0.0.1", "5683"); avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req->content, req->length); avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &res->content, res->length); expect_has_buffered_data_check(SIMPLE_ENV.mocksock, false); +} + +AVS_UNIT_TEST(downloader, in_buffer_size_enforces_smaller_initial_block_size) { + setup_simple("coap://127.0.0.1:5683"); + + // the downloader should realize it cannot hold blocks bigger than 128 + // bytes and request that size + size_t new_capacity = 256; + memcpy((void *) (intptr_t) &SIMPLE_ENV.base->anjay->in_shared_buffer + ->capacity, + &new_capacity, sizeof(new_capacity)); + + avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock); + avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock); + avs_unit_mocksock_expect_connect( + SIMPLE_ENV.mocksock, "127.0.0.1", "5683", + .and_then = + expect_in_buffer_size_enforces_smaller_initial_block_size); // expect handler calls expect_next_block(&SIMPLE_ENV.data, @@ -939,8 +1064,10 @@ AVS_UNIT_TEST(downloader, in_buffer_size_enforces_smaller_initial_block_size) { teardown_simple(); } -AVS_UNIT_TEST(downloader, renegotiation_while_requesting_more_than_available) { - setup_simple("coap://127.0.0.1:5683"); +static void expect_renegotiation_while_requesting_more_than_available( + avs_net_socket_t *socket, void *dummy) { + assert(socket == SIMPLE_ENV.mocksock); + (void) dummy; // We request as much as we can (i.e. 1024 bytes) const coap_test_msg_t *req = @@ -952,11 +1079,21 @@ AVS_UNIT_TEST(downloader, renegotiation_while_requesting_more_than_available) { COAP_MSG(ACK, CONTENT, ID_TOKEN_RAW(0, nth_token(0)), BLOCK2(0, 128, DESPAIR)); - avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, "127.0.0.1", "5683"); avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req->content, req->length); avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &res->content, res->length); expect_has_buffered_data_check(SIMPLE_ENV.mocksock, false); +} + +AVS_UNIT_TEST(downloader, renegotiation_while_requesting_more_than_available) { + setup_simple("coap://127.0.0.1:5683"); + + avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock); + avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock); + avs_unit_mocksock_expect_connect( + SIMPLE_ENV.mocksock, "127.0.0.1", "5683", + .and_then = + expect_renegotiation_while_requesting_more_than_available); // expect handler calls expect_next_block(&SIMPLE_ENV.data, @@ -973,20 +1110,10 @@ AVS_UNIT_TEST(downloader, renegotiation_while_requesting_more_than_available) { teardown_simple(); } -AVS_UNIT_TEST(downloader, renegotiation_after_first_packet) { - setup_simple("coap://127.0.0.1:5683"); - - avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, "127.0.0.1", "5683"); - - on_next_block_args_t args; - memset(&args, 0, sizeof(args)); - - // We request as much as we can (i.e. 64 bytes due to limit of - // in_buffer_size) - size_t new_capacity = 128; - memcpy((void *) (intptr_t) &SIMPLE_ENV.base->anjay->in_shared_buffer - ->capacity, - &new_capacity, sizeof(new_capacity)); +static void expect_renegotiation_after_first_packet(avs_net_socket_t *socket, + void *dummy) { + assert(socket == SIMPLE_ENV.mocksock); + (void) dummy; const coap_test_msg_t *req = COAP_MSG(CON, GET, ID_TOKEN_RAW(0, nth_token(0)), NO_PAYLOAD); @@ -995,17 +1122,12 @@ AVS_UNIT_TEST(downloader, renegotiation_after_first_packet) { const coap_test_msg_t *res = COAP_MSG(ACK, CONTENT, ID_TOKEN_RAW(0, nth_token(0)), BLOCK2(0, 64, DESPAIR)); + avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req->content, req->length); avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &res->content, res->length); expect_has_buffered_data_check(SIMPLE_ENV.mocksock, true); - memset(args.data, 0, sizeof(args.data)); - assert(strlen(DESPAIR) > 64); - memcpy(args.data, DESPAIR, 64); - args.data_size = strlen(args.data); - expect_next_block(&SIMPLE_ENV.data, args); - // We then request another block with negotiated 64 bytes req = COAP_MSG(CON, GET, ID_TOKEN_RAW(1, nth_token(1)), BLOCK2(1, 64, "")); // But the server is weird, and responds with an even smaller block size @@ -1019,11 +1141,6 @@ AVS_UNIT_TEST(downloader, renegotiation_after_first_packet) { avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &res->content, res->length); expect_has_buffered_data_check(SIMPLE_ENV.mocksock, true); - memset(args.data, 0, sizeof(args.data)); - strncpy(args.data, DESPAIR + 64, 32); - args.data_size = strlen(args.data); - expect_next_block(&SIMPLE_ENV.data, args); - // Last block - no surprises this time. req = COAP_MSG(CON, GET, ID_TOKEN_RAW(2, nth_token(2)), BLOCK2(3, 32, "")); res = COAP_MSG(ACK, CONTENT, ID_TOKEN_RAW(2, nth_token(2)), @@ -1032,6 +1149,37 @@ AVS_UNIT_TEST(downloader, renegotiation_after_first_packet) { req->length); avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &res->content, res->length); expect_has_buffered_data_check(SIMPLE_ENV.mocksock, false); +} + +AVS_UNIT_TEST(downloader, renegotiation_after_first_packet) { + setup_simple("coap://127.0.0.1:5683"); + + avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock); + avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock); + avs_unit_mocksock_expect_connect( + SIMPLE_ENV.mocksock, "127.0.0.1", "5683", + .and_then = expect_renegotiation_after_first_packet); + + on_next_block_args_t args; + memset(&args, 0, sizeof(args)); + + // We request as much as we can (i.e. 64 bytes due to limit of + // in_buffer_size) + size_t new_capacity = 128; + memcpy((void *) (intptr_t) &SIMPLE_ENV.base->anjay->in_shared_buffer + ->capacity, + &new_capacity, sizeof(new_capacity)); + + memset(args.data, 0, sizeof(args.data)); + assert(strlen(DESPAIR) > 64); + memcpy(args.data, DESPAIR, 64); + args.data_size = strlen(args.data); + expect_next_block(&SIMPLE_ENV.data, args); + + memset(args.data, 0, sizeof(args.data)); + strncpy(args.data, DESPAIR + 64, 32); + args.data_size = strlen(args.data); + expect_next_block(&SIMPLE_ENV.data, args); memset(args.data, 0, sizeof(args.data)); strncpy(args.data, DESPAIR + 64 + 32, 32); @@ -1046,66 +1194,75 @@ AVS_UNIT_TEST(downloader, renegotiation_after_first_packet) { teardown_simple(); } +static void expect_resumption_at_some_offset(avs_net_socket_t *socket, + void *offset_) { + assert(socket == SIMPLE_ENV.mocksock); + size_t offset = (uintptr_t) offset_; + + on_next_block_args_t args; + memset(&args, 0, sizeof(args)); + + enum { BLOCK_SIZE = 32 }; + + size_t current_offset = offset; + size_t msg_id = 0; + while (sizeof(DESPAIR) - current_offset > 0) { + size_t seq_num = current_offset / BLOCK_SIZE; + const coap_test_msg_t *req = + seq_num == 0 ? COAP_MSG(CON, GET, + ID_TOKEN_RAW(msg_id, nth_token(msg_id)), + NO_PAYLOAD) + : COAP_MSG(CON, GET, + ID_TOKEN_RAW(msg_id, nth_token(msg_id)), + BLOCK2(seq_num, BLOCK_SIZE, "")); + const coap_test_msg_t *res = + COAP_MSG(ACK, CONTENT, ID_TOKEN_RAW(msg_id, nth_token(msg_id)), + BLOCK2(seq_num, BLOCK_SIZE, DESPAIR)); + avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req->content, + req->length); + avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &res->content, + res->length); + + // Copy contents from the current_offset till the end of the + // enclosing block. + const size_t bytes_till_block_end = + AVS_MIN((seq_num + 1) * BLOCK_SIZE - current_offset, + sizeof(DESPAIR) - current_offset); + + memset(args.data, 0, sizeof(args.data)); + // User handler gets the data from a specified offset, even if + // it is pointing at the middle of the block that has to be + // received for a given offset. + memcpy(args.data, DESPAIR + current_offset, bytes_till_block_end); + // See BLOCK2 macro - it ignores terminating '\0', so strlen() + // must be used to compute actual data length. + args.data_size = strlen(args.data); + expect_next_block(&SIMPLE_ENV.data, args); + + current_offset += bytes_till_block_end; + ++msg_id; + + expect_has_buffered_data_check(SIMPLE_ENV.mocksock, + sizeof(DESPAIR) - current_offset > 0); + } +} + AVS_UNIT_TEST(downloader, resumption_at_some_offset) { for (size_t offset = 0; offset < sizeof(DESPAIR); ++offset) { setup_simple("coap://127.0.0.1:5683"); - avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, "127.0.0.1", - "5683"); - - on_next_block_args_t args; - memset(&args, 0, sizeof(args)); + avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock); + avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock); + avs_unit_mocksock_expect_connect( + SIMPLE_ENV.mocksock, "127.0.0.1", "5683", + .and_then = expect_resumption_at_some_offset, + .and_then_arg = (void *) (uintptr_t) offset); size_t new_capacity = 64; memcpy((void *) (intptr_t) &SIMPLE_ENV.base->anjay->in_shared_buffer ->capacity, &new_capacity, sizeof(new_capacity)); - enum { BLOCK_SIZE = 32 }; - - size_t current_offset = offset; - size_t msg_id = 0; - while (sizeof(DESPAIR) - current_offset > 0) { - size_t seq_num = current_offset / BLOCK_SIZE; - const coap_test_msg_t *req = - seq_num == 0 - ? COAP_MSG(CON, GET, - ID_TOKEN_RAW(msg_id, nth_token(msg_id)), - NO_PAYLOAD) - : COAP_MSG(CON, GET, - ID_TOKEN_RAW(msg_id, nth_token(msg_id)), - BLOCK2(seq_num, BLOCK_SIZE, "")); - const coap_test_msg_t *res = - COAP_MSG(ACK, CONTENT, - ID_TOKEN_RAW(msg_id, nth_token(msg_id)), - BLOCK2(seq_num, BLOCK_SIZE, DESPAIR)); - avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req->content, - req->length); - avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &res->content, - res->length); - - // Copy contents from the current_offset till the end of the - // enclosing block. - const size_t bytes_till_block_end = - AVS_MIN((seq_num + 1) * BLOCK_SIZE - current_offset, - sizeof(DESPAIR) - current_offset); - - memset(args.data, 0, sizeof(args.data)); - // User handler gets the data from a specified offset, even if - // it is pointing at the middle of the block that has to be - // received for a given offset. - memcpy(args.data, DESPAIR + current_offset, bytes_till_block_end); - // See BLOCK2 macro - it ignores terminating '\0', so strlen() - // must be used to compute actual data length. - args.data_size = strlen(args.data); - expect_next_block(&SIMPLE_ENV.data, args); - - current_offset += bytes_till_block_end; - ++msg_id; - - expect_has_buffered_data_check( - SIMPLE_ENV.mocksock, sizeof(DESPAIR) - current_offset > 0); - } expect_download_finished(&SIMPLE_ENV.data, _anjay_download_status_success()); @@ -1136,8 +1293,6 @@ AVS_UNIT_TEST(downloader, resumption_at_some_offset) { AVS_UNIT_TEST(downloader, resumption_without_etag_and_block_estimation) { setup_simple("coap://127.0.0.1:5683"); - avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, "127.0.0.1", "5683"); - on_next_block_args_t args; memset(&args, 0, sizeof(args)); @@ -1154,8 +1309,11 @@ AVS_UNIT_TEST(downloader, resumption_without_etag_and_block_estimation) { COAP_MSG(CON, GET, ID_TOKEN_RAW(0, nth_token(0)), BLOCK2(1, 64, "")); - avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req->content, - req->length); + avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock); + avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock); + avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, "127.0.0.1", "5683", + .and_then = expect_single_req, + .and_then_arg = (void *) (intptr_t) req); anjay_download_handle_t handle = NULL; AVS_UNIT_ASSERT_SUCCESS( @@ -1185,8 +1343,6 @@ AVS_UNIT_TEST(downloader, resumption_with_etag_and_block_estimation) { setup_simple_with_etag("coap://127.0.0.1:5683", etag); - avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, "127.0.0.1", "5683"); - on_next_block_args_t args; memset(&args, 0, sizeof(args)); @@ -1204,8 +1360,11 @@ AVS_UNIT_TEST(downloader, resumption_with_etag_and_block_estimation) { COAP_MSG(CON, GET, ID_TOKEN_RAW(0, nth_token(0)), BLOCK2(1, 64, "")); - avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req->content, - req->length); + avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock); + avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock); + avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, "127.0.0.1", "5683", + .and_then = expect_single_req, + .and_then_arg = (void *) (intptr_t) req); anjay_download_handle_t handle = NULL; AVS_UNIT_ASSERT_SUCCESS( diff --git a/tests/integration/framework/asserts.py b/tests/integration/framework/asserts.py index a5c875a3..ae56cd95 100644 --- a/tests/integration/framework/asserts.py +++ b/tests/integration/framework/asserts.py @@ -208,13 +208,13 @@ def assertDemoRequestsBootstrap(self, uri_path='', uri_query=None, respond_with_ self.bootstrap_server.send(Lwm2mErrorResponse.matching( pkt)(code=respond_with_error_code)) - def assertDtlsReconnect(self, server=None, timeout_s=-1, deadline=None): + def assertDtlsReconnect(self, server=None, timeout_s=-1, deadline=None, expected_error='0x6780'): serv = server or self.serv with self.assertRaises(RuntimeError) as raised: serv.recv(timeout_s=timeout_s, deadline=deadline) # -0x6780 == MBEDTLS_ERR_SSL_CLIENT_RECONNECT - self.assertIn('0x6780', raised.exception.args[0]) + self.assertIn(expected_error, raised.exception.args[0]) def assertPktIsDtlsClientHello(self, pkt, seq_number=ANY): if seq_number is not ANY and seq_number >= 2 ** 48: diff --git a/tests/integration/framework/create_xlsx_test_report.py b/tests/integration/framework/create_xlsx_test_report.py new file mode 100644 index 00000000..da5041ca --- /dev/null +++ b/tests/integration/framework/create_xlsx_test_report.py @@ -0,0 +1,286 @@ +# -*- coding: utf-8 -*- +# +# 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. + +import argparse +import enum +import os +import re +import sys +import collections +from operator import itemgetter + +from openpyxl import Workbook +from openpyxl.utils import get_column_letter +from openpyxl.worksheet.table import Table, TableStyleInfo +from openpyxl.styles import Alignment, Font, PatternFill, Border, Side + + +ColumnValues = collections.namedtuple("ColumnValues", ["int", "name"]) + +class ColumnIndex(enum.Enum): + TYPE = ColumnValues(1, "TYPE") + SUITE = ColumnValues(2, "SUITE") + NAME = ColumnValues(3, "NAME") + STATUS = ColumnValues(4, "STATUS") + TIME = ColumnValues(5, "TIME [s]") + FAIL_REASON = ColumnValues(6, "FAIL REASON") + + def to_letter(self): + return chr(ord('A') - 1 + self.value.int) + +# Assert that the FAIL_REASON has the largest value +assert all(ColumnIndex.FAIL_REASON.value.int >= member.value.int for member in ColumnIndex), \ + "FAIL_REASON should be the largest value" + +# Assert that integer values are in ascending order (1, 2, 3 ...) +for i, elem in enumerate(ColumnIndex): + assert(elem.value.int == i + 1), \ + "ColumnIndex integer values should be arranged in ascending order" + + +class TestResultParser(): + def __init__(self, log_file_path) -> None: + self.log_file_path = log_file_path + + def get_test_results(self): + test_type, test_suite = self._get_test_type_and_suite() + file_content = self._read_log_file_content() + if file_content is None or len(file_content) == 0: + return None + test_name, test_status, test_time = self._get_result_info(file_content) + fail_reason = self._get_fail_reason(test_status, file_content) + + ret_result = [None] * len(ColumnIndex) + ret_result[ColumnIndex.TYPE.value.int - 1] = test_type + ret_result[ColumnIndex.SUITE.value.int - 1] = test_suite + ret_result[ColumnIndex.NAME.value.int - 1] = test_name + ret_result[ColumnIndex.STATUS.value.int - 1] = test_status + ret_result[ColumnIndex.TIME.value.int - 1] = test_time + ret_result[ColumnIndex.FAIL_REASON.value.int - 1] = fail_reason + return ret_result + + def _status_is_fail_or_error(self, status): + if status == "FAIL" or status == "ERROR": + return True + return False + + def _read_log_file_content(self): + if not os.path.exists(self.log_file_path): + return None + with open(self.log_file_path, 'r') as file: + lines = file.readlines() + + return lines + + def _get_result_info(self, file_content): + # an example success log file may look like this: + # SmsDtls . . . . . . . . . . . . . . . . . . . . . . . . . . . OK (34.34s) + # an example FAIL/ERROR log file may look like this: + # NonconfirmableExecuteTest . . . . . . . . . . . . . . . . . . FAIL + # Test NonconfirmableExecuteTest failed! + # (assertion description...) + # + # Stack trace: + # (python stack trace...) + result_line = file_content[0].replace('. ', '').replace(" ", " ").strip().split(" ") + test_name = result_line[0] + test_status = result_line[1] + try: + test_time = result_line[2][1:-2] + except: + test_time = None + + return test_name, test_status, test_time + + def _has_whitespaces_only(self, line): + if re.match(r'^\s*$', line): + return True + return False + + def _get_fail_reason(self, status, file_content): + if not self._status_is_fail_or_error(status): + return "" + ret = [] + for line in file_content: + if line == "Stack trace:\n": + break + if self._has_whitespaces_only(line): + continue + ret.append(line) + + return ''.join(ret[1:]).strip() + + def _get_test_type_and_suite(self): + directory_name = os.path.basename(os.path.dirname(self.log_file_path)).split('.') + test_type = directory_name[0] + test_suite = '.'.join(directory_name[1:]) + + return test_type, test_suite + + +class XlsxFileCreator(): + def __init__(self) -> None: + self.wb = Workbook() + self.ws = self.wb.active + self.number_of_rows = 0 + + def append_test_results(self, test_results): + sorted_results = self._sort_test_results(test_results) + for result in sorted_results: + if result is not None: + self.ws.append(result) + self.number_of_rows += 1 + + def perform_formating(self): + self._apply_vertical_center_alignment() + self._format_first_row() + self._adjust_rows_height() + self._adjust_columns_width() + self._add_status_colors() + self._format_time_as_numbers() + self._add_borders() + self._format_as_table() + + def add_header(self): + header = [] + for elem in ColumnIndex: + header.append(elem.value.name) + self.ws.append(header) + self.number_of_rows += 1 + + def _sort_test_results(self, unsorted_results): + sorted_results = sorted(unsorted_results, key=itemgetter(ColumnIndex.TYPE.value.int - 1, + ColumnIndex.SUITE.value.int - 1, + ColumnIndex.NAME.value.int - 1)) + return sorted_results + + def _adjust_rows_height(self): + for row in self.ws.iter_rows(): + max_height = 0 + for cell in row: + try: + # NOTE: partially based on: + # https://stackoverflow.com/questions/39529662/python-automatically-adjust-width-of-an-excel-files-columns + cell_height = len(str(cell.value).split('\n')) * 13 * cell.font.size/10 + if cell_height > max_height: + max_height = cell_height + except: + pass + self.ws.row_dimensions[row[0].row].height = max_height + + def _adjust_columns_width(self): + for column in self.ws.iter_cols(): + max_length = 0 + column_letter = get_column_letter(column[0].column) + for cell in column: + try: + # NOTE: partially based on: + # https://stackoverflow.com/questions/39529662/python-automatically-adjust-width-of-an-excel-files-columns + row_width = len(cell.value) * cell.font.size/10 + if row_width > max_length: + max_length = row_width + if cell.font.bold == True: + max_length *= 1.1 + except: + pass + adjusted_width = (max_length + 3) + self.ws.column_dimensions[column_letter].width = adjusted_width + + def _apply_vertical_center_alignment(self): + for row in self.ws.iter_rows(): + for cell in row: + cell.alignment = Alignment(vertical='center') + for col in self.ws.iter_cols(min_col=1, max_col=len(ColumnIndex) - 1): + for cell in col: + cell.alignment = Alignment(horizontal='center', vertical='center') + + def _format_first_row(self): + fill = PatternFill(start_color="729FCF", end_color="729FCF", fill_type="solid") + for cell in self.ws[1]: + cell.font = Font(size=15, bold=True) + cell.alignment = Alignment(horizontal='center', vertical='center') + cell.fill = fill + + def _add_status_colors(self): + ok_fill = PatternFill(start_color="A1FF41", end_color="A1FF41", fill_type="solid") + fail_fill = PatternFill(start_color="E11941", end_color="E11941", fill_type="solid") + skip_fill = PatternFill(start_color="FDF362", end_color="FDF362", fill_type="solid") + for col in self.ws.iter_cols(min_col=ColumnIndex.STATUS.value.int, max_col=ColumnIndex.STATUS.value.int): + for cell in col: + if cell.value == "OK": + cell.fill = ok_fill + elif cell.value == "FAIL" or cell.value == "ERROR": + cell.fill = fail_fill + elif cell.value == "SKIP": + cell.fill = skip_fill + + def _add_borders(self): + border = Border( + left = Side(border_style="thin", color="000000"), + right = Side(border_style="thin", color="000000"), + top = Side(border_style="thin", color="000000"), + bottom = Side(border_style="thin", color="000000"), + ) + for row in self.ws.iter_rows(): + for cell in row: + cell.border = border + + def _format_time_as_numbers(self): + for col in self.ws.iter_cols(min_col=ColumnIndex.TIME.value.int, max_col=ColumnIndex.TIME.value.int): + for cell in col: + if cell.value != ColumnIndex.TIME.value.name and cell.value != None: + cell.number_format = "0.00" + cell.value = float(cell.value) + + def _format_as_table(self): + ref = "A1:" + list(ColumnIndex)[-1].to_letter() + str(self.number_of_rows) + table = Table(displayName="Table", ref=ref) + + style = TableStyleInfo( + name="TableStyleMedium9", showFirstColumn=False, + showLastColumn=False, showRowStripes=False, showColumnStripes=False + ) + table.tableStyleInfo = style + + self.ws.add_table(table) + + +def enumerate_log_files(path): + for root, _, files in os.walk(path, topdown=False): + for file in files: + if re.match(r'.*\.log$', file): + yield os.path.join(root, file) + + +def _main(): + parser = argparse.ArgumentParser(description='Create .xlsx file out of output .log files', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument('-d', '--log-dir', + help='directory that contains output .log files', + required=True) + args = parser.parse_args() + + test_results = [] + for log_file_path in enumerate_log_files(args.log_dir): + result_parser = TestResultParser(log_file_path) + test_results.append(result_parser.get_test_results()) + + xlsx_file = XlsxFileCreator() + xlsx_file.add_header() + xlsx_file.append_test_results(test_results) + + xlsx_file.perform_formating() + + output_path = os.path.join(args.log_dir, "../..", "integration_test_results.xlsx") + xlsx_file.wb.save(output_path) + + print(f"{os.path.basename(__file__)}: saved test reports to {os.path.abspath(output_path)}") + +if __name__ == '__main__': + sys.exit(_main()) diff --git a/tests/integration/framework/lwm2m/coap/option.py b/tests/integration/framework/lwm2m/coap/option.py index bc4b4a54..a8dbcde2 100644 --- a/tests/integration/framework/lwm2m/coap/option.py +++ b/tests/integration/framework/lwm2m/coap/option.py @@ -73,33 +73,31 @@ def __init__(self, number, content=b''): @staticmethod def parse_ext_value(short_value, data): if short_value < 13: - return short_value, 0 + return short_value elif short_value == 13: - return 13 + struct.unpack('!B', data[:1])[0], 1 + return 13 + struct.unpack('!B', bytes([next(data)]))[0] elif short_value == 14: - return 13 + 256 + struct.unpack('!H', data[:2])[0], 2 + return 13 + 256 + struct.unpack('!H', bytes([next(data) for i in range(2)]))[0] elif short_value == 15: raise ValueError('reserved short value') @staticmethod - def parse(data, prev_opt_number): - short_delta_length, = struct.unpack('!B', data[:1]) + def parse(data_, prev_opt_number): + short_delta_length, = struct.unpack('!B', bytes([next(data_)])) short_delta = (short_delta_length >> 4) & 0x0F short_length = short_delta_length & 0x0F - at = 1 - number_delta, bytes_parsed = Option.parse_ext_value(short_delta, data[at:at + 2]) + number_delta = Option.parse_ext_value(short_delta, data_) number = prev_opt_number + number_delta - at += bytes_parsed - length, bytes_parsed = Option.parse_ext_value(short_length, data[at:at + 2]) - at += bytes_parsed + length = Option.parse_ext_value(short_length, data_) - if len(data) < at + length: + try: + content = bytes([next(data_) for i in range(length)]) + except StopIteration: raise ValueError('incomplete option') - content = bytes(data[at:at + length]) - return Option.get_class_by_number(number)(number, content), at + length + return Option.get_class_by_number(number)(number, content) @staticmethod def serialize_ext_value(value): diff --git a/tests/integration/framework/lwm2m/coap/packet.py b/tests/integration/framework/lwm2m/coap/packet.py index 37994eb5..af3444c4 100644 --- a/tests/integration/framework/lwm2m/coap/packet.py +++ b/tests/integration/framework/lwm2m/coap/packet.py @@ -6,7 +6,8 @@ # # Licensed under the AVSystem-5-clause License. # See the attached LICENSE file for details. - +import collections.abc +import itertools import operator import struct import logging @@ -75,35 +76,28 @@ def next(): _TOKEN_GENERATOR = RandomTokenGenerator() -def _parse_tcp_header(packet): - if len(packet) < 2: - raise ValueError("invalid CoAP message: %s" % hexlify(packet)) - len_tkl = packet[0] +def _parse_tcp_header(bytestream): + len_tkl = next(bytestream) short_len = len_tkl >> 4 token_length = len_tkl & 0x0F - curr_offset = 1 - if short_len <= 12: length = short_len elif short_len == 13: - length = packet[1] + 13 - curr_offset += 1 + length = next(bytestream) + 13 elif short_len == 14: - length = int.from_bytes(packet[1:3], byteorder='big') + 269 - curr_offset += 2 + length_bytes = bytes([next(bytestream) for i in range(2)]) + length = int.from_bytes(length_bytes, byteorder='big') + 269 else: - length = int.from_bytes(packet[1:5], byteorder='big') + 65805 - curr_offset += 4 + length_bytes = bytes([next(bytestream) for i in range(4)]) + length = int.from_bytes(length_bytes, byteorder='big') + 65805 - code = Code.from_byte(packet[curr_offset]) - curr_offset += 1 - return Header(code, None, None, None, token_length), curr_offset + code = Code.from_byte(next(bytestream)) + return Header(code, None, None, None, token_length), length + token_length -def _parse_udp_header(packet): - if len(packet) < 4: - raise ValueError("invalid CoAP message: %s" % hexlify(packet)) - version_type_token_length, code, msg_id = struct.unpack('!BBH', packet[:4]) +def _parse_udp_header(bytestream): + header = bytes([next(bytestream) for i in range(4)]) + version_type_token_length, code, msg_id = struct.unpack('!BBH', header) code = Code.from_byte(code) version = (version_type_token_length >> 6) & 0x03 @@ -113,9 +107,7 @@ def _parse_udp_header(packet): if version != 1: raise ValueError("invalid CoAP version: %d, expected 1" % version) - at = 4 - - return Header(code, version, type, msg_id, token_length), at + return Header(code, version, type, msg_id, token_length) class Packet(object): def __init__(self, type=None, code=1, msg_id=0, token=b'', options=None, content=b'', version=1): @@ -181,36 +173,43 @@ def _size_breakdown(self, header): @staticmethod def parse(self, transport=Transport.UDP): - packet = memoryview(self) - if transport == Transport.UDP: - header, offset = _parse_udp_header(packet) - elif transport == Transport.TCP: - header, offset = _parse_tcp_header(packet) - else: - raise ValueError("Invalid transport: %r" % (transport,)) + bytestream = self + if not isinstance(bytestream, collections.abc.Iterator): + bytestream = iter(bytestream) + + try: + if transport == Transport.UDP: + header = _parse_udp_header(bytestream) + elif transport == Transport.TCP: + header, length = _parse_tcp_header(bytestream) + bytestream = itertools.islice(bytestream, length) + else: + raise ValueError("Invalid transport: %r" % (transport,)) + except StopIteration: + raise ValueError("invalid CoAP message: %s" % (hexlify(self),)) if header.token_length > 8: raise ValueError("invalid CoAP token length: %d, expected <= 8" % header.token_length) - token = packet[offset:offset+header.token_length] - offset += header.token_length + token = bytes([next(bytestream) for i in range(header.token_length)]) options = [] content = b'' - while offset < len(packet): - if packet[offset] == 0xFF: - content = packet[offset + 1:] + while True: + try: + byte = next(bytestream) + except StopIteration: + break + if byte == 0xFF: + content = bytes(bytestream) if not content: raise ValueError('payload marker at end of packet is invalid') - offset = len(packet) + break else: - opt, bytes_parsed = Option.parse(packet[offset:], options[-1].number if options else 0) + bytestream = itertools.chain((byte,), bytestream) + opt = Option.parse(bytestream, options[-1].number if options else 0) options.append(opt) - offset += bytes_parsed - - if offset != len(packet): - raise ValueError("CoAP packet malformed starting at offset %d: %s" % (offset, hexlify(packet[offset:]))) pkt = Packet(header.type, header.code, header.id, token, options, content, header.version) if transport == Transport.UDP: diff --git a/tests/integration/framework/lwm2m/coap/server.py b/tests/integration/framework/lwm2m/coap/server.py index f352b77a..91b05cd6 100644 --- a/tests/integration/framework/lwm2m/coap/server.py +++ b/tests/integration/framework/lwm2m/coap/server.py @@ -9,6 +9,7 @@ import contextlib import enum +import errno import socket import time from typing import Tuple, Optional @@ -57,7 +58,14 @@ def _override_timeout(sock, *args, **kwargs): sock.settimeout(max(deadline - time.time(), 0)) yield finally: - sock.settimeout(orig_timeout_s) + try: + sock.settimeout(orig_timeout_s) + except OSError as e: + if e.errno == errno.EBADF: + # sock has been closed during yield, ignore + pass + else: + raise def _disconnect_socket(old_sock, family): @@ -251,7 +259,7 @@ def reset(self, listen_port=None) -> None: def send(self, coap_packet: Packet) -> None: self.socket.send(coap_packet.serialize(transport=self.transport)) - def recv_raw(self, timeout_s=-1, deadline=None, peek=False): + def recv_raw(self, timeout_s=-1, deadline=None, peek=False, bufsize=65536): deadline = _calculate_deadline(timeout_s, deadline) # NOTE: get_remote_addr() can sometimes return None, if someone @@ -269,7 +277,7 @@ def recv_raw(self, timeout_s=-1, deadline=None, peek=False): return self._filtered_messages.pop(0) with _override_timeout(self.socket, deadline=deadline): - raw_pkt = self.socket.recv(65536) + raw_pkt = self.socket.recv(bufsize) if peek: self._filtered_messages.append(raw_pkt) @@ -285,10 +293,26 @@ def recv(self, timeout_s: float = -1, deadline=None, filter=None, peek=False) -> filtered_messages = [] while True: try: - raw_pkt = self.recv_raw(deadline=deadline) + if self.transport == Transport.TCP: + raw_pkt = b'' + + def pkt_iterator(): + nonlocal raw_pkt + while True: + data = self.recv_raw(deadline=deadline, bufsize=1) + if len(data) == 0: + break + raw_pkt += data + for b in data: + yield b + + pkt_source = pkt_iterator() + else: + raw_pkt = self.recv_raw(deadline=deadline) + pkt_source = raw_pkt + pkt = Packet.parse(pkt_source, transport=self.transport) except BlockingIOError: raise socket.timeout('timed out') - pkt = Packet.parse(raw_pkt, transport=self.transport) filter_result = filter(pkt) if peek or not filter_result: filtered_messages.append(raw_pkt) diff --git a/tests/integration/framework/pretty_test_runner.py b/tests/integration/framework/pretty_test_runner.py index 72f0e141..cee9532d 100644 --- a/tests/integration/framework/pretty_test_runner.py +++ b/tests/integration/framework/pretty_test_runner.py @@ -99,15 +99,28 @@ def write_test_skip(self, reason): class PrettyTestResult(unittest.TestResult): - def __init__(self, suite, stream, logfile_stream, colorize=False): + def __init__(self, suite, stream, logdir, colorize=False): unittest.TestResult.__init__(self) self.suite = suite self.stream = stream - self.logfile = logfile_stream + self.logdir = logdir + self.logfile = None self.times = {} self.successes = [] + def createLogFile(self, test): + self.finishLogFile() + f = open(os.path.join(self.logdir, f"{get_test_name(test)}.log"), 'w') + self.logfile = ResultStream(f) + + def finishLogFile(self): + if getattr(self, 'logfile', None) is not None: + self.logfile.stream.close() + self.logfile = None + def startTest(self, test): + self.createLogFile(test) + self.logfile.write_test_name(get_test_name(test)) self.stream.write_test_name(get_test_name(test)) @@ -172,14 +185,17 @@ def __init__(self, config, stream=sys.stderr): self.results = [] self.config = config - def run(self, suite, logfile): + def run(self, suite, logdir): "Run given test suite." - result = PrettyTestResult(suite, self.stream, ResultStream(logfile)) + result = PrettyTestResult(suite, self.stream, logdir) suite_name = get_suite_name(suite) self.stream.write_suite_name(suite_name) - suite(result) + try: + suite(result) + finally: + result.finishLogFile() self.results.append(result) self.stream.write_suite_result(suite_name, result) diff --git a/tests/integration/run_tests.sh.in b/tests/integration/run_tests.sh.in index 92e34bf5..79e25ffb 100755 --- a/tests/integration/run_tests.sh.in +++ b/tests/integration/run_tests.sh.in @@ -11,23 +11,28 @@ 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/" + if [ "$1" == "-h" ]; then COMMAND="@CMAKE_CTEST_COMMAND@ -R hsm"; fi if [ $RERUNS == 0 ]; then - $COMMAND -j@NPROC@ --output-on-failure && exit 0; + $COMMAND -j@NPROC@ --output-on-failure && $CREATE_XLSX_REPORTS && exit 0; else - $COMMAND -j@NPROC@ && exit 0; + $COMMAND -j@NPROC@ && $CREATE_XLSX_REPORTS && exit 0; fi if [ $RERUNS -gt 0 ]; then if [ $RERUNS -gt 1 ]; then for i in $(seq 1 $(($RERUNS-1))); do - $COMMAND --rerun-failed && exit 0; + $COMMAND --rerun-failed && $CREATE_XLSX_REPORTS && exit 0; done; fi - $COMMAND --rerun-failed --output-on-failure && exit 0; + $COMMAND --rerun-failed --output-on-failure && $CREATE_XLSX_REPORTS && exit 0; fi +$CREATE_XLSX_REPORTS + exit 1 diff --git a/tests/integration/runtest.py b/tests/integration/runtest.py index a74c916f..5977ec51 100755 --- a/tests/integration/runtest.py +++ b/tests/integration/runtest.py @@ -88,12 +88,10 @@ def run_tests(suites, config): if suite.countTestCases() == 0: continue - log_dir = os.path.join(config.logs_path, 'test') - ensure_dir(log_dir) - log_filename = os.path.join(log_dir, '%s.log' % (get_suite_name(suite),)) + logdir = os.path.join(config.logs_path, 'test', get_suite_name(suite)) + ensure_dir(logdir) - with open(log_filename, 'w') as logfile: - test_runner.run(suite, logfile) + test_runner.run(suite, logdir) seconds_elapsed = time.time() - start_time all_tests = sum(r.testsRun for r in test_runner.results) diff --git a/tests/integration/suites/default/advanced_firmware_update.py b/tests/integration/suites/default/advanced_firmware_update.py index 58940fd5..c6e193b9 100644 --- a/tests/integration/suites/default/advanced_firmware_update.py +++ b/tests/integration/suites/default/advanced_firmware_update.py @@ -227,7 +227,7 @@ def write_firmware_and_wait_for_download(self, inst: int, self.fail('firmware still not downloaded') - def wait_until_state_is(self, inst, state, timeout_s=2): + def wait_until_state_is(self, inst, state, timeout_s=10): deadline = time.time() + timeout_s while time.time() < deadline: time.sleep(0.1) @@ -290,6 +290,16 @@ class TestWithHttpsServer(TestWithTlsServer, class TestWithCoapServer(FirmwareUpdate.TestWithCoapServerMixin, Test): pass + class TestWithCoapsServer(FirmwareUpdate.TestWithCoapsServerMixin, Test): + def setUp(self, extra_cmdline_args=None, *args, **kwargs): + extra_cmdline_args = (extra_cmdline_args or []) + ['--afu-psk-identity', + str(binascii.hexlify( + self.FW_PSK_IDENTITY), 'ascii'), + '--afu-psk-key', + str(binascii.hexlify( + self.FW_PSK_KEY), 'ascii')] + super().setUp(*args, extra_cmdline_args=extra_cmdline_args, **kwargs) + class TestWithPartialDownload(Test): GARBAGE_SIZE = 8000 @@ -341,6 +351,24 @@ def send(self, *args, **kwargs): **self.FW_PKG_OPTS)) self.fw_uri = file_server.get_resource_uri('/firmware') + class TestWithPartialCoapsDownloadAndRestart( + TestWithPartialDownloadAndRestart, + TestWithCoapsServer): + def setUp(self): + class SlowServer(coap.DtlsServer): + def send(self, *args, **kwargs): + time.sleep(0.5) + return super().send(*args, **kwargs) + + super().setUp(coap_server_class=SlowServer) + + with self.file_server as file_server: + file_server.set_resource('/firmware', + make_firmware_package( + self.FIRMWARE_SCRIPT_CONTENT, + **self.FW_PKG_OPTS)) + self.fw_uri = file_server.get_resource_uri('/firmware') + class TestWithPartialHttpDownloadAndRestart( FirmwareUpdate.TestWithPartialHttpDownloadAndRestartMixin, TestWithPartialDownloadAndRestart, @@ -411,11 +439,14 @@ def runTest(self): self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv()) + # Demo delays its reset while APP update. Wait for it and terminate if reset not occurred. + self._terminate_demo() + class AdvancedFirmwareUpdateUriTest(AdvancedFirmwareUpdate.TestWithHttpServer): - def setUp(self): - super().setUp() + def setUp(self, *args, **kwargs): + super().setUp(*args, **kwargs) self.set_check_marker(True) self.set_auto_deregister(False) self.set_reset_machine(False) @@ -431,6 +462,56 @@ def runTest(self): self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv()) + # Demo delays its reset while APP update. Wait for it and terminate if reset not occurred. + self._terminate_demo() + + +class AdvancedFirmwareUpdateUriAutoSuspend(AdvancedFirmwareUpdateUriTest): + def setUp(self): + super().setUp(extra_cmdline_args=['--fw-auto-suspend']) + + +class AdvancedFirmwareUpdateUriManualSuspend(AdvancedFirmwareUpdate.TestWithHttpServer): + def runTest(self): + self.provide_response() + + requests = list(self.requests) + firmware_uri = self.get_firmware_uri() + + self.communicate('afu-suspend') + + # Write /5/0/1 (Firmware URI) + req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI, firmware_uri) + self.serv.send(req) + self.assertMsgEqual(Lwm2mChanged.matching(req)(), + self.serv.recv()) + + # wait until client enters the DOWNLOADING state + deadline = time.time() + 20 + while time.time() < deadline: + time.sleep(0.5) + if self.read_state(Instances.APP) == UpdateState.DOWNLOADING: + break + else: + self.fail('firmware still not in DOWNLOADING state') + + time.sleep(5) + self.assertEqual(requests, self.requests) + + # resume the download + self.communicate('afu-reconnect') + + # wait until client downloads the firmware + deadline = time.time() + 20 + while time.time() < deadline: + time.sleep(0.5) + if self.read_state(Instances.APP) == UpdateState.DOWNLOADED: + break + else: + self.fail('firmware still not downloaded') + + self.assertEqual(requests + ['/firmware'], self.requests) + class AdvancedFirmwareUpdateStateChangeTest( AdvancedFirmwareUpdate.TestWithHttpServer): @@ -487,6 +568,9 @@ def runTest(self): # there should be exactly one request self.assertEqual(['/firmware'], self.requests) + # Demo delays its reset while APP update. Wait for it and terminate if reset not occurred. + self._terminate_demo() + class AdvancedFirmwareUpdateSendStateChangeTest( AdvancedFirmwareUpdate.TestWithHttpServer): @@ -736,6 +820,56 @@ def runTest(self): +class AdvancedFirmwareUpdateHttpsReconnectTest( + AdvancedFirmwareUpdate.TestWithPartialDownloadAndRestart, + AdvancedFirmwareUpdate.TestWithHttpsServer): + RESPONSE_DELAY = 0.5 + CHUNK_SIZE = 1000 + ETAGS = True + + def setUp(self): + super().setUp() + self.set_check_marker(True) + self.set_auto_deregister(False) + self.set_reset_machine(False) + + def runTest(self): + self.provide_response_app_img() + + # 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() + + self.assertEqual(len(self.requests), 1) + + self.communicate('afu-reconnect') + self.provide_response_app_img() + + # wait until client downloads the firmware + deadline = time.time() + 20 + while time.time() < deadline: + time.sleep(0.5) + + if self.read_state(Instances.APP) == UpdateState.DOWNLOADED: + break + else: + raise + + # Execute /33629/0/2 (Update) + req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update) + self.serv.send(req) + self.assertMsgEqual(Lwm2mChanged.matching(req)(), + self.serv.recv()) + + # Demo delays its reset while APP update. Wait for it and terminate if reset not occurred. + self._terminate_demo() + + class AdvancedFirmwareUpdateHttpsCancelPackageTest( AdvancedFirmwareUpdate.TestWithPartialDownload, AdvancedFirmwareUpdate.TestWithHttpServer): @@ -885,6 +1019,9 @@ def runTest(self): self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv()) + # Demo delays its reset while APP update. Wait for it and terminate if reset not occurred. + self._terminate_demo() + class AdvancedFirmwareUpdateHttpsTest( AdvancedFirmwareUpdate.TestWithHttpsServer): @@ -906,6 +1043,9 @@ def runTest(self): self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv()) + # Demo delays its reset while APP update. Wait for it and terminate if reset not occurred. + self._terminate_demo() + class AdvancedFirmwareUpdateUnconfiguredHttpsTest( AdvancedFirmwareUpdate.TestWithHttpsServer): @@ -1069,6 +1209,474 @@ def runTest(self): self.write_firmware_and_wait_for_download(Instances.APP, fw_uri) +class AdvancedFirmwareUpdateCoapsUri(AdvancedFirmwareUpdate.TestWithCoapsServer, + AdvancedFirmwareUpdateCoapUri): + pass + + +class AdvancedFirmwareUpdateCoapsUriAutoSuspend(AdvancedFirmwareUpdateCoapsUri): + def setUp(self): + super().setUp(extra_cmdline_args=['--afu-auto-suspend']) + + +class AdvancedFirmwareUpdateCoapsUriManualSuspend(AdvancedFirmwareUpdateCoapsUri): + def runTest(self): + with self.file_server as file_server: + file_server.set_resource('/firmware', + make_firmware_package(self.FIRMWARE_SCRIPT_CONTENT, + **self.FW_PKG_OPTS)) + fw_uri = file_server.get_resource_uri('/firmware') + + self.communicate('afu-suspend') + + # Write /33629/0/1 (Firmware URI) + req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI, fw_uri) + self.serv.send(req) + self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv()) + + # wait until the state machine enters the DOWNLOADING state + deadline = time.time() + 20 + while time.time() < deadline: + time.sleep(0.5) + if self.read_state(Instances.APP) == UpdateState.DOWNLOADING: + break + else: + self.fail('firmware still not in DOWNLOADING state') + + time.sleep(5) + with self.file_server as file_server: + self.assertEqual(0, len(file_server.requests)) + + # resume the download + self.communicate('afu-reconnect') + + # wait until client downloads the firmware + deadline = time.time() + 20 + while time.time() < deadline: + time.sleep(0.5) + if self.read_state(Instances.APP) == UpdateState.DOWNLOADED: + break + else: + self.fail('firmware still not downloaded') + + +class AdvancedFirmwareUpdateCoapsReconnectTest( + AdvancedFirmwareUpdate.TestWithPartialCoapsDownloadAndRestart): + def runTest(self): + # Write /33629/0/1 (Firmware URI) + req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI, self.fw_uri) + self.serv.send(req) + self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv()) + + self.wait_for_half_download() + + 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') + + deadline = time.time() + 20 + while time.time() < deadline: + time.sleep(0.5) + + if self.read_state(Instances.APP) == UpdateState.DOWNLOADED: + break + else: + raise + + +class AdvancedFirmwareUpdateCoapsSuspendDuringOfflineAndReconnectDuringOnlineTest( + AdvancedFirmwareUpdate.TestWithPartialCoapsDownloadAndRestart): + def runTest(self): + # Write /33629/0/1 (Firmware URI) + req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI, self.fw_uri) + self.serv.send(req) + self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv()) + + self.wait_for_half_download() + + with self.file_server as file_server: + # Flush any buffers + while True: + try: + file_server._server.recv(timeout_s=1) + except socket.timeout: + break + + self.communicate('enter-offline') + + with self.assertRaises(socket.timeout): + file_server._server.recv(timeout_s=5) + + self.communicate('afu-suspend') + + with self.assertRaises(socket.timeout): + file_server._server.recv(timeout_s=5) + + self.communicate('exit-offline') + + self.assertDemoRegisters() + + with self.assertRaises(socket.timeout): + file_server._server.recv(timeout_s=5) + + file_server._server.reset() + self.communicate('afu-reconnect') + self.assertPktIsDtlsClientHello( + file_server._server._raw_udp_socket.recv(65536, socket.MSG_PEEK)) + + deadline = time.time() + 20 + while time.time() < deadline: + time.sleep(0.5) + + if self.read_state(Instances.APP) == UpdateState.DOWNLOADED: + break + else: + raise + + +class AdvancedFirmwareUpdateCoapsSuspendDuringOfflineAndReconnectDuringOfflineTest( + AdvancedFirmwareUpdate.TestWithPartialCoapsDownloadAndRestart): + def runTest(self): + # Write /33629/0/1 (Firmware URI) + req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI, self.fw_uri) + self.serv.send(req) + self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv()) + + self.wait_for_half_download() + + with self.file_server as file_server: + # Flush any buffers + while True: + try: + file_server._server.recv(timeout_s=1) + except socket.timeout: + break + + self.communicate('enter-offline') + + with self.assertRaises(socket.timeout): + file_server._server.recv(timeout_s=5) + + self.communicate('afu-suspend') + + with self.assertRaises(socket.timeout): + file_server._server.recv(timeout_s=5) + + self.communicate('afu-reconnect') + + with self.assertRaises(socket.timeout): + file_server._server.recv(timeout_s=5) + + file_server._server.reset() + self.communicate('exit-offline') + self.assertPktIsDtlsClientHello( + file_server._server._raw_udp_socket.recv(65536, socket.MSG_PEEK)) + + self.assertDemoRegisters() + + deadline = time.time() + 20 + while time.time() < deadline: + time.sleep(0.5) + + if self.read_state(Instances.APP) == UpdateState.DOWNLOADED: + break + else: + raise + + +class AdvancedFirmwareUpdateCoapsOfflineDuringSuspendAndReconnectDuringOnlineTest( + AdvancedFirmwareUpdate.TestWithPartialCoapsDownloadAndRestart): + def runTest(self): + # Write /33629/0/1 (Firmware URI) + req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI, self.fw_uri) + self.serv.send(req) + self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv()) + + self.wait_for_half_download() + + with self.file_server as file_server: + # Flush any buffers + while True: + try: + file_server._server.recv(timeout_s=1) + except socket.timeout: + break + + self.communicate('afu-suspend') + + with self.assertRaises(socket.timeout): + file_server._server.recv(timeout_s=5) + + self.communicate('enter-offline') + + with self.assertRaises(socket.timeout): + file_server._server.recv(timeout_s=5) + + self.communicate('exit-offline') + + self.assertDemoRegisters() + + with self.assertRaises(socket.timeout): + file_server._server.recv(timeout_s=5) + + file_server._server.reset() + self.communicate('afu-reconnect') + self.assertPktIsDtlsClientHello( + file_server._server._raw_udp_socket.recv(65536, socket.MSG_PEEK)) + + deadline = time.time() + 20 + while time.time() < deadline: + time.sleep(0.5) + + if self.read_state(Instances.APP) == UpdateState.DOWNLOADED: + break + else: + raise + + +class AdvancedFirmwareUpdateCoapsOfflineDuringSuspendAndReconnectDuringOfflineTest( + AdvancedFirmwareUpdate.TestWithPartialCoapsDownloadAndRestart): + def runTest(self): + # Write /33629/0/1 (Firmware URI) + req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI, self.fw_uri) + self.serv.send(req) + self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv()) + + self.wait_for_half_download() + + with self.file_server as file_server: + # Flush any buffers + while True: + try: + file_server._server.recv(timeout_s=1) + except socket.timeout: + break + + self.communicate('afu-suspend') + + with self.assertRaises(socket.timeout): + file_server._server.recv(timeout_s=5) + + self.communicate('enter-offline') + + with self.assertRaises(socket.timeout): + file_server._server.recv(timeout_s=5) + + self.communicate('afu-reconnect') + + with self.assertRaises(socket.timeout): + file_server._server.recv(timeout_s=5) + + file_server._server.reset() + self.communicate('exit-offline') + self.assertPktIsDtlsClientHello( + file_server._server._raw_udp_socket.recv(65536, socket.MSG_PEEK)) + + self.assertDemoRegisters() + + deadline = time.time() + 20 + while time.time() < deadline: + time.sleep(0.5) + + if self.read_state(Instances.APP) == UpdateState.DOWNLOADED: + break + else: + raise + + +class AdvancedFirmwareUpdateHttpSuspendDuringOfflineAndReconnectDuringOnlineTest( + AdvancedFirmwareUpdate.TestWithPartialHttpDownloadAndRestart): + 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() + + self.communicate('enter-offline') + + time.sleep(5) + fsize = os.stat(self.fw_file_name).st_size + self.assertEqual(len(self.requests), 1) + self.provide_response() + + self.communicate('afu-suspend') + + time.sleep(5) + self.assertEqual(os.stat(self.fw_file_name).st_size, fsize) + self.assertEqual(len(self.requests), 1) + + self.communicate('exit-offline') + + self.assertDemoRegisters() + + time.sleep(5) + self.assertEqual(os.stat(self.fw_file_name).st_size, fsize) + self.assertEqual(len(self.requests), 1) + + self.communicate('afu-reconnect') + + deadline = time.time() + 20 + while time.time() < deadline: + time.sleep(0.5) + + if self.read_state(Instances.APP) == UpdateState.DOWNLOADED: + break + else: + raise + + self.assertEqual(len(self.requests), 2) + + +class AdvancedFirmwareUpdateHttpSuspendDuringOfflineAndReconnectDuringOfflineTest( + AdvancedFirmwareUpdate.TestWithPartialHttpDownloadAndRestart): + 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() + + self.communicate('enter-offline') + + time.sleep(5) + fsize = os.stat(self.fw_file_name).st_size + self.assertEqual(len(self.requests), 1) + self.provide_response() + + self.communicate('afu-suspend') + + time.sleep(5) + self.assertEqual(os.stat(self.fw_file_name).st_size, fsize) + self.assertEqual(len(self.requests), 1) + + self.communicate('afu-reconnect') + + time.sleep(5) + self.assertEqual(os.stat(self.fw_file_name).st_size, fsize) + self.assertEqual(len(self.requests), 1) + + self.communicate('exit-offline') + + self.assertDemoRegisters() + + deadline = time.time() + 20 + while time.time() < deadline: + time.sleep(0.5) + + if self.read_state(Instances.APP) == UpdateState.DOWNLOADED: + break + else: + raise + + self.assertEqual(len(self.requests), 2) + + +class AdvancedFirmwareUpdateHttpOfflineDuringSuspendAndReconnectDuringOnlineTest( + AdvancedFirmwareUpdate.TestWithPartialHttpDownloadAndRestart): + 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() + + self.communicate('afu-suspend') + + time.sleep(5) + fsize = os.stat(self.fw_file_name).st_size + self.assertEqual(len(self.requests), 1) + self.provide_response() + + self.communicate('enter-offline') + + time.sleep(5) + self.assertEqual(os.stat(self.fw_file_name).st_size, fsize) + self.assertEqual(len(self.requests), 1) + + self.communicate('exit-offline') + + self.assertDemoRegisters() + + time.sleep(5) + self.assertEqual(os.stat(self.fw_file_name).st_size, fsize) + self.assertEqual(len(self.requests), 1) + + self.communicate('afu-reconnect') + + deadline = time.time() + 20 + while time.time() < deadline: + time.sleep(0.5) + + if self.read_state(Instances.APP) == UpdateState.DOWNLOADED: + break + else: + raise + + self.assertEqual(len(self.requests), 2) + + +class AdvancedFirmwareUpdateHttpOfflineDuringSuspendAndReconnectDuringOfflineTest( + AdvancedFirmwareUpdate.TestWithPartialHttpDownloadAndRestart): + 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() + + self.communicate('afu-suspend') + + time.sleep(5) + fsize = os.stat(self.fw_file_name).st_size + self.assertEqual(len(self.requests), 1) + self.provide_response() + + self.communicate('enter-offline') + + time.sleep(5) + self.assertEqual(os.stat(self.fw_file_name).st_size, fsize) + self.assertEqual(len(self.requests), 1) + + self.communicate('afu-reconnect') + + time.sleep(5) + self.assertEqual(os.stat(self.fw_file_name).st_size, fsize) + self.assertEqual(len(self.requests), 1) + + self.communicate('exit-offline') + + self.assertDemoRegisters() + + deadline = time.time() + 20 + while time.time() < deadline: + time.sleep(0.5) + + if self.read_state(Instances.APP) == UpdateState.DOWNLOADED: + break + else: + raise + + self.assertEqual(len(self.requests), 2) + + class AdvancedFirmwareUpdateRestartWithDownloaded(AdvancedFirmwareUpdate.Test): def setUp(self): super().setUp() @@ -1106,6 +1714,9 @@ def runTest(self): self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv()) + # Demo delays its reset while APP update. Wait for it and terminate if reset not occurred. + self._terminate_demo() + @@ -1382,6 +1993,9 @@ def runTest(self): self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv()) + # Demo delays its reset while APP update. Wait for it and terminate if reset not occurred. + self._terminate_demo() + class AdvancedFirmwareUpdateWeakEtagTest( AdvancedFirmwareUpdate.TestWithHttpServer): @@ -1415,6 +2029,9 @@ def runTest(self): self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv()) + # Demo delays its reset while APP update. Wait for it and terminate if reset not occurred. + self._terminate_demo() + class SameSocketDownload: class Test(test_suite.Lwm2mDtlsSingleServerTest, @@ -2305,6 +2922,9 @@ def runTest(self): self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv()) + # Demo delays its reset while APP update. Wait for it and terminate if reset not occurred. + self._terminate_demo() + class AdvancedFirmwareUpdateUriTestTwoNotLinkedImages( AdvancedFirmwareUpdate.TestWithHttpServer): @@ -2348,6 +2968,9 @@ def runTest(self): self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv()) + # Demo delays its reset while APP update. Wait for it and terminate if reset not occurred. + self._terminate_demo() + class AdvancedFirmwareUpdatePackageTestFourNotLinkedImages( AdvancedFirmwareUpdate.Test): @@ -2439,6 +3062,9 @@ def runTest(self): self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv()) + # Demo delays its reset while APP update. Wait for it and terminate if reset not occurred. + self._terminate_demo() + class AdvancedFirmwareUpdateUriTestFourNotLinkedImages( AdvancedFirmwareUpdate.TestWithHttpServer): @@ -2507,6 +3133,10 @@ def runTest(self): self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv()) + # Demo delays its reset while APP update. Wait for it and terminate if reset not occurred. + self._terminate_demo() + + class AdvancedFirmwareUpdateUriTestFourNotLinkedImagesAPPFirst( AdvancedFirmwareUpdate.TestWithHttpServer): def setUp(self): @@ -3057,6 +3687,9 @@ def runTest(self): self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv()) + # Demo delays its reset while APP update. Wait for it and terminate if reset not occurred. + self._terminate_demo() + class AdvancedFirmwareUpdatePackageTestWithMultiPackageAllImages( AdvancedFirmwareUpdate.Test): @@ -3151,6 +3784,9 @@ def runTest(self): self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv()) + # Demo delays its reset while APP update. Wait for it and terminate if reset not occurred. + self._terminate_demo() + class AdvancedFirmwareUpdatePackageTestWithMultiPackageConflictingDownloads( AdvancedFirmwareUpdate.Test): @@ -3698,3 +4334,64 @@ def runTest(self): # Both instances still should end with DOWNLOADED state self.wait_until_state_is(Instances.APP, UpdateState.DOWNLOADED) self.wait_until_state_is(Instances.TEE, UpdateState.DOWNLOADED) + + +class AdvancedFirmwareUpdateForceAppToUpdateFirstAndCheckProperStateOfAdditionalImgAfterReboot( + AdvancedFirmwareUpdate.TestWithHttpServer): + def setUp(self): + super().setUp() + self.set_check_marker(False) + self.set_auto_deregister(True) + self.set_reset_machine(False) + + def runTest(self): + # Prepare package for /33629/0 + self.FW_PKG_OPTS = {'magic': b'AJAY_APP', 'version': 2} + self.provide_response_app_img(use_real_app=True) + + # Write /33629/0/0 (Firmware) + self.write_firmware_and_wait_for_download(Instances.APP, + self.get_firmware_uri()) + + # Check /33629/0 state and result + self.assertEqual(UpdateState.DOWNLOADED, self.read_state(Instances.APP)) + self.assertEqual(UpdateResult.INITIAL, + self.read_update_result(Instances.APP)) + + # Prepare package for /33629/1 + self.FW_PKG_OPTS = {'magic': b'AJAY_TEE', 'version': 2} + self.prepare_package_additional_img(content=DUMMY_FILE) + + # Write /33629/1/0 (Firmware) + req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.TEE].Package, + self.PACKAGE, + format=coap.ContentFormat.APPLICATION_OCTET_STREAM) + self.serv.send(req) + self.assertMsgEqual(Lwm2mChanged.matching(req)(), + self.serv.recv()) + + # Check /33629/1 state and result + self.assertEqual(UpdateState.DOWNLOADED, self.read_state(Instances.TEE)) + self.assertEqual(UpdateResult.INITIAL, + self.read_update_result(Instances.TEE)) + + # Execute /33629/1/2 (Update) + req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.TEE].Update, + content=b'0=\'\'') + self.serv.send(req) + self.assertMsgEqual(Lwm2mChanged.matching(req)(), + self.serv.recv()) + + # Demo should reboot, it needs new server + self.serv.reset() + self.assertDemoRegisters() + + # After init state should be IDLE in both instances + self.wait_until_state_is(Instances.APP, UpdateState.IDLE) + self.wait_until_state_is(Instances.TEE, UpdateState.IDLE) + + # Check both results + self.assertEqual(UpdateResult.SUCCESS, + self.read_update_result(Instances.APP)) + self.assertEqual(UpdateResult.SUCCESS, + self.read_update_result(Instances.TEE)) diff --git a/tests/integration/suites/default/async.py b/tests/integration/suites/default/async.py index 23abf3c4..382e9a8b 100644 --- a/tests/integration/suites/default/async.py +++ b/tests/integration/suites/default/async.py @@ -27,6 +27,43 @@ def runTest(self): Lwm2mCreated.matching(register)(location=self.DEFAULT_REGISTER_ENDPOINT)) +class NoDmChangeDuringRegistration(test_suite.Lwm2mSingleServerTest): + def setUp(self): + super().setUp(auto_register=False) + + def runTest(self): + register1 = self.assertDemoRegisters(respond=False) + + # This will schedule a reload, which should be a no-op + self.communicate('exit-offline') + + register2 = self.assertDemoRegisters() + # assert that what we received is a retransmission, not a new register attempt + self.assertMsgEqual(register1, register2) + + +class QueueModeChangeDuringRegistration(test_suite.Lwm2mSingleServerTest): + def setUp(self): + super().setUp(auto_register=False, maximum_version='1.1') + + def runTest(self): + register1 = self.assertDemoRegisters(version='1.1', respond=False) + + self.communicate('set-queue-mode-preference PREFER_QUEUE_MODE') + # This will schedule a reload + self.communicate('exit-offline') + + register2 = self.assertDemoRegisters(version='1.1', respond=False, lwm2m11_queue_mode=True) + self.assertNotEqual(register1.token, register2.token) + + # This will schedule another reload, which should be a no-op + self.communicate('exit-offline') + + register3 = self.assertDemoRegisters(version='1.1', lwm2m11_queue_mode=True) + # assert that what we received is a retransmission, not a new register attempt + self.assertMsgEqual(register2, register3) + + class DmChangeDuringUpdate(test_suite.Lwm2mTest): def setUp(self): super().setUp(servers=2, num_servers_passed=1) diff --git a/tests/integration/suites/default/firmware_update.py b/tests/integration/suites/default/firmware_update.py index 3fb4a510..1aa4159f 100644 --- a/tests/integration/suites/default/firmware_update.py +++ b/tests/integration/suites/default/firmware_update.py @@ -393,6 +393,23 @@ def tearDown(self): class TestWithCoapServer(TestWithCoapServerMixin, Test): pass + class TestWithCoapsServerMixin(TestWithCoapServerMixin): + FW_PSK_IDENTITY = b'fw-psk-identity' + FW_PSK_KEY = b'fw-psk-key' + + def setUp(self, coap_server_class=coap.DtlsServer, extra_cmdline_args=None, *args, **kwargs): + extra_cmdline_args = (extra_cmdline_args or []) + ['--fw-psk-identity', + str(binascii.hexlify( + self.FW_PSK_IDENTITY), 'ascii'), + '--fw-psk-key', str(binascii.hexlify( + self.FW_PSK_KEY), 'ascii')] + super().setUp(*args, coap_server=coap_server_class(psk_identity=self.FW_PSK_IDENTITY, + psk_key=self.FW_PSK_KEY), + extra_cmdline_args=extra_cmdline_args, **kwargs) + + class TestWithCoapsServer(TestWithCoapsServerMixin, Test): + pass + class DemoArgsExtractorMixin: def _get_valgrind_args(self): # these tests call demo_process.kill(), so Valgrind is not really @@ -454,6 +471,21 @@ def send(self, *args, **kwargs): make_firmware_package(self.FIRMWARE_SCRIPT_CONTENT)) self.fw_uri = file_server.get_resource_uri('/firmware') + class TestWithPartialCoapsDownloadAndRestart(TestWithPartialDownloadAndRestart, + TestWithCoapsServer): + def setUp(self): + class SlowServer(coap.DtlsServer): + def send(self, *args, **kwargs): + time.sleep(0.5) + return super().send(*args, **kwargs) + + super().setUp(coap_server_class=SlowServer) + + with self.file_server as file_server: + file_server.set_resource('/firmware', + make_firmware_package(self.FIRMWARE_SCRIPT_CONTENT)) + self.fw_uri = file_server.get_resource_uri('/firmware') + class TestWithPartialHttpDownloadAndRestartMixin: def get_etag(self, response_content): return '"%d"' % zlib.crc32(response_content) @@ -554,8 +586,8 @@ def runTest(self): class FirmwareUpdateUriTest(FirmwareUpdate.TestWithHttpServer): - def setUp(self): - super().setUp() + def setUp(self, *args, **kwargs): + super().setUp(*args, **kwargs) self.set_check_marker(True) self.set_auto_deregister(False) self.set_reset_machine(False) @@ -571,6 +603,53 @@ def runTest(self): self.serv.recv()) +class FirmwareUpdateUriAutoSuspend(FirmwareUpdateUriTest): + def setUp(self): + super().setUp(extra_cmdline_args=['--fw-auto-suspend']) + + +class FirmwareUpdateUriManualSuspend(FirmwareUpdate.TestWithHttpServer): + def runTest(self): + self.provide_response() + + requests = list(self.requests) + firmware_uri = self.get_firmware_uri() + + self.communicate('fw-update-suspend') + + # Write /5/0/1 (Firmware URI) + req = Lwm2mWrite(ResPath.FirmwareUpdate.PackageURI, firmware_uri) + self.serv.send(req) + self.assertMsgEqual(Lwm2mChanged.matching(req)(), + self.serv.recv()) + + # wait until client enters the DOWNLOADING state + deadline = time.time() + 20 + while time.time() < deadline: + time.sleep(0.5) + if self.read_state() == UpdateState.DOWNLOADING: + break + else: + self.fail('firmware still not in DOWNLOADING state') + + time.sleep(5) + self.assertEqual(requests, self.requests) + + # resume the download + self.communicate('fw-update-reconnect') + + # wait until client downloads the firmware + deadline = time.time() + 20 + while time.time() < deadline: + time.sleep(0.5) + if self.read_state() == UpdateState.DOWNLOADED: + break + else: + self.fail('firmware still not downloaded') + + self.assertEqual(requests + ['/firmware'], self.requests) + + class FirmwareUpdateStateChangeTest(FirmwareUpdate.TestWithHttpServer): def setUp(self): super().setUp() @@ -877,6 +956,52 @@ def runTest(self): self.serv.recv()) +class FirmwareUpdateHttpsReconnectTest(FirmwareUpdate.TestWithPartialDownloadAndRestart, + FirmwareUpdate.TestWithHttpsServer): + RESPONSE_DELAY = 0.5 + CHUNK_SIZE = 1000 + ETAGS = True + + def setUp(self): + super().setUp() + self.set_check_marker(True) + self.set_auto_deregister(False) + self.set_reset_machine(False) + + 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() + + self.assertEqual(len(self.requests), 1) + + self.communicate('fw-update-reconnect') + self.provide_response() + + deadline = time.time() + 20 + while time.time() < deadline: + time.sleep(0.5) + + if self.read_state() == UpdateState.DOWNLOADED: + break + else: + raise + + self.assertEqual(len(self.requests), 2) + + # Execute /5/0/2 (Update) + req = Lwm2mExecute(ResPath.FirmwareUpdate.Update) + self.serv.send(req) + self.assertMsgEqual(Lwm2mChanged.matching(req)(), + self.serv.recv()) + + class FirmwareUpdateHttpsCancelPackageTest(FirmwareUpdate.TestWithPartialDownload, FirmwareUpdate.TestWithHttpServer): RESPONSE_DELAY = 0.5 @@ -1174,6 +1299,478 @@ def runTest(self): self.write_firmware_and_wait_for_download(fw_uri) +class FirmwareUpdateCoapsUri(FirmwareUpdate.TestWithCoapsServer, FirmwareUpdateCoapUri): + pass + + +class FirmwareUpdateCoapsUriAutoSuspend(FirmwareUpdateCoapsUri): + def setUp(self): + super().setUp(extra_cmdline_args=['--fw-auto-suspend']) + + +class FirmwareUpdateCoapsUriManualSuspend(FirmwareUpdateCoapsUri): + def runTest(self): + with self.file_server as file_server: + file_server.set_resource('/firmware', + make_firmware_package(self.FIRMWARE_SCRIPT_CONTENT)) + fw_uri = file_server.get_resource_uri('/firmware') + + self.communicate('fw-update-suspend') + + # Write /5/0/1 (Firmware URI) + req = Lwm2mWrite(ResPath.FirmwareUpdate.PackageURI, fw_uri) + self.serv.send(req) + self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv()) + + # wait until the state machine enters the DOWNLOADING state + deadline = time.time() + 20 + while time.time() < deadline: + time.sleep(0.5) + if self.read_state() == UpdateState.DOWNLOADING: + break + else: + self.fail('firmware still not in DOWNLOADING state') + + time.sleep(5) + with self.file_server as file_server: + self.assertEqual(0, len(file_server.requests)) + + # resume the download + self.communicate('fw-update-reconnect') + + # wait until client downloads the firmware + deadline = time.time() + 20 + while time.time() < deadline: + time.sleep(0.5) + if self.read_state() == UpdateState.DOWNLOADED: + break + else: + self.fail('firmware still not downloaded') + + +class FirmwareUpdateCoapsReconnectTest(FirmwareUpdate.TestWithPartialCoapsDownloadAndRestart): + def setUp(self): + super().setUp() + self.set_check_marker(True) + self.set_auto_deregister(False) + self.set_reset_machine(False) + + def runTest(self): + # Write /5/0/1 (Firmware URI) + req = Lwm2mWrite(ResPath.FirmwareUpdate.PackageURI, self.fw_uri) + self.serv.send(req) + self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv()) + + self.wait_for_half_download() + + 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') + + deadline = time.time() + 20 + while time.time() < deadline: + time.sleep(0.5) + + if self.read_state() == UpdateState.DOWNLOADED: + break + else: + raise + + # Execute /5/0/2 (Update) + req = Lwm2mExecute(ResPath.FirmwareUpdate.Update) + self.serv.send(req) + self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv()) + + +class FirmwareUpdateCoapsSuspendDuringOfflineAndReconnectDuringOnlineTest( + FirmwareUpdate.TestWithPartialCoapsDownloadAndRestart): + def runTest(self): + # Write /5/0/1 (Firmware URI) + req = Lwm2mWrite(ResPath.FirmwareUpdate.PackageURI, self.fw_uri) + self.serv.send(req) + self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv()) + + self.wait_for_half_download() + + with self.file_server as file_server: + # Flush any buffers + while True: + try: + file_server._server.recv(timeout_s=1) + except socket.timeout: + break + + self.communicate('enter-offline') + + with self.assertRaises(socket.timeout): + file_server._server.recv(timeout_s=5) + + self.communicate('fw-update-suspend') + + with self.assertRaises(socket.timeout): + file_server._server.recv(timeout_s=5) + + self.communicate('exit-offline') + + self.assertDemoRegisters() + + with self.assertRaises(socket.timeout): + file_server._server.recv(timeout_s=5) + + file_server._server.reset() + self.communicate('fw-update-reconnect') + self.assertPktIsDtlsClientHello( + file_server._server._raw_udp_socket.recv(65536, socket.MSG_PEEK)) + + deadline = time.time() + 20 + while time.time() < deadline: + time.sleep(0.5) + + if self.read_state() == UpdateState.DOWNLOADED: + break + else: + raise + + +class FirmwareUpdateCoapsSuspendDuringOfflineAndReconnectDuringOfflineTest( + FirmwareUpdate.TestWithPartialCoapsDownloadAndRestart): + def runTest(self): + # Write /5/0/1 (Firmware URI) + req = Lwm2mWrite(ResPath.FirmwareUpdate.PackageURI, self.fw_uri) + self.serv.send(req) + self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv()) + + self.wait_for_half_download() + + with self.file_server as file_server: + # Flush any buffers + while True: + try: + file_server._server.recv(timeout_s=1) + except socket.timeout: + break + + self.communicate('enter-offline') + + with self.assertRaises(socket.timeout): + file_server._server.recv(timeout_s=5) + + self.communicate('fw-update-suspend') + + with self.assertRaises(socket.timeout): + file_server._server.recv(timeout_s=5) + + self.communicate('fw-update-reconnect') + + with self.assertRaises(socket.timeout): + file_server._server.recv(timeout_s=5) + + file_server._server.reset() + self.communicate('exit-offline') + self.assertPktIsDtlsClientHello( + file_server._server._raw_udp_socket.recv(65536, socket.MSG_PEEK)) + + self.assertDemoRegisters() + + deadline = time.time() + 20 + while time.time() < deadline: + time.sleep(0.5) + + if self.read_state() == UpdateState.DOWNLOADED: + break + else: + raise + + +class FirmwareUpdateCoapsOfflineDuringSuspendAndReconnectDuringOnlineTest( + FirmwareUpdate.TestWithPartialCoapsDownloadAndRestart): + def runTest(self): + # Write /5/0/1 (Firmware URI) + req = Lwm2mWrite(ResPath.FirmwareUpdate.PackageURI, self.fw_uri) + self.serv.send(req) + self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv()) + + self.wait_for_half_download() + + with self.file_server as file_server: + # Flush any buffers + while True: + try: + file_server._server.recv(timeout_s=1) + except socket.timeout: + break + + self.communicate('fw-update-suspend') + + with self.assertRaises(socket.timeout): + file_server._server.recv(timeout_s=5) + + self.communicate('enter-offline') + + with self.assertRaises(socket.timeout): + file_server._server.recv(timeout_s=5) + + self.communicate('exit-offline') + + self.assertDemoRegisters() + + with self.assertRaises(socket.timeout): + file_server._server.recv(timeout_s=5) + + file_server._server.reset() + self.communicate('fw-update-reconnect') + self.assertPktIsDtlsClientHello( + file_server._server._raw_udp_socket.recv(65536, socket.MSG_PEEK)) + + deadline = time.time() + 20 + while time.time() < deadline: + time.sleep(0.5) + + if self.read_state() == UpdateState.DOWNLOADED: + break + else: + raise + + +class FirmwareUpdateCoapsOfflineDuringSuspendAndReconnectDuringOfflineTest( + FirmwareUpdate.TestWithPartialCoapsDownloadAndRestart): + def runTest(self): + # Write /5/0/1 (Firmware URI) + req = Lwm2mWrite(ResPath.FirmwareUpdate.PackageURI, self.fw_uri) + self.serv.send(req) + self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv()) + + self.wait_for_half_download() + + with self.file_server as file_server: + # Flush any buffers + while True: + try: + file_server._server.recv(timeout_s=1) + except socket.timeout: + break + + self.communicate('fw-update-suspend') + + with self.assertRaises(socket.timeout): + file_server._server.recv(timeout_s=5) + + self.communicate('enter-offline') + + with self.assertRaises(socket.timeout): + file_server._server.recv(timeout_s=5) + + self.communicate('fw-update-reconnect') + + with self.assertRaises(socket.timeout): + file_server._server.recv(timeout_s=5) + + file_server._server.reset() + self.communicate('exit-offline') + self.assertPktIsDtlsClientHello( + file_server._server._raw_udp_socket.recv(65536, socket.MSG_PEEK)) + + self.assertDemoRegisters() + + deadline = time.time() + 20 + while time.time() < deadline: + time.sleep(0.5) + + if self.read_state() == UpdateState.DOWNLOADED: + break + else: + raise + + +class FirmwareUpdateHttpSuspendDuringOfflineAndReconnectDuringOnlineTest( + FirmwareUpdate.TestWithPartialHttpDownloadAndRestart): + 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() + + self.communicate('enter-offline') + + time.sleep(5) + fsize = os.stat(self.fw_file_name).st_size + self.assertEqual(len(self.requests), 1) + self.provide_response() + + self.communicate('fw-update-suspend') + + time.sleep(5) + self.assertEqual(os.stat(self.fw_file_name).st_size, fsize) + self.assertEqual(len(self.requests), 1) + + self.communicate('exit-offline') + + self.assertDemoRegisters() + + time.sleep(5) + self.assertEqual(os.stat(self.fw_file_name).st_size, fsize) + self.assertEqual(len(self.requests), 1) + + self.communicate('fw-update-reconnect') + + deadline = time.time() + 20 + while time.time() < deadline: + time.sleep(0.5) + + if self.read_state() == UpdateState.DOWNLOADED: + break + else: + raise + + self.assertEqual(len(self.requests), 2) + + +class FirmwareUpdateHttpSuspendDuringOfflineAndReconnectDuringOfflineTest( + FirmwareUpdate.TestWithPartialHttpDownloadAndRestart): + 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() + + self.communicate('enter-offline') + + time.sleep(5) + fsize = os.stat(self.fw_file_name).st_size + self.assertEqual(len(self.requests), 1) + self.provide_response() + + self.communicate('fw-update-suspend') + + time.sleep(5) + self.assertEqual(os.stat(self.fw_file_name).st_size, fsize) + self.assertEqual(len(self.requests), 1) + + self.communicate('fw-update-reconnect') + + time.sleep(5) + self.assertEqual(os.stat(self.fw_file_name).st_size, fsize) + self.assertEqual(len(self.requests), 1) + + self.communicate('exit-offline') + + self.assertDemoRegisters() + + deadline = time.time() + 20 + while time.time() < deadline: + time.sleep(0.5) + + if self.read_state() == UpdateState.DOWNLOADED: + break + else: + raise + + self.assertEqual(len(self.requests), 2) + + +class FirmwareUpdateHttpOfflineDuringSuspendAndReconnectDuringOnlineTest( + FirmwareUpdate.TestWithPartialHttpDownloadAndRestart): + 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() + + self.communicate('fw-update-suspend') + + time.sleep(5) + fsize = os.stat(self.fw_file_name).st_size + self.assertEqual(len(self.requests), 1) + self.provide_response() + + self.communicate('enter-offline') + + time.sleep(5) + self.assertEqual(os.stat(self.fw_file_name).st_size, fsize) + self.assertEqual(len(self.requests), 1) + + self.communicate('exit-offline') + + self.assertDemoRegisters() + + time.sleep(5) + self.assertEqual(os.stat(self.fw_file_name).st_size, fsize) + self.assertEqual(len(self.requests), 1) + + self.communicate('fw-update-reconnect') + + deadline = time.time() + 20 + while time.time() < deadline: + time.sleep(0.5) + + if self.read_state() == UpdateState.DOWNLOADED: + break + else: + raise + + self.assertEqual(len(self.requests), 2) + + +class FirmwareUpdateHttpOfflineDuringSuspendAndReconnectDuringOfflineTest( + FirmwareUpdate.TestWithPartialHttpDownloadAndRestart): + 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() + + self.communicate('fw-update-suspend') + + time.sleep(5) + fsize = os.stat(self.fw_file_name).st_size + self.assertEqual(len(self.requests), 1) + self.provide_response() + + self.communicate('enter-offline') + + time.sleep(5) + self.assertEqual(os.stat(self.fw_file_name).st_size, fsize) + self.assertEqual(len(self.requests), 1) + + self.communicate('fw-update-reconnect') + + time.sleep(5) + self.assertEqual(os.stat(self.fw_file_name).st_size, fsize) + self.assertEqual(len(self.requests), 1) + + self.communicate('exit-offline') + + self.assertDemoRegisters() + + deadline = time.time() + 20 + while time.time() < deadline: + time.sleep(0.5) + + if self.read_state() == UpdateState.DOWNLOADED: + break + else: + raise + + self.assertEqual(len(self.requests), 2) + + class FirmwareUpdateRestartWithDownloaded(FirmwareUpdate.Test): def setUp(self): super().setUp() diff --git a/tests/integration/suites/default/notifications.py b/tests/integration/suites/default/notifications.py index de8a1156..df4b1258 100644 --- a/tests/integration/suites/default/notifications.py +++ b/tests/integration/suites/default/notifications.py @@ -189,3 +189,38 @@ def runTest(self): # Check that no notifications are sent with self.assertRaises(socket.timeout): print(self.serv.recv(timeout_s=5)) + + +class UdpNotificationErrorTest(test_suite.Lwm2mSingleServerTest, test_suite.Lwm2mDmOperations): + def setUp(self): + super().setUp(extra_cmdline_args=['--confirmable-notifications']) + + def runTest(self): + self.write_resource(self.serv, oid=OID.Server, iid=1, rid=RID.Server.DefaultMaxPeriod, + content=b'2') + + self.create_instance(self.serv, oid=OID.Test, iid=1) + orig_notif = self.observe(self.serv, oid=OID.Test, iid=1, rid=RID.Test.Counter) + self.delete_instance(self.serv, oid=OID.Test, iid=1) + + notif = self.serv.recv(timeout_s=5) + self.assertEqual(notif.code, coap.Code.RES_NOT_FOUND) + self.assertEqual(notif.token, orig_notif.token) + self.serv.send(Lwm2mEmpty.matching(notif)()) + + +class TcpNotificationErrorTest(test_suite.Lwm2mSingleTcpServerTest, test_suite.Lwm2mDmOperations): + def setUp(self): + super().setUp(extra_cmdline_args=['--confirmable-notifications'], binding='T') + + def runTest(self): + self.write_resource(self.serv, oid=OID.Server, iid=1, rid=RID.Server.DefaultMaxPeriod, + content=b'2') + + self.create_instance(self.serv, oid=OID.Test, iid=1) + orig_notif = self.observe(self.serv, oid=OID.Test, iid=1, rid=RID.Test.Counter) + self.delete_instance(self.serv, oid=OID.Test, iid=1) + + notif = self.serv.recv(timeout_s=5) + self.assertEqual(notif.code, coap.Code.RES_NOT_FOUND) + self.assertEqual(notif.token, orig_notif.token) diff --git a/tools/anjay_codegen.py b/tools/anjay_codegen.py index 2c8518dd..ca166c9f 100755 --- a/tools/anjay_codegen.py +++ b/tools/anjay_codegen.py @@ -148,7 +148,6 @@ anjay_iid_t iid) { (void) anjay; {{ obj_repr_type }} *obj = get_obj(obj_ptr); - assert(obj); return add_instance(obj, iid) ? 0 : ANJAY_ERR_INTERNAL; } @@ -158,7 +157,6 @@ anjay_iid_t iid) { (void) anjay; {{ obj_repr_type }} *obj = get_obj(obj_ptr); - assert(obj); AVS_LIST({{ obj_inst_type }}) *it; AVS_LIST_FOREACH_PTR(it, &obj->instances) { @@ -186,7 +184,6 @@ {% endif %} {{ obj_repr_type }} *obj = get_obj(obj_ptr); - assert(obj); {% if obj.multiple %} {{ obj_inst_type }} *inst = find_instance(obj, iid); assert(inst); @@ -227,7 +224,6 @@ {% endif %} {{ obj_repr_type }} *obj = get_obj(obj_ptr); - assert(obj); {% if obj.multiple %} {{ obj_inst_type }} *inst = find_instance(obj, iid); assert(inst); @@ -261,7 +257,6 @@ {% endif %} {{ obj_repr_type }} *obj = get_obj(obj_ptr); - assert(obj); {% if obj.multiple %} {{ obj_inst_type }} *inst = find_instance(obj, iid); assert(inst); @@ -295,7 +290,6 @@ (void) arg_ctx; {{ obj_repr_type }} *obj = get_obj(obj_ptr); - assert(obj); {% if obj.multiple %} {{ obj_inst_type }} *inst = find_instance(obj, iid); assert(inst); @@ -328,7 +322,6 @@ {% endif %} {{ obj_repr_type }} *obj = get_obj(obj_ptr); - assert(obj); {% if obj.multiple %} {{ obj_inst_type }} *inst = find_instance(obj, iid); assert(inst); @@ -362,7 +355,6 @@ {% endif %} {{ obj_repr_type }} *obj = get_obj(obj_ptr); - assert(obj); {% if obj.multiple %} {{ obj_inst_type }} *inst = find_instance(obj, iid); assert(inst); @@ -502,7 +494,6 @@ {% endif %} {{ obj_repr_type }} *obj = get_obj(obj_ptr); - assert(obj); {% if obj.multiple %} assert(iid < AVS_ARRAY_SIZE(obj->instances)); {{ obj_inst_type }} *inst = &obj->instances[iid]; @@ -545,7 +536,6 @@ {% endif %} {{ obj_repr_type }} *obj = get_obj(obj_ptr); - assert(obj); {% if obj.multiple %} assert(iid < AVS_ARRAY_SIZE(obj->instances)); {{ obj_inst_type }} *inst = &obj->instances[iid]; @@ -579,7 +569,6 @@ {% endif %} {{ obj_repr_type }} *obj = get_obj(obj_ptr); - assert(obj); {% if obj.multiple %} assert(iid < AVS_ARRAY_SIZE(obj->instances)); {{ obj_inst_type }} *inst = &obj->instances[iid]; @@ -613,7 +602,6 @@ (void) arg_ctx; {{ obj_repr_type }} *obj = get_obj(obj_ptr); - assert(obj); {% if obj.multiple %} assert(iid < AVS_ARRAY_SIZE(obj->instances)); {{ obj_inst_type }} *inst = &obj->instances[iid]; @@ -646,7 +634,6 @@ {% endif %} {{ obj_repr_type }} *obj = get_obj(obj_ptr); - assert(obj); {% if obj.multiple %} assert(iid < AVS_ARRAY_SIZE(obj->instances)); {{ obj_inst_type }} *inst = &obj->instances[iid]; @@ -680,7 +667,6 @@ {% endif %} {{ obj_repr_type }} *obj = get_obj(obj_ptr); - assert(obj); {% if obj.multiple %} assert(iid < AVS_ARRAY_SIZE(obj->instances)); {{ obj_inst_type }} *inst = &obj->instances[iid]; @@ -859,7 +845,6 @@ const anjay_dm_object_def_t *const *obj_ptr, anjay_iid_t iid) { {{ obj_cxx_type }} *obj = get_obj(obj_ptr); - assert(obj); return add_instance(obj, iid) ? 0 : ANJAY_ERR_INTERNAL; } @@ -868,7 +853,6 @@ const anjay_dm_object_def_t *const *obj_ptr, anjay_iid_t iid) { {{ obj_cxx_type }} *obj = get_obj(obj_ptr); - assert(obj); auto erase_it = std::find_if(obj->instances.begin(), obj->instances.end(), [iid](const {{ obj_inst_cxx_type }} &inst) { @@ -889,7 +873,6 @@ {% endif %} {{ obj_cxx_type }} *obj = get_obj(obj_ptr); - assert(obj); {% if obj.multiple %} {{ obj_inst_cxx_type }} *inst = find_instance(obj, iid); assert(inst); @@ -925,7 +908,6 @@ {% endif %} {{ obj_cxx_type }} *obj = get_obj(obj_ptr); - assert(obj); {% if obj.multiple %} {{ obj_inst_cxx_type }} *inst = find_instance(obj, iid); assert(inst); @@ -958,7 +940,6 @@ {% endif %} {{ obj_cxx_type }} *obj = get_obj(obj_ptr); - assert(obj); {% if obj.multiple %} {{ obj_inst_cxx_type }} *inst = find_instance(obj, iid); assert(inst); @@ -990,7 +971,6 @@ {% endif %} {{ obj_cxx_type }} *obj = get_obj(obj_ptr); - assert(obj); {% if obj.multiple %} {{ obj_inst_cxx_type }} *inst = find_instance(obj, iid); assert(inst); @@ -1022,7 +1002,6 @@ {% endif %} {{ obj_cxx_type }} *obj = get_obj(obj_ptr); - assert(obj); {% if obj.multiple %} {{ obj_inst_cxx_type }} *inst = find_instance(obj, iid); assert(inst); @@ -1055,7 +1034,6 @@ {% endif %} {{ obj_cxx_type }} *obj = get_obj(obj_ptr); - assert(obj); {% if obj.multiple %} {{ obj_inst_cxx_type }} *inst = find_instance(obj, iid); assert(inst); @@ -1186,7 +1164,6 @@ const anjay_dm_object_def_t *const *obj_ptr, anjay_dm_list_ctx_t *ctx) { {{ obj_cxx_type }} *obj = get_obj(obj_ptr); - assert(obj); for (anjay_iid_t iid = 0; iid < obj->instances.size(); iid++) { anjay_dm_emit(ctx, iid); } @@ -1204,7 +1181,6 @@ {% endif %} {{ obj_cxx_type }} *obj = get_obj(obj_ptr); - assert(obj); {% if obj.multiple %} assert(iid < obj->instances.size()); {{ obj_inst_cxx_type }} &inst = obj->instances[iid]; @@ -1242,7 +1218,6 @@ {% endif %} {{ obj_cxx_type }} *obj = get_obj(obj_ptr); - assert(obj); {% if obj.multiple %} assert(iid < obj->instances.size()); {{ obj_inst_cxx_type }} &inst = obj->instances[iid]; @@ -1275,7 +1250,6 @@ {% endif %} {{ obj_cxx_type }} *obj = get_obj(obj_ptr); - assert(obj); {% if obj.multiple %} assert(iid < obj->instances.size()); {{ obj_inst_cxx_type }} &inst = obj->instances[iid]; @@ -1307,7 +1281,6 @@ {% endif %} {{ obj_cxx_type }} *obj = get_obj(obj_ptr); - assert(obj); {% if obj.multiple %} assert(iid < obj->instances.size()); {{ obj_inst_cxx_type }} &inst = obj->instances[iid]; @@ -1339,7 +1312,6 @@ {% endif %} {{ obj_cxx_type }} *obj = get_obj(obj_ptr); - assert(obj); {% if obj.multiple %} assert(iid < obj->instances.size()); {{ obj_inst_cxx_type }} &inst = obj->instances[iid]; @@ -1372,7 +1344,6 @@ {% endif %} {{ obj_cxx_type }} *obj = get_obj(obj_ptr); - assert(obj); {% if obj.multiple %} assert(iid < obj->instances.size()); {{ obj_inst_cxx_type }} &inst = obj->instances[iid]; diff --git a/tools/anjay_config_log_tool.py b/tools/anjay_config_log_tool.py index 159723ba..cf67a0a9 100755 --- a/tools/anjay_config_log_tool.py +++ b/tools/anjay_config_log_tool.py @@ -37,8 +37,9 @@ def enumerate_variables(config_files): def emit(config_file, name, value): if name in result: assert name in origins - raise ValueError( - 'Variable %s from %s duplicates one from %s' % (name, config_file, origins[name])) + if result[name] != value: + raise ValueError('Variable %s from %s conflicts with one from %s' % ( + name, config_file, origins[name])) result[name] = value origins[name] = config_file diff --git a/tools/ci-psa/Dockerfile b/tools/ci-psa/Dockerfile index bc3b7527..0d33c353 100644 --- a/tools/ci-psa/Dockerfile +++ b/tools/ci-psa/Dockerfile @@ -6,10 +6,10 @@ # See the attached LICENSE file for details. -FROM ubuntu:21.10 +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 +RUN pip3 install sphinx_rtd_theme cbor2 dpkt gitpython cryptography openpyxl RUN $(which echo) -e " \n\ \n\ diff --git a/tools/ci/rockylinux-9/Dockerfile b/tools/ci/rockylinux-9/Dockerfile index 6e2f30e6..488313b7 100644 --- a/tools/ci/rockylinux-9/Dockerfile +++ b/tools/ci/rockylinux-9/Dockerfile @@ -14,4 +14,4 @@ RUN dnf update -y && \ 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 +RUN pip3 install sphinx sphinx-rtd-theme cbor2 aiocoap wheel linuxdoc openpyxl diff --git a/tools/ci/ubuntu-18.04/Dockerfile b/tools/ci/ubuntu-18.04/Dockerfile index ccc9b815..d23b9587 100644 --- a/tools/ci/ubuntu-18.04/Dockerfile +++ b/tools/ci/ubuntu-18.04/Dockerfile @@ -11,7 +11,7 @@ RUN apt-get update && \ 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 +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 # Solve issues with EPERM when running dumpcap diff --git a/tools/ci/ubuntu-20.04/Dockerfile b/tools/ci/ubuntu-20.04/Dockerfile index 309b1a7a..025c1377 100644 --- a/tools/ci/ubuntu-20.04/Dockerfile +++ b/tools/ci/ubuntu-20.04/Dockerfile @@ -11,6 +11,6 @@ RUN apt-get update && \ 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 +RUN pip3 install sphinx sphinx-rtd-theme cbor2 aiocoap linuxdoc openpyxl # 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 641b488d..d8d3f50e 100644 --- a/tools/ci/ubuntu-22.04/Dockerfile +++ b/tools/ci/ubuntu-22.04/Dockerfile @@ -11,6 +11,6 @@ RUN apt-get update && \ 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 +RUN pip3 install sphinx sphinx-rtd-theme cbor2 aiocoap linuxdoc openpyxl # Solve issues with EPERM when running dumpcap RUN setcap '' $(which dumpcap) diff --git a/tools/license_headers.py b/tools/license_headers.py index d11ff45d..8ffd3d07 100755 --- a/tools/license_headers.py +++ b/tools/license_headers.py @@ -44,6 +44,7 @@ 'example_configs/', '^examples/', '__pycache__', + '^standalone/', '^tests/fuzz/test_cases/', '^tools/provisioning-tool/configs/', '^valgrind_test\.supp$',