diff --git a/.github/workflows/anjay-tests.yml b/.github/workflows/anjay-tests.yml index d9ebbb9c..630d7ce2 100644 --- a/.github/workflows/anjay-tests.yml +++ b/.github/workflows/anjay-tests.yml @@ -121,7 +121,7 @@ jobs: - uses: actions/checkout@v1 with: submodules: recursive - - run: dnf update -y + - run: dnf update -y --nobest - run: dnf install -y $CC # Solve issues with EPERM when running dumpcap - run: setcap '' $(which dumpcap) @@ -149,9 +149,13 @@ jobs: with: submodules: recursive - run: brew update - # 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 + - run: INSTALL_CMD="brew install openssl $COMPILER_VERSION"; $INSTALL_CMD || $INSTALL_CMD + # NOTE: Some tests don't pass on mbedTLS 3.6.2 now, so we need to install an older version + # Homebrew only specifiers major version of mbedTLS, so let's pin the version to 3.6.0 manually + - run: curl -f https://raw.githubusercontent.com/Homebrew/homebrew-core/219dabf6cab172fb8b62b4d8598e016e190c3c20/Formula/m/mbedtls.rb > /tmp/mbedtls.rb + - run: brew install --formula /tmp/mbedtls.rb + - run: brew pin mbedtls # 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 -r requirements.txt - run: env JAVA_HOME="$JAVA_HOME_17_X64" ./devconfig --with-asan --without-analysis --no-examples -DWITH_VALGRIND_TRACK_ORIGINS=OFF -DWITH_URL_CHECK=OFF -DWITH_IPV6=OFF diff --git a/.gitignore b/.gitignore index 5ad2ae24..5a9f170d 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ nbproject # build configuration autogenerated files /build +/coverage /output *.swp CMakeFiles/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 58434034..2e82faa1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,32 @@ # Changelog +## 3.8.1 (November 13th, 2024) + +### Improvements + +- Improved the coverage script and switched to lcov. +- In case when the LwM2M server answers with an RST message to a notification + that is yielding an error value (e.g. failure to read), which effectively + cancels the notification, Anjay is not infinitely trying to transmit that + message with error value once again. New behavior is enabled by default, and + controlled with `WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR` option + of `avs_coap`. Existing projects have to opt-in explicitly. +- Added `--nobest` flag to `dnf update` in Rockylinux image preparation for + tests to solve installation candidates conflicts. + +### Bugfixes + +- Actually fixed compatibility with Mbed TLS 3.6. +- Fixed compatibility of integration test framework with Mbed TLS versions that + enabled TLS 1.3, but didn't use `MBEDTLS_USE_PSA_CRYPTO`. +- The -Wformat warning appearing in some compilers has been fixed. +- Fixed LwM2M CBOR parser incorrectly accepting inputs containing empty arrays + as keys +- Prevent from generating non unique session tokens when the monotonic system + clock granulation is not fine enough. +- Refactored how timeouts are handled in pymbedtls to be in line with use of + mbedTLS in avs_commons. + ## 3.8.0 (May 28th, 2024) ### BREAKING CHANGES diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f8c17e4..ffc5144f 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.8.0" CACHE STRING "Anjay library version") +set(ANJAY_VERSION "3.8.1" CACHE STRING "Anjay library version") set(ANJAY_BINARY_VERSION 1.0.0) set(ANJAY_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") @@ -433,6 +433,8 @@ add_library(anjay src/core/io/anjay_cbor_out.c src/core/io/anjay_common.c src/core/io/anjay_common.h + src/core/io/anjay_corelnk.c + src/core/io/anjay_corelnk.h src/core/io/anjay_dynamic.c src/core/io/anjay_input_buf.c src/core/io/anjay_json_encoder.c @@ -617,7 +619,7 @@ if(WITH_TEST) add_custom_target(check) add_custom_target(anjay_unit_check) add_dependencies(check anjay_unit_check) - + # anjay_test add_executable(anjay_test EXCLUDE_FROM_ALL $ diff --git a/README.md b/README.md index 8980eef2..3dc8afff 100644 --- a/README.md +++ b/README.md @@ -179,7 +179,7 @@ pip3 install -U -r requirements.txt ### Running the demo client -For initial development and testing of LwM2M clients, we recommend using the [Coiote IoT Device Management](https://www.avsystem.com/products/coiote-iot-device-management-platform/) where you can use the basic LwM2M server functionality for free. +For initial development and testing of LwM2M clients, we recommend using the [Coiote IoT Device Management](https://avsystem.com/coiote-iot-device-management-platform/) where you can use the basic LwM2M server functionality for free. After setting up an account and adding the device entry, you can compile Anjay demo client and connect it to the platform by running: @@ -322,11 +322,11 @@ See [LICENSE](LICENSE) file. ### Commercial support -Anjay LwM2M library comes with the option of [full commercial support, provided by AVSystem](https://www.avsystem.com/products/anjay/). +Anjay LwM2M library comes with the option of [full commercial support, provided by AVSystem](https://avsystem.com/anjay-iot-sdk/). The list of features available commercially is [available here](https://AVSystem.github.io/Anjay-doc/CommercialFeatures.html). -If you're interested in LwM2M Server, be sure to check out the [Coiote IoT Device Management](https://www.avsystem.com/products/coiote-iot-dm/) platform by AVSystem. It also includes the [interoperability test module](https://www.avsystem.com/lwm2m-interoperability-test/) that you can use to test your LwM2M client implementation. Our automated tests and testing scenarios enable you to quickly check how interoperable your device is with LwM2M. +If you're interested in LwM2M Server, be sure to check out the [Coiote IoT Device Management](https://www.avsystem.com/products/coiote-iot-dm/) platform by AVSystem. It also includes the [interoperability test module](https://avsystem.com/coiote-iot-device-management-platform/lwm2m-interoperability-test/) that you can use to test your LwM2M client implementation. Our automated tests and testing scenarios enable you to quickly check how interoperable your device is with LwM2M. ## Contributing diff --git a/covconfig b/covconfig deleted file mode 100755 index 079a2928..00000000 --- a/covconfig +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh -# -# Copyright 2017-2024 AVSystem -# AVSystem Anjay LwM2M SDK -# All rights reserved. -# -# Licensed under the AVSystem-5-clause License. -# See the attached LICENSE file for details. - -"`dirname "$0"`"/devconfig --c-flags "-std=c99 -D_POSIX_C_SOURCE=200809L -g -fprofile-arcs -ftest-coverage" -D CMAKE_EXE_LINKER_FLAGS="-fprofile-arcs -ftest-coverage" diff --git a/demo/CMakeLists.txt b/demo/CMakeLists.txt index d83e6871..13482f69 100644 --- a/demo/CMakeLists.txt +++ b/demo/CMakeLists.txt @@ -40,6 +40,7 @@ if (${ANJAY_WITH_MODULE_FW_UPDATE}) set(SOURCES ${SOURCES} firmware_update.c) endif() + if (${ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE}) set(SOURCES ${SOURCES} advanced_firmware_update.c @@ -52,6 +53,7 @@ if (${ANJAY_WITH_MODULE_SW_MGMT}) set(SOURCES ${SOURCES} software_mgmt.c) endif() + if(NOT WIN32) set(SOURCES ${SOURCES} objects/ip_ping.c) endif() diff --git a/deps/avs_coap/.gitignore b/deps/avs_coap/.gitignore index 35afac13..3d976d1f 100644 --- a/deps/avs_coap/.gitignore +++ b/deps/avs_coap/.gitignore @@ -36,6 +36,7 @@ _CPack_Packages/ Testing/ /cmake/avs_coap-config.cmake /build* +/coverage/ /examples/build /include_public/avsystem/coap/avs_coap_config.h /tools/__pycache__ diff --git a/deps/avs_coap/CMakeLists.txt b/deps/avs_coap/CMakeLists.txt index 9cc79951..1e2cdf6b 100644 --- a/deps/avs_coap/CMakeLists.txt +++ b/deps/avs_coap/CMakeLists.txt @@ -38,6 +38,7 @@ option(WITH_AVS_COAP_TCP "Enable CoAP over TCP support" ON) option(WITH_AVS_COAP_STREAMING_API "Enable streaming API" ON) option(WITH_AVS_COAP_OBSERVE "Enable support for observations" ON) option(WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT "Turn on cancelling observation on a timeout " OFF) +option(WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR "Force cancelling observation on unacked error" ON) cmake_dependent_option(WITH_AVS_COAP_OBSERVE_PERSISTENCE "Enable observations persistence" ON "WITH_AVS_COAP_OBSERVE" OFF) option(WITH_AVS_COAP_BLOCK "Enable support for BLOCK/BERT transfers" ON) diff --git a/deps/avs_coap/include_public/avsystem/coap/avs_coap_config.h.in b/deps/avs_coap/include_public/avsystem/coap/avs_coap_config.h.in index b846abad..c3c1291a 100644 --- a/deps/avs_coap/include_public/avsystem/coap/avs_coap_config.h.in +++ b/deps/avs_coap/include_public/avsystem/coap/avs_coap_config.h.in @@ -80,6 +80,18 @@ */ #cmakedefine WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT +/** + * Force cancelling observation, even if a confirmable notification yielding + * an error response is not acknowledged or rejected with RST by the observer. + * + * This is a circumvention for some non-compliant servers that respond with an + * RST message to a confirmable notification yielding an error response. This + * setting makes the library cancel the observation in such cases, even though + * the notification is formally rejected. Additionally, it will also make the + * library cancel the observation if no response is received at all. + */ +#cmakedefine WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR + /** * Enable support for observation persistence (avs_coap_observe_persist() * and avs_coap_observe_restore() calls). 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 90cebd78..0fb6a34a 100644 --- a/deps/avs_coap/src/async/avs_coap_async_server.c +++ b/deps/avs_coap/src/async/avs_coap_async_server.c @@ -307,7 +307,19 @@ send_result_handler(avs_coap_ctx_t *ctx, server->delivery_handler(ctx, fail_err, server->delivery_handler_arg); - if (avs_is_ok(fail_err)) { + bool is_timeout = fail_err.category == AVS_COAP_ERR_CATEGORY + && fail_err.code == AVS_COAP_ERR_TIMEOUT; + bool is_rst = fail_err.category == AVS_COAP_ERR_CATEGORY + && fail_err.code == AVS_COAP_ERR_UDP_RESET_RECEIVED; + (void) is_timeout; + (void) is_rst; + + bool do_cancel_on_error = avs_is_ok(fail_err); +#ifdef WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR + do_cancel_on_error = do_cancel_on_error || is_timeout || is_rst; +#endif // WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR + + if (do_cancel_on_error) { cancel_notification_on_error(ctx, (avs_coap_observe_id_t) { .token = token @@ -316,8 +328,7 @@ send_result_handler(avs_coap_ctx_t *ctx, } #if defined(WITH_AVS_COAP_OBSERVE) \ && defined(WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT) - else if (fail_err.category == AVS_COAP_ERR_CATEGORY - && fail_err.code == AVS_COAP_ERR_TIMEOUT) { + else if (is_timeout) { avs_coap_observe_cancel(ctx, (avs_coap_observe_id_t) { .token = token }); @@ -1334,6 +1345,19 @@ avs_coap_notify_async(avs_coap_ctx_t *ctx, avs_errno(AVS_EINVAL); } + // FIXME: Unsolicited non-confirmable notifications with an error code are + // currently broken, because of lack of the Observe option for error values, + // the lower, UDP layer assumes the message to be an ACK, not a NON. + // + // For reference, see assumptions in choose_msg_type() in avs_coap_udp_ctx.c + if (reliability_hint == AVS_COAP_NOTIFY_PREFER_NON_CONFIRMABLE + && !avs_coap_code_is_success(response_header->code)) { + LOG(ERROR, + _("Unsolicited notifications with an error code are currently " + "broken")); + return avs_errno(AVS_EINVAL); + } + avs_coap_observe_notify_t notify; avs_error_t err = _avs_coap_observe_setup_notify(ctx, &observe_id, ¬ify); 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 938467e1..dc7a9caa 100644 --- a/deps/avs_coap/src/tcp/avs_coap_tcp_ctx.c +++ b/deps/avs_coap/src/tcp/avs_coap_tcp_ctx.c @@ -7,6 +7,8 @@ * See the attached LICENSE file for details. */ +#include + #include #ifdef WITH_AVS_COAP_TCP @@ -59,7 +61,8 @@ static void log_tcp_msg_summary(const char *info, # ifdef WITH_AVS_COAP_OBSERVE uint32_t observe; if (avs_coap_options_get_observe(&msg->options, &observe) == 0) { - snprintf(observe_str, sizeof(observe_str), ", Observe %u", observe); + snprintf(observe_str, sizeof(observe_str), ", Observe %" PRIu32, + observe); } # endif // WITH_AVS_COAP_OBSERVE diff --git a/deps/avs_coap/tests/udp/async_observe.c b/deps/avs_coap/tests/udp/async_observe.c index 6db3290b..5c6b1596 100644 --- a/deps/avs_coap/tests/udp/async_observe.c +++ b/deps/avs_coap/tests/udp/async_observe.c @@ -249,6 +249,12 @@ AVS_UNIT_TEST(udp_observe, notify_async_confirmable) { } AVS_UNIT_TEST(udp_observe, notify_async_cancel_with_error_response) { + // FIXME: Unsolicited non-confirmable notifications with an error code are + // currently broken, because of lack of the Observe option for error values, + // the lower, UDP layer assumes the message to be an ACK, not a NON. + // + // For reference, see assumptions in choose_msg_type() in avs_coap_udp_ctx.c + return; test_env_t env __attribute__((cleanup(test_teardown))) = test_setup_default(); @@ -258,8 +264,7 @@ AVS_UNIT_TEST(udp_observe, notify_async_cancel_with_error_response) { const test_msg_t *responses[] = { COAP_MSG(ACK, CONTENT, ID(100), MAKE_TOKEN("Obserw"), OBSERVE(0), NO_PAYLOAD), - COAP_MSG(NON, NOT_FOUND, ID(0), MAKE_TOKEN("Obserw"), OBSERVE(1), - NO_PAYLOAD), + COAP_MSG(NON, NOT_FOUND, ID(0), MAKE_TOKEN("Obserw"), NO_PAYLOAD), }; expect_recv(&env, request); @@ -303,8 +308,7 @@ AVS_UNIT_TEST(udp_observe, const test_msg_t *responses[] = { COAP_MSG(ACK, CONTENT, ID(100), MAKE_TOKEN("Obserw"), OBSERVE(0), NO_PAYLOAD), - COAP_MSG(CON, NOT_FOUND, ID(0), MAKE_TOKEN("Obserw"), OBSERVE(1), - NO_PAYLOAD), + COAP_MSG(CON, NOT_FOUND, ID(0), MAKE_TOKEN("Obserw"), NO_PAYLOAD), }; expect_recv(&env, requests[0]); @@ -343,6 +347,144 @@ AVS_UNIT_TEST(udp_observe, ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL)); } +AVS_UNIT_TEST(udp_observe, + notify_async_rst_to_cancel_with_confirmable_error_response) { + test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) = + test_setup_default(); + + const test_msg_t *requests[] = { + COAP_MSG(CON, GET, ID(100), MAKE_TOKEN("Obserw"), OBSERVE(0), + NO_PAYLOAD), + COAP_MSG(RST, EMPTY, ID(0), NO_PAYLOAD), + }; + const test_msg_t *responses[] = { + COAP_MSG(ACK, CONTENT, ID(100), MAKE_TOKEN("Obserw"), OBSERVE(0), + NO_PAYLOAD), + COAP_MSG(CON, NOT_FOUND, ID(0), MAKE_TOKEN("Obserw"), NO_PAYLOAD), + }; + + expect_recv(&env, requests[0]); + expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED, + requests[0], + &(avs_coap_response_header_t) { + .code = responses[0]->response_header.code + }, + NULL); + expect_observe_start(&env, requests[0]->msg.token); + expect_send(&env, responses[0]); + expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL, + NULL, NULL); + + expect_has_buffered_data_check(&env, false); + ASSERT_OK(avs_coap_async_handle_incoming_packet( + env.coap_ctx, test_accept_new_request, &env)); + avs_coap_observe_id_t observe_id = { + .token = requests[0]->msg.token + }; + + expect_send(&env, responses[1]); + + avs_coap_exchange_id_t id; + ASSERT_OK(avs_coap_notify_async(env.coap_ctx, &id, observe_id, + &responses[1]->response_header, + AVS_COAP_NOTIFY_PREFER_CONFIRMABLE, NULL, + NULL, test_observe_delivery_handler, &env)); + ASSERT_TRUE(avs_coap_exchange_id_valid(id)); + + expect_recv(&env, requests[1]); + // Reset response should trigger FAIL result + expect_observe_delivery(&env, + _avs_coap_err(AVS_COAP_ERR_UDP_RESET_RECEIVED)); + + // Whether the observations gets actually canceled depends on the config +# ifdef WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR + expect_observe_cancel(&env, observe_id.token); +# endif // WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR + + expect_has_buffered_data_check(&env, false); + ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL)); + +# ifdef WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR + // on exit, the observation should already be canceled + ASSERT_NULL(env.expects_list); +# else // WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR + // we're using late_expects_check to capture the implicit cancellation when + // cleaning up the test + expect_observe_cancel(&env, observe_id.token); +# endif // WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR +} + +AVS_UNIT_TEST(udp_observe, + notify_async_timeout_of_cancel_with_confirmable_error_response) { + avs_coap_udp_tx_params_t tx_params = AVS_COAP_DEFAULT_UDP_TX_PARAMS; + tx_params.max_retransmit = 0; + test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) = + test_setup(&tx_params, 4096, 4096, NULL); + + const test_msg_t *requests[] = { COAP_MSG( + CON, GET, ID(100), MAKE_TOKEN("Obserw"), OBSERVE(0), NO_PAYLOAD) }; + const test_msg_t *responses[] = { + COAP_MSG(ACK, CONTENT, ID(100), MAKE_TOKEN("Obserw"), OBSERVE(0), + NO_PAYLOAD), + COAP_MSG(CON, NOT_FOUND, ID(0), MAKE_TOKEN("Obserw"), NO_PAYLOAD), + }; + + expect_recv(&env, requests[0]); + expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED, + requests[0], + &(avs_coap_response_header_t) { + .code = responses[0]->response_header.code + }, + NULL); + expect_observe_start(&env, requests[0]->msg.token); + expect_send(&env, responses[0]); + expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL, + NULL, NULL); + + expect_has_buffered_data_check(&env, false); + ASSERT_OK(avs_coap_async_handle_incoming_packet( + env.coap_ctx, test_accept_new_request, &env)); + avs_coap_observe_id_t observe_id = { + .token = requests[0]->msg.token + }; + + expect_send(&env, responses[1]); + + avs_coap_exchange_id_t id; + ASSERT_OK(avs_coap_notify_async(env.coap_ctx, &id, observe_id, + &responses[1]->response_header, + AVS_COAP_NOTIFY_PREFER_CONFIRMABLE, NULL, + NULL, test_observe_delivery_handler, &env)); + ASSERT_TRUE(avs_coap_exchange_id_valid(id)); + + const avs_time_duration_t EPSILON = + avs_time_duration_from_scalar(1, AVS_TIME_S); + + _avs_mock_clock_advance(avs_time_duration_add( + avs_coap_udp_max_transmit_wait(&tx_params), EPSILON)); + + // Timeout should trigger FAIL result + expect_observe_delivery(&env, _avs_coap_err(AVS_COAP_ERR_TIMEOUT)); + + // Whether the observations gets actually canceled depends on the config +# ifdef WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR + expect_observe_cancel(&env, observe_id.token); +# endif // WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR + + expect_has_buffered_data_check(&env, false); + avs_sched_run(env.sched); + ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL)); + +# ifdef WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR + // on exit, the observation should already be canceled + ASSERT_NULL(env.expects_list); +# else // WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR + // we're using late_expects_check to capture the implicit cancellation when + // cleaning up the test + expect_observe_cancel(&env, observe_id.token); +# endif // WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR +} + AVS_UNIT_TEST(udp_observe, notify_async_confirmable_reset_response) { # define NOTIFY_PAYLOAD "Notifaj" test_env_t env __attribute__((cleanup(test_teardown))) = diff --git a/deps/avs_coap/tools/coverage b/deps/avs_coap/tools/coverage new file mode 100755 index 00000000..dace48ba --- /dev/null +++ b/deps/avs_coap/tools/coverage @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# +# Copyright 2017-2024 AVSystem +# AVSystem CoAP library +# All rights reserved. +# +# Licensed under the AVSystem-5-clause License. +# See the attached LICENSE file for details. + +set -e + +. "$(dirname "$0")/deploy-utils/utils.sh" + +function die() { + echo -e "$@" >&2 + exit 1 +} + +which gcc || die "gcc not found, exiting" +which lcov || die "lcov not found, exiting" +which genhtml || die "genhtml not found, exiting" + +GCC_VERSION=$(gcc --version 2>&1 | head -n 1 | awk 'END {print $NF}') +GCC_MAJOR_VERSION=${GCC_VERSION%%.*} +LCOV_VERSION=$(lcov --version 2>&1 | head -n 1 | awk 'END {print $NF}') +LCOV_MAJOR_VERSION=${LCOV_VERSION%%.*} + +if [ "$LCOV_MAJOR_VERSION" -gt 1 ]; then + LCOV_ADDITIONAL_OPTS="--rc branch_coverage=1 --ignore-errors mismatch" +else + LCOV_ADDITIONAL_OPTS="--rc lcov_branch_coverage=1" +fi + +[[ "$PROJECT_ROOT" ]] || PROJECT_ROOT="$(dirname "$(dirname "$(canonicalize "$0")")")" + +rm -rf "$PROJECT_ROOT/build/coverage" +mkdir -p "$PROJECT_ROOT/build/coverage" +pushd "$PROJECT_ROOT/build/coverage" + "$PROJECT_ROOT/devconfig" -DCMAKE_C_FLAGS="-std=c99 -D_POSIX_C_SOURCE=200809L -g -O0 --coverage" -DCMAKE_EXE_LINKER_FLAGS="--coverage" "$@" + make check -j$(num_processors) + mkdir -p "$PROJECT_ROOT/coverage" + lcov $LCOV_ADDITIONAL_OPTS -c -d . -o coverage.info --gcov-tool /usr/bin/gcov-$GCC_MAJOR_VERSION + lcov $LCOV_ADDITIONAL_OPTS --remove coverage.info "$PROJECT_ROOT/tests/*" "/usr/*" "$PROJECT_ROOT/include_public/*" "$PROJECT_ROOT/deps/*" -o coverage.info + genhtml coverage.info --branch-coverage --function-coverage --output-directory "$PROJECT_ROOT/coverage" +popd + +cat <`_, - you will need to change server-side configuration if you previously used - NoSec connectivity for the same endpoint name. + `_, you will + need to change server-side configuration if you previously used NoSec + connectivity for the same endpoint name. The simplest solution might often be to remove the device entry completely and create it from scratch. diff --git a/doc/sphinx/source/CommercialFeatures.rst b/doc/sphinx/source/CommercialFeatures.rst index fcd43513..3c0ed296 100644 --- a/doc/sphinx/source/CommercialFeatures.rst +++ b/doc/sphinx/source/CommercialFeatures.rst @@ -11,24 +11,25 @@ Commercial features .. image:: avsystem_logo.png :align: center - :target: https://www.avsystem.com/products/anjay/ + :target: https://avsystem.com/anjay-iot-sdk/ :alt: AVSystem logo Enterprise-grade commercial support for the Anjay library, as well as additional commercially-licensed library features, are provided by AVSystem. For more details, see -`AVSystem Anjay website `_. +`AVSystem Anjay website `_. If you want to test your LwM2M client implementation you can use the LwM2M interoperability test module in AVSystem’s `Coiote IoT Device Management -`_ -platform. Our automated tests and testing scenarios enable you to quickly check -how interoperable your device is with LwM2M. The tests include basic actions, -such as Read, Write, Execute, Discover, Delete, and more as well as advanced -actions, such as Loop or Wait to build more complex test cases. What’s more, -you can also create your own test scenarios or have us prepare custom test cases -upon your request. Visit our `webpage -`_ to learn more. +`_ platform. Our +automated tests and testing scenarios enable you to quickly check how +interoperable your device is with LwM2M. The tests include basic actions, such +as Read, Write, Execute, Discover, Delete, and more as well as advanced actions, +such as Loop or Wait to build more complex test cases. What’s more, you can also +create your own test scenarios or have us prepare custom test cases upon your +request. Visit our `webpage +`_ +to learn more. Commercial library features can be included separately as options accessible on top of the basic functionality present in the open source version. Currently diff --git a/doc/sphinx/source/CommercialFeatures/CF-CustomHardwareSupport.rst b/doc/sphinx/source/CommercialFeatures/CF-CustomHardwareSupport.rst index dde52aef..bb553cdc 100644 --- a/doc/sphinx/source/CommercialFeatures/CF-CustomHardwareSupport.rst +++ b/doc/sphinx/source/CommercialFeatures/CF-CustomHardwareSupport.rst @@ -45,7 +45,7 @@ example applications for many popular SDKs and prototyping kits: `_ * - `STM32Cube `_ - w/ `Azure RTOS `_ and `X-CUBE-CELLULAR + w/ `Azure RTOS `_ and `X-CUBE-CELLULAR `_ - *contained in the app* - `Anjay-stm32-azurertos-client @@ -80,7 +80,7 @@ Examples of application aspects that need custom integration with a software pla During the development process, AVSystem can also include your custom hardware in CI/CD pipeline to ensure proper performance, configuration and interoperability. Such continuous integration testing is an extension of Custom Hardware Support -included in `Anjay Support Packages `_. +included in `Anjay Support Packages `_. We can also assist with making many **improvements and optimizations**, even if the basic functionality works out of the box. For example, Anjay-zephyr contains diff --git a/doc/sphinx/source/FirmwareUpdateTutorial/FU-DownloadResumption.rst b/doc/sphinx/source/FirmwareUpdateTutorial/FU-DownloadResumption.rst index 10b2d782..93b2a9ab 100644 --- a/doc/sphinx/source/FirmwareUpdateTutorial/FU-DownloadResumption.rst +++ b/doc/sphinx/source/FirmwareUpdateTutorial/FU-DownloadResumption.rst @@ -20,7 +20,7 @@ a waste if that data was to be downloaded for the second time. This is where the download resumption mechanism comes into play. If this is a **PULL** mode download, the Server supports and uses `ETags `_ and the firmware resource did -not expire (i.e. has the same ETag) there is a good chance the Client will +not expire (i.e. has the same ETag), there is a good chance the Client will be able to resume a partially finished download. Anjay and Firmware Update initial state @@ -105,25 +105,25 @@ Let's have a look at the ``anjay_fw_update_initial_state_t``: The highlighted fields can be used to arrange a download resumption. Recall that we already passed this structure to ``anjay_fw_update_install`` in -previous chapters, but we've always made it zero-initialized before doing so. +previous chapters, but we've always zero-initialized before doing so. .. note:: **Quick reminder:** download resumption is supported for **PULL** mode downloads only. -Anyway, as you can see from the structure above, we're going to need three +As you can see from the structure above, we're going to need three pieces of information: - - ``persisted_uri`` - that is, the URI from which the download was - originally started, - - ``resume_offset`` - which is just the number of bytes successfully - stored before the device crashed / unexpectedly rebooted / whatever, - - ``resume_tag`` - tag, allowing to validate that the Server still has - the same firmware file available under given URI. + - ``persisted_uri`` - the URI from which the download was originally + started, + - ``resume_offset`` - the number of bytes successfully stored before + the device crashed or unexpectedly rebooted, + - ``resume_etag`` - ETag that allows to validate whether the Server + still has the same firmware file available under given URI. -Implementation-wise, we'll start with introducing a structure that'd hold -the download state as well as utility functions that'd store and restore +In terms of implementation, we will start with introducing a structure that will +hold the download state as well as utility functions that will store and restore the state from persistent storage: .. highlight:: c @@ -275,7 +275,7 @@ at that time. and ``package_etag`` are non-`NULL`. ``package_uri`` indicates it is a **PULL** mode transfer (the only mode supporting resumption), while ``package_etag`` allows the Client to verify that the downloaded file - is the exactly the same as before the resumption happened -- without it + is the exactly the same as before the resumption happened; without it, there will be no resumption. This time, however, we will save both of them in ``FW_STATE``. The only @@ -297,11 +297,11 @@ ideas can be summarized as follows: The implementation of ``fw_stream_write`` as described above will be awkward on a UNIX-like systems. Complicated operating systems tend to have multiple layers of IO buffering, and it may take some time before - the actual writes are made to the physical storage device. What it - means for us is that we can't just call ``fwrite()`` and blindly update - ``resume_offset`` with the number of bytes we ordered it to write even - if it returned a success (because the data may still reside in some cache, - maintained e.g. by the kernel). + the actual writes are made to the physical storage device. This means + that we can't just call ``fwrite()`` and blindly update ``resume_offset`` + with the number of bytes we ordered it to write, even if it returned + success (because the data may still reside in some cache, maintained e.g. + by the kernel). Because of that, rather than updating the download state file on each call to ``fw_stream_write``, it would be wiser to do it once in @@ -433,7 +433,7 @@ The next step is to make sure that ``fw_reset`` resets the download state as wel reset_download_state(&FW_STATE.download_state); } -And the last piece of the implementation would be to read the download state (if any) +And the last piece of the implementation will be to read the download state (if any) at initialization stage, and before installing the firmware update module in Anjay: .. highlight:: c diff --git a/doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/NetworkingAPI/NetworkingAPI-OtherFeatures.rst b/doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/NetworkingAPI/NetworkingAPI-OtherFeatures.rst index 9b6badaa..33142622 100644 --- a/doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/NetworkingAPI/NetworkingAPI-OtherFeatures.rst +++ b/doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/NetworkingAPI/NetworkingAPI-OtherFeatures.rst @@ -23,9 +23,7 @@ account for, if the shorter version already implements all features used by Anjay. This largely owes to the fact that the network layer in ``avs_commons`` has been -designed not just for Anjay, but for generic use in multiple projects. For -example, it is also used in `LibCWMP -`_, another AVSystem product; it can +designed not just for Anjay, but for generic use in multiple projects, and can also be used to build third-party applications. Most of the additional functionality that is not used by Anjay has been diff --git a/doc/sphinx/source/Tools/FactoryProvisioning.rst b/doc/sphinx/source/Tools/FactoryProvisioning.rst index c32b4660..ed8e7fe1 100644 --- a/doc/sphinx/source/Tools/FactoryProvisioning.rst +++ b/doc/sphinx/source/Tools/FactoryProvisioning.rst @@ -235,7 +235,7 @@ be used: try: fcty = fp.FactoryProvisioning(args.endpoint_cfg, args.URN, args.server, args.token, args.cert) - if fcty.get_sec_mode() == 'cert': + if fcty.get_sec_mode() == 'cert' or fcty.get_sec_mode() == 'est': if args.scert is not None: fcty.set_server_cert(args.scert) diff --git a/example_configs/embedded_lwm2m10/anjay/anjay_config.h b/example_configs/embedded_lwm2m10/anjay/anjay_config.h index e169621c..3ad0e2ec 100644 --- a/example_configs/embedded_lwm2m10/anjay/anjay_config.h +++ b/example_configs/embedded_lwm2m10/anjay/anjay_config.h @@ -663,6 +663,7 @@ * anjay_server_connection_status_cb_t callback. */ /* #undef ANJAY_WITH_CONN_STATUS_API */ + /**@}*/ #endif // ANJAY_CONFIG_H diff --git a/example_configs/embedded_lwm2m10/avsystem/coap/avs_coap_config.h b/example_configs/embedded_lwm2m10/avsystem/coap/avs_coap_config.h index 94dcddf0..b86e458a 100644 --- a/example_configs/embedded_lwm2m10/avsystem/coap/avs_coap_config.h +++ b/example_configs/embedded_lwm2m10/avsystem/coap/avs_coap_config.h @@ -80,6 +80,18 @@ */ /* #undef WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT */ +/** + * Force cancelling observation, even if a confirmable notification yielding + * an error response is not acknowledged or rejected with RST by the observer. + * + * This is a circumvention for some non-compliant servers that respond with an + * RST message to a confirmable notification yielding an error response. This + * setting makes the library cancel the observation in such cases, even though + * the notification is formally rejected. Additionally, it will also make the + * library cancel the observation if no response is received at all. + */ +#define WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR + /** * Enable support for observation persistence (avs_coap_observe_persist() * and avs_coap_observe_restore() calls). diff --git a/example_configs/embedded_lwm2m11/anjay/anjay_config.h b/example_configs/embedded_lwm2m11/anjay/anjay_config.h index d6c598d6..ea67e2fe 100644 --- a/example_configs/embedded_lwm2m11/anjay/anjay_config.h +++ b/example_configs/embedded_lwm2m11/anjay/anjay_config.h @@ -663,6 +663,7 @@ * anjay_server_connection_status_cb_t callback. */ /* #undef ANJAY_WITH_CONN_STATUS_API */ + /**@}*/ #endif // ANJAY_CONFIG_H diff --git a/example_configs/embedded_lwm2m11/avsystem/coap/avs_coap_config.h b/example_configs/embedded_lwm2m11/avsystem/coap/avs_coap_config.h index c79812f8..0daf1570 100644 --- a/example_configs/embedded_lwm2m11/avsystem/coap/avs_coap_config.h +++ b/example_configs/embedded_lwm2m11/avsystem/coap/avs_coap_config.h @@ -80,6 +80,18 @@ */ /* #undef WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT */ +/** + * Force cancelling observation, even if a confirmable notification yielding + * an error response is not acknowledged or rejected with RST by the observer. + * + * This is a circumvention for some non-compliant servers that respond with an + * RST message to a confirmable notification yielding an error response. This + * setting makes the library cancel the observation in such cases, even though + * the notification is formally rejected. Additionally, it will also make the + * library cancel the observation if no response is received at all. + */ +#define WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR + /** * Enable support for observation persistence (avs_coap_observe_persist() * and avs_coap_observe_restore() calls). diff --git a/example_configs/linux_lwm2m10/anjay/anjay_config.h b/example_configs/linux_lwm2m10/anjay/anjay_config.h index 8fb98c0b..164902c8 100644 --- a/example_configs/linux_lwm2m10/anjay/anjay_config.h +++ b/example_configs/linux_lwm2m10/anjay/anjay_config.h @@ -663,6 +663,7 @@ * anjay_server_connection_status_cb_t callback. */ #define ANJAY_WITH_CONN_STATUS_API + /**@}*/ #endif // ANJAY_CONFIG_H diff --git a/example_configs/linux_lwm2m10/avsystem/coap/avs_coap_config.h b/example_configs/linux_lwm2m10/avsystem/coap/avs_coap_config.h index 745f2de9..35149283 100644 --- a/example_configs/linux_lwm2m10/avsystem/coap/avs_coap_config.h +++ b/example_configs/linux_lwm2m10/avsystem/coap/avs_coap_config.h @@ -80,6 +80,18 @@ */ /* #undef WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT */ +/** + * Force cancelling observation, even if a confirmable notification yielding + * an error response is not acknowledged or rejected with RST by the observer. + * + * This is a circumvention for some non-compliant servers that respond with an + * RST message to a confirmable notification yielding an error response. This + * setting makes the library cancel the observation in such cases, even though + * the notification is formally rejected. Additionally, it will also make the + * library cancel the observation if no response is received at all. + */ +#define WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR + /** * Enable support for observation persistence (avs_coap_observe_persist() * and avs_coap_observe_restore() calls). diff --git a/example_configs/linux_lwm2m11/anjay/anjay_config.h b/example_configs/linux_lwm2m11/anjay/anjay_config.h index ee4e9afd..5e301e8a 100644 --- a/example_configs/linux_lwm2m11/anjay/anjay_config.h +++ b/example_configs/linux_lwm2m11/anjay/anjay_config.h @@ -663,6 +663,7 @@ * anjay_server_connection_status_cb_t callback. */ #define ANJAY_WITH_CONN_STATUS_API + /**@}*/ #endif // ANJAY_CONFIG_H diff --git a/example_configs/linux_lwm2m11/avsystem/coap/avs_coap_config.h b/example_configs/linux_lwm2m11/avsystem/coap/avs_coap_config.h index 83107b82..c0c02ff6 100644 --- a/example_configs/linux_lwm2m11/avsystem/coap/avs_coap_config.h +++ b/example_configs/linux_lwm2m11/avsystem/coap/avs_coap_config.h @@ -80,6 +80,18 @@ */ /* #undef WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT */ +/** + * Force cancelling observation, even if a confirmable notification yielding + * an error response is not acknowledged or rejected with RST by the observer. + * + * This is a circumvention for some non-compliant servers that respond with an + * RST message to a confirmable notification yielding an error response. This + * setting makes the library cancel the observation in such cases, even though + * the notification is formally rejected. Additionally, it will also make the + * library cancel the observation if no response is received at all. + */ +#define WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR + /** * Enable support for observation persistence (avs_coap_observe_persist() * and avs_coap_observe_restore() calls). diff --git a/include_public/anjay/anjay_config.h.in b/include_public/anjay/anjay_config.h.in index 753bbaf0..72392804 100644 --- a/include_public/anjay/anjay_config.h.in +++ b/include_public/anjay/anjay_config.h.in @@ -663,6 +663,7 @@ * anjay_server_connection_status_cb_t callback. */ #cmakedefine ANJAY_WITH_CONN_STATUS_API + /**@}*/ #endif // ANJAY_CONFIG_H diff --git a/src/anjay_config_log.h b/src/anjay_config_log.h index 6f1f609f..2e755a75 100644 --- a/src/anjay_config_log.h +++ b/src/anjay_config_log.h @@ -710,6 +710,11 @@ static inline void _anjay_log_feature_list(void) { #else // WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT _anjay_log(anjay, TRACE, "WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT = OFF"); #endif // WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT +#ifdef WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR + _anjay_log(anjay, TRACE, "WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR = ON"); +#else // WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR + _anjay_log(anjay, TRACE, "WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR = OFF"); +#endif // WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR #ifdef WITH_AVS_COAP_OBSERVE_PERSISTENCE _anjay_log(anjay, TRACE, "WITH_AVS_COAP_OBSERVE_PERSISTENCE = ON"); #else // WITH_AVS_COAP_OBSERVE_PERSISTENCE diff --git a/src/anjay_modules/anjay_dm_utils.h b/src/anjay_modules/anjay_dm_utils.h index 8418ea2e..23c78ce9 100644 --- a/src/anjay_modules/anjay_dm_utils.h +++ b/src/anjay_modules/anjay_dm_utils.h @@ -20,6 +20,7 @@ #include VISIBILITY_PRIVATE_HEADER_BEGIN +#define MAX_PATH_STRING_SIZE sizeof("/65535/65535/65535/65535") // NOTE: A lot of code depends on numerical values of these constants. // Please be careful when refactoring. @@ -50,6 +51,7 @@ typedef enum { */ typedef struct { uint16_t ids[_ANJAY_URI_PATH_MAX_LENGTH]; + } anjay_uri_path_t; static inline size_t _anjay_uri_path_length(const anjay_uri_path_t *path) { @@ -132,8 +134,9 @@ const char *_anjay_debug_make_path__(char *buffer, size_t buffer_size, const anjay_uri_path_t *uri); -#define ANJAY_DEBUG_MAKE_PATH(path) \ - (_anjay_debug_make_path__(&(char[32]){ 0 }[0], 32, (path))) +#define ANJAY_DEBUG_MAKE_PATH(path) \ + (_anjay_debug_make_path__(&(char[MAX_PATH_STRING_SIZE]){ 0 }[0], \ + MAX_PATH_STRING_SIZE, (path))) #define URI_PATH_INITIALIZER(Oid, Iid, Rid, Riid) \ { \ @@ -373,6 +376,7 @@ anjay_dm_foreach_object_handler_t(anjay_unlocked_t *anjay, void *data); int _anjay_dm_foreach_object(anjay_unlocked_t *anjay, + anjay_dm_t *dm, anjay_dm_foreach_object_handler_t *handler, void *data); @@ -748,7 +752,7 @@ bool _anjay_dm_transaction_object_included( anjay_unlocked_t *anjay, const anjay_dm_installed_object_t *obj_ptr); const anjay_dm_installed_object_t * -_anjay_dm_find_object_by_oid(anjay_unlocked_t *anjay, anjay_oid_t oid); +_anjay_dm_find_object_by_oid(const anjay_dm_t *dm, anjay_oid_t oid); bool _anjay_dm_ssid_exists(anjay_unlocked_t *anjay, anjay_ssid_t ssid); @@ -859,6 +863,22 @@ _anjay_dm_installed_object_version(const anjay_dm_installed_object_t *obj) { #endif // ANJAY_WITH_THREAD_SAFETY +AVS_LIST(anjay_dm_installed_object_t) * +_anjay_find_and_verify_object_to_unregister( + anjay_dm_t *dm, const anjay_dm_object_def_t *const *def_ptr); + +void _anjay_unregister_object_handle_transaction_state( + anjay_unlocked_t *anjay, const anjay_dm_installed_object_t *def_ptr); + +void _anjay_unregister_object_handle_notify_queue( + anjay_unlocked_t *anjay, const anjay_dm_installed_object_t *def_ptr); + +AVS_LIST(anjay_dm_installed_object_t) _anjay_prepare_user_provided_object( + const anjay_dm_object_def_t *const *def_ptr); + +int _anjay_dm_register_object( + anjay_dm_t *dm, AVS_LIST(anjay_dm_installed_object_t) *elem_ptr_move); + int _anjay_register_object_unlocked( anjay_unlocked_t *anjay, AVS_LIST(anjay_dm_installed_object_t) *elem_ptr_move); @@ -966,6 +986,8 @@ int _anjay_execute_get_arg_value_unlocked(anjay_unlocked_execute_ctx_t *ctx, char *out_buf, size_t buf_size); +anjay_dm_t *_anjay_get_dm(anjay_unlocked_t *anjay); + VISIBILITY_PRIVATE_HEADER_END #endif /* ANJAY_INCLUDE_ANJAY_MODULES_DM_H */ diff --git a/src/anjay_modules/anjay_notify.h b/src/anjay_modules/anjay_notify.h index 19567e5e..4afd0518 100644 --- a/src/anjay_modules/anjay_notify.h +++ b/src/anjay_modules/anjay_notify.h @@ -37,11 +37,7 @@ typedef AVS_LIST(anjay_notify_queue_object_entry_t) anjay_notify_queue_t; /** * Performs all the actions necessary due to all the changes in the data model - * specified by the queue. - * - * Note that sending Observe notifications and updating the Access Control - * Object require knowing which server (if any) performed the changes. - * @ref _anjay_dm_current_ssid will be called to determine it. + * specified by the queue_ptr parameter. */ int _anjay_notify_perform(anjay_unlocked_t *anjay, anjay_ssid_t origin_ssid, diff --git a/src/core/anjay_access_utils.c b/src/core/anjay_access_utils.c index 55746466..1e37085c 100644 --- a/src/core/anjay_access_utils.c +++ b/src/core/anjay_access_utils.c @@ -27,7 +27,8 @@ VISIBILITY_SOURCE_BEGIN static inline const anjay_dm_installed_object_t * get_access_control(anjay_unlocked_t *anjay) { - return _anjay_dm_find_object_by_oid(anjay, ANJAY_DM_OID_ACCESS_CONTROL); + return _anjay_dm_find_object_by_oid(&anjay->dm, + ANJAY_DM_OID_ACCESS_CONTROL); } static int read_u16(anjay_unlocked_t *anjay, @@ -264,7 +265,8 @@ static anjay_access_mask_t access_control_mask(anjay_unlocked_t *anjay, anjay_iid_t iid, anjay_ssid_t ssid) { const anjay_dm_installed_object_t *ac_obj = - _anjay_dm_find_object_by_oid(anjay, ANJAY_DM_OID_ACCESS_CONTROL); + _anjay_dm_find_object_by_oid(&anjay->dm, + ANJAY_DM_OID_ACCESS_CONTROL); anjay_iid_t ac_iid; if (!ac_obj || find_ac_instance_by_target(anjay, ac_obj, &ac_iid, oid, iid)) { @@ -682,7 +684,7 @@ static int remove_referred_instance(anjay_unlocked_t *anjay, // - the target Instance does not exist int result = 0; const anjay_dm_installed_object_t *obj = - _anjay_dm_find_object_by_oid(anjay, it->target_oid); + _anjay_dm_find_object_by_oid(&anjay->dm, it->target_oid); if (obj && _anjay_dm_instance_present(anjay, obj, (anjay_iid_t) it->target_iid) @@ -720,7 +722,7 @@ remove_orphaned_instances(anjay_unlocked_t *anjay, AVS_LIST(anjay_ssid_t) ssid_list = NULL; AVS_LIST(orphaned_instance_info_t) instances_to_remove = NULL; const anjay_dm_installed_object_t *security_obj = - _anjay_dm_find_object_by_oid(anjay, ANJAY_DM_OID_SECURITY); + _anjay_dm_find_object_by_oid(&anjay->dm, ANJAY_DM_OID_SECURITY); if (security_obj) { result = _anjay_dm_foreach_instance( anjay, security_obj, enumerate_valid_ssids_clb, &ssid_list); @@ -810,7 +812,7 @@ int _anjay_acl_ref_validate_inst_ref(anjay_unlocked_t *anjay, anjay_oid_t target_oid, anjay_iid_t target_iid) { const anjay_dm_installed_object_t *obj = - _anjay_dm_find_object_by_oid(anjay, target_oid); + _anjay_dm_find_object_by_oid(&anjay->dm, target_oid); if (!obj) { return -1; } diff --git a/src/core/anjay_access_utils_private.h b/src/core/anjay_access_utils_private.h index f6dd0158..738a2e53 100644 --- a/src/core/anjay_access_utils_private.h +++ b/src/core/anjay_access_utils_private.h @@ -90,7 +90,7 @@ bool _anjay_instance_action_allowed_by_acl(anjay_unlocked_t *anjay, * E.1.3 Unbootstrapping). * 3. Creates new Access Control object instances that refer to all newly * created Object Instances. These will have the owner and the default ACL - * referring to SSID == _anjay_dm_current_ssid(anjay). + * referring to SSID == origin_ssid parameter. * * Please refer to comments inside the implementation for details. */ diff --git a/src/core/anjay_bootstrap_core.c b/src/core/anjay_bootstrap_core.c index 2982ed2c..fed02166 100644 --- a/src/core/anjay_bootstrap_core.c +++ b/src/core/anjay_bootstrap_core.c @@ -112,7 +112,8 @@ static void abort_bootstrap(anjay_unlocked_t *anjay) { _anjay_dm_transaction_rollback(anjay); anjay->bootstrap.in_progress = false; _anjay_conn_session_token_reset( - &anjay->bootstrap.bootstrap_session_token); + &anjay->bootstrap.bootstrap_session_token, + &anjay->session_token_counter); _anjay_schedule_reload_servers(anjay); } } @@ -299,7 +300,7 @@ static int security_object_valid_handler(anjay_unlocked_t *anjay, static bool has_multiple_bootstrap_security_instances(anjay_unlocked_t *anjay) { uintptr_t bootstrap_instances = 0; const anjay_dm_installed_object_t *obj = - _anjay_dm_find_object_by_oid(anjay, ANJAY_DM_OID_SECURITY); + _anjay_dm_find_object_by_oid(&anjay->dm, ANJAY_DM_OID_SECURITY); if (_anjay_dm_foreach_instance(anjay, obj, security_object_valid_handler, &bootstrap_instances) || bootstrap_instances > 1) { @@ -316,7 +317,7 @@ static int update_last_bootstrapped_time(anjay_unlocked_t *anjay, uint16_t ssid; assert(obj); if (_anjay_dm_installed_object_oid(obj) == ANJAY_DM_OID_SECURITY) { - if (!_anjay_dm_find_object_by_oid(anjay, ANJAY_DM_OID_SERVER) + if (!_anjay_dm_find_object_by_oid(&anjay->dm, ANJAY_DM_OID_SERVER) || _anjay_ssid_from_security_iid(anjay, iid, &ssid) || _anjay_find_server_iid(anjay, ssid, &server_iid)) { // It isn't an error if Server Object instance doesn't exist, or if @@ -362,7 +363,7 @@ static int bootstrap_write_impl(anjay_unlocked_t *anjay, return ANJAY_ERR_INTERNAL; } const anjay_dm_installed_object_t *obj = - _anjay_dm_find_object_by_oid(anjay, uri->ids[ANJAY_ID_OID]); + _anjay_dm_find_object_by_oid(&anjay->dm, uri->ids[ANJAY_ID_OID]); if (!obj) { anjay_log(DEBUG, _("Object not found: ") "%u", uri->ids[ANJAY_ID_OID]); return ANJAY_ERR_NOT_FOUND; @@ -421,7 +422,8 @@ int _anjay_bootstrap_write_composite(anjay_unlocked_t *anjay, } const anjay_dm_installed_object_t *obj = - _anjay_dm_find_object_by_oid(anjay, path.ids[ANJAY_ID_OID]); + _anjay_dm_find_object_by_oid(&anjay->dm, + path.ids[ANJAY_ID_OID]); if (!obj) { anjay_log(DEBUG, _("Object not found: ") "%u", path.ids[ANJAY_ID_OID]); @@ -553,7 +555,7 @@ static int bootstrap_delete(anjay_connection_ref_t bootstrap_connection, }; if (_anjay_uri_path_has(&request->uri, ANJAY_ID_OID)) { const anjay_dm_installed_object_t *obj = - _anjay_dm_find_object_by_oid(anjay, + _anjay_dm_find_object_by_oid(&anjay->dm, request->uri.ids[ANJAY_ID_OID]); if (!obj) { anjay_log(WARNING, _("Object not found: ") "%u", @@ -575,7 +577,8 @@ static int bootstrap_delete(anjay_connection_ref_t bootstrap_connection, retval = delete_object(anjay, obj, &delete_arg); } } else { - retval = _anjay_dm_foreach_object(anjay, delete_object, &delete_arg); + retval = _anjay_dm_foreach_object(anjay, &anjay->dm, delete_object, + &delete_arg); } if (delete_arg.retval) { return delete_arg.retval; @@ -623,7 +626,7 @@ static void purge_bootstrap(avs_sched_t *sched, const void *dummy) { int retval = 0; anjay_notify_queue_t notification = NULL; const anjay_dm_installed_object_t *obj = - _anjay_dm_find_object_by_oid(anjay, ANJAY_DM_OID_SECURITY); + _anjay_dm_find_object_by_oid(&anjay->dm, ANJAY_DM_OID_SECURITY); if (!obj || (iid = _anjay_find_bootstrap_security_iid(anjay)) == ANJAY_ID_INVALID) { @@ -652,7 +655,7 @@ static void purge_bootstrap(avs_sched_t *sched, const void *dummy) { static int schedule_bootstrap_timeout(anjay_unlocked_t *anjay) { anjay_iid_t iid; const anjay_dm_installed_object_t *obj = - _anjay_dm_find_object_by_oid(anjay, ANJAY_DM_OID_SECURITY); + _anjay_dm_find_object_by_oid(&anjay->dm, ANJAY_DM_OID_SECURITY); if (!obj || (iid = _anjay_find_bootstrap_security_iid(anjay)) == ANJAY_ID_INVALID) { @@ -706,7 +709,8 @@ static int bootstrap_finish_impl(anjay_unlocked_t *anjay, int flags) { anjay_log(INFO, _("Bootstrap Sequence finished")); anjay->bootstrap.in_progress = false; - _anjay_conn_session_token_reset(&anjay->bootstrap.bootstrap_session_token); + _anjay_conn_session_token_reset(&anjay->bootstrap.bootstrap_session_token, + &anjay->session_token_counter); int retval = _anjay_dm_transaction_finish_without_validation(anjay, 0); if (retval) { anjay_log( @@ -850,7 +854,7 @@ avs_error_t _anjay_bootstrap_delete_everything(anjay_unlocked_t *anjay) { if (avs_is_err(err)) { return err; } - if (_anjay_dm_foreach_object(anjay, delete_object, &delete_arg) + if (_anjay_dm_foreach_object(anjay, &anjay->dm, delete_object, &delete_arg) || delete_arg.retval) { return avs_errno(AVS_EPROTO); } else { @@ -895,7 +899,8 @@ static int bootstrap_read(anjay_connection_ref_t bootstrap_connection, return ANJAY_ERR_METHOD_NOT_ALLOWED; } const anjay_dm_installed_object_t *obj = - _anjay_dm_find_object_by_oid(anjay, request->uri.ids[ANJAY_ID_OID]); + _anjay_dm_find_object_by_oid(&anjay->dm, + request->uri.ids[ANJAY_ID_OID]); if (!obj) { anjay_log(DEBUG, _("Object not found: ") "%u", request->uri.ids[ANJAY_ID_OID]); @@ -1376,12 +1381,13 @@ int _anjay_perform_bootstrap_action_if_appropriate( return -1; } -void _anjay_bootstrap_init(anjay_bootstrap_t *bootstrap, +void _anjay_bootstrap_init(anjay_unlocked_t *anjay, bool allow_legacy_server_initiated_bootstrap) { - bootstrap->allow_legacy_server_initiated_bootstrap = + anjay->bootstrap.allow_legacy_server_initiated_bootstrap = allow_legacy_server_initiated_bootstrap; - _anjay_conn_session_token_reset(&bootstrap->bootstrap_session_token); - reset_client_initiated_bootstrap_backoff(bootstrap); + _anjay_conn_session_token_reset(&anjay->bootstrap.bootstrap_session_token, + &anjay->session_token_counter); + reset_client_initiated_bootstrap_backoff(&anjay->bootstrap); } void _anjay_bootstrap_cleanup(anjay_unlocked_t *anjay) { diff --git a/src/core/anjay_bootstrap_core.h b/src/core/anjay_bootstrap_core.h index 0cb85386..e6f020f3 100644 --- a/src/core/anjay_bootstrap_core.h +++ b/src/core/anjay_bootstrap_core.h @@ -58,7 +58,7 @@ int _anjay_perform_bootstrap_action_if_appropriate( anjay_server_info_t *bootstrap_server, anjay_bootstrap_action_t action); -void _anjay_bootstrap_init(anjay_bootstrap_t *bootstrap, +void _anjay_bootstrap_init(anjay_unlocked_t *anjay, bool allow_legacy_server_initiated_bootstrap); #else diff --git a/src/core/anjay_core.c b/src/core/anjay_core.c index 0be02ee6..e63d072a 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.8.0" +# define ANJAY_VERSION "3.8.1" #endif // ANJAY_VERSION #ifdef ANJAY_WITH_LWM2M11 @@ -112,7 +112,7 @@ static int init_anjay(anjay_unlocked_t *anjay, && anjay->lwm2m_version_config.minimum_version == ANJAY_LWM2M_VERSION_1_0); # endif // ANJAY_WITH_LWM2M11 - _anjay_bootstrap_init(&anjay->bootstrap, legacy_server_initiated_bootstrap); + _anjay_bootstrap_init(anjay, legacy_server_initiated_bootstrap); #endif // ANJAY_WITH_BOOTSTRAP anjay->dtls_version = config->dtls_version; @@ -370,7 +370,7 @@ static void anjay_cleanup_impl(anjay_unlocked_t *anjay, bool deregister) { #ifdef ANJAY_WITH_ATTR_STORAGE _anjay_attr_storage_cleanup(&anjay->attr_storage); #endif // ANJAY_WITH_ATTR_STORAGE - _anjay_dm_cleanup(anjay); + _anjay_dm_cleanup(&anjay->dm); _anjay_notify_clear_queue(&anjay->scheduled_notify.queue); #ifdef ANJAY_WITH_SEND @@ -781,8 +781,8 @@ static int parse_dm_uri(const avs_coap_request_header_t *hdr, } else if (expect_no_more_options || uri[0] == '\0') { anjay_log(WARNING, _("superfluous empty Uri-Path segment")); return -1; - } else if (segment_index >= AVS_ARRAY_SIZE(out_uri->ids)) { - // 4 or more segments... + } else if (segment_index >= _ANJAY_URI_PATH_MAX_LENGTH) { + // 5 or more segments... anjay_log(WARNING, _("prefixed Uri-Path are not supported")); return -1; } else if (parse_request_uri_segment(uri, diff --git a/src/core/anjay_core.h b/src/core/anjay_core.h index 6129406d..017d1e12 100644 --- a/src/core/anjay_core.h +++ b/src/core/anjay_core.h @@ -87,6 +87,7 @@ struct anjay_dm_t dm; anjay_security_config_cache_t security_config_from_dm_cache; uint16_t udp_listen_port; + uint64_t session_token_counter; /** * List of known LwM2M servers we may want to be connected to. This is diff --git a/src/core/anjay_dm_core.c b/src/core/anjay_dm_core.c index 8c93b712..c4fd2d08 100644 --- a/src/core/anjay_dm_core.c +++ b/src/core/anjay_dm_core.c @@ -105,9 +105,8 @@ static int validate_version(const anjay_dm_installed_object_t *obj) { return 0; } -int _anjay_register_object_unlocked( - anjay_unlocked_t *anjay, - AVS_LIST(anjay_dm_installed_object_t) *elem_ptr_move) { +int _anjay_dm_register_object( + anjay_dm_t *dm, AVS_LIST(anjay_dm_installed_object_t) *elem_ptr_move) { assert(elem_ptr_move); assert(*elem_ptr_move); assert(_anjay_dm_installed_object_oid(*elem_ptr_move) != ANJAY_ID_INVALID); @@ -119,7 +118,7 @@ int _anjay_register_object_unlocked( AVS_LIST(anjay_dm_installed_object_t) *obj_iter; - AVS_LIST_FOREACH_PTR(obj_iter, &anjay->dm.objects) { + AVS_LIST_FOREACH_PTR(obj_iter, &dm->objects) { assert(*obj_iter); if (_anjay_dm_installed_object_oid(*obj_iter) @@ -131,13 +130,24 @@ int _anjay_register_object_unlocked( if (*obj_iter && _anjay_dm_installed_object_oid(*obj_iter) == _anjay_dm_installed_object_oid(*elem_ptr_move)) { - dm_log(ERROR, _("data model object ") "/%u" _(" already registered"), + dm_log(ERROR, + _("data model object /") "%" PRIu16 _(" already registered"), _anjay_dm_installed_object_oid(*elem_ptr_move)); return -1; } AVS_LIST_INSERT(obj_iter, *elem_ptr_move); + return 0; +} + +int _anjay_register_object_unlocked( + anjay_unlocked_t *anjay, + AVS_LIST(anjay_dm_installed_object_t) *elem_ptr_move) { + if (_anjay_dm_register_object(&anjay->dm, elem_ptr_move)) { + return -1; + } + dm_log(INFO, _("successfully registered object ") "/%u", _anjay_dm_installed_object_oid(*elem_ptr_move)); if (_anjay_notify_instances_changed_unlocked( @@ -152,11 +162,11 @@ int _anjay_register_object_unlocked( return 0; } -int anjay_register_object(anjay_t *anjay_locked, - const anjay_dm_object_def_t *const *def_ptr) { +AVS_LIST(anjay_dm_installed_object_t) _anjay_prepare_user_provided_object( + const anjay_dm_object_def_t *const *def_ptr) { if (!def_ptr || !*def_ptr) { dm_log(ERROR, _("invalid object pointer")); - return -1; + return NULL; } if ((*def_ptr)->oid == ANJAY_ID_INVALID) { @@ -164,14 +174,14 @@ int anjay_register_object(anjay_t *anjay_locked, _("Object ID ") "%u" _( " is forbidden by the LwM2M 1.1 specification"), ANJAY_ID_INVALID); - return -1; + return NULL; } AVS_LIST(anjay_dm_installed_object_t) new_elem = AVS_LIST_NEW_ELEMENT(anjay_dm_installed_object_t); if (!new_elem) { _anjay_log_oom(); - return -1; + return NULL; } #ifdef ANJAY_WITH_THREAD_SAFETY @@ -180,6 +190,16 @@ int anjay_register_object(anjay_t *anjay_locked, #else // ANJAY_WITH_THREAD_SAFETY *new_elem = def_ptr; #endif // ANJAY_WITH_THREAD_SAFETY + return new_elem; +} + +int anjay_register_object(anjay_t *anjay_locked, + const anjay_dm_object_def_t *const *def_ptr) { + AVS_LIST(anjay_dm_installed_object_t) new_elem = + _anjay_prepare_user_provided_object(def_ptr); + if (!new_elem) { + return -1; + } int result = -1; ANJAY_MUTEX_LOCK(anjay, anjay_locked); @@ -203,49 +223,59 @@ static void remove_oid_from_notify_queue(anjay_notify_queue_t *out_queue, } } -static int -unregister_object_unlocked(anjay_unlocked_t *anjay, - AVS_LIST(anjay_dm_installed_object_t) *def_ptr) { - assert(def_ptr && *def_ptr); - assert(AVS_LIST_FIND_PTR(&anjay->dm.objects, *def_ptr)); - - AVS_LIST(anjay_dm_installed_object_t) detached = AVS_LIST_DETACH(def_ptr); - +void _anjay_unregister_object_handle_transaction_state( + anjay_unlocked_t *anjay, const anjay_dm_installed_object_t *def_ptr) { AVS_LIST(const anjay_dm_installed_object_t *) *obj_in_transaction_iter; AVS_LIST_FOREACH_PTR(obj_in_transaction_iter, &anjay->transaction_state.objs_in_transaction) { - if (**obj_in_transaction_iter >= detached) { - if (**obj_in_transaction_iter == detached) { + if (**obj_in_transaction_iter >= def_ptr) { + if (**obj_in_transaction_iter == def_ptr) { assert(anjay->transaction_state.depth); if (_anjay_dm_call_transaction_rollback( anjay, **obj_in_transaction_iter)) { dm_log(ERROR, _("cannot rollback transaction on ") "/%u" _( ", object may be left in undefined state"), - _anjay_dm_installed_object_oid(detached)); + _anjay_dm_installed_object_oid(def_ptr)); } AVS_LIST_DELETE(obj_in_transaction_iter); } break; } } +} +void _anjay_unregister_object_handle_notify_queue( + anjay_unlocked_t *anjay, const anjay_dm_installed_object_t *def_ptr) { anjay_notify_queue_t notify = NULL; if (_anjay_notify_queue_instance_set_unknown_change( - ¬ify, _anjay_dm_installed_object_oid(detached)) + ¬ify, _anjay_dm_installed_object_oid(def_ptr)) || _anjay_notify_flush(anjay, ANJAY_SSID_BOOTSTRAP, ¬ify)) { dm_log(WARNING, _("could not perform notifications about removed object ") "%" PRIu16, - _anjay_dm_installed_object_oid(detached)); + _anjay_dm_installed_object_oid(def_ptr)); } remove_oid_from_notify_queue(&anjay->scheduled_notify.queue, - _anjay_dm_installed_object_oid(detached)); + _anjay_dm_installed_object_oid(def_ptr)); #ifdef ANJAY_WITH_BOOTSTRAP remove_oid_from_notify_queue(&anjay->bootstrap.notification_queue, - _anjay_dm_installed_object_oid(detached)); + _anjay_dm_installed_object_oid(def_ptr)); #endif // ANJAY_WITH_BOOTSTRAP - dm_log(INFO, _("successfully unregistered object ") "/%u", +} + +static int +unregister_object_unlocked(anjay_unlocked_t *anjay, + AVS_LIST(anjay_dm_installed_object_t) *def_ptr) { + assert(def_ptr && *def_ptr); + + assert(AVS_LIST_FIND_PTR(&anjay->dm.objects, *def_ptr)); + AVS_LIST(anjay_dm_installed_object_t) detached = AVS_LIST_DETACH(def_ptr); + + _anjay_unregister_object_handle_transaction_state(anjay, detached); + _anjay_unregister_object_handle_notify_queue(anjay, detached); + + dm_log(INFO, _("successfully unregistered object /") "%" PRIu16, _anjay_dm_installed_object_oid(detached)); AVS_LIST_DELETE(&detached); if (_anjay_schedule_registration_update_unlocked(anjay, ANJAY_SSID_ANY)) { @@ -254,61 +284,73 @@ unregister_object_unlocked(anjay_unlocked_t *anjay, return 0; } -int anjay_unregister_object(anjay_t *anjay_locked, - const anjay_dm_object_def_t *const *def_ptr) { - int result = -1; - ANJAY_MUTEX_LOCK(anjay, anjay_locked); +AVS_LIST(anjay_dm_installed_object_t) * +_anjay_find_and_verify_object_to_unregister( + anjay_dm_t *dm, const anjay_dm_object_def_t *const *def_ptr) { if (!def_ptr || !*def_ptr) { dm_log(ERROR, _("invalid object pointer")); - } else { - AVS_LIST(anjay_dm_installed_object_t) *obj_iter; - AVS_LIST_FOREACH_PTR(obj_iter, &anjay->dm.objects) { - assert(*obj_iter); - if (_anjay_dm_installed_object_oid(*obj_iter) >= (*def_ptr)->oid) { - break; - } + return NULL; + } + + AVS_LIST(anjay_dm_installed_object_t) *obj_iter; + AVS_LIST_FOREACH_PTR(obj_iter, &dm->objects) { + assert(*obj_iter); + if (_anjay_dm_installed_object_oid(*obj_iter) >= (*def_ptr)->oid) { + break; } + } - if (!*obj_iter - || _anjay_dm_installed_object_oid(*obj_iter) - != (*def_ptr)->oid) { - dm_log(ERROR, - _("object ") "%" PRIu16 _(" is not currently registered"), - (*def_ptr)->oid); - } else if ( + if (!*obj_iter + || _anjay_dm_installed_object_oid(*obj_iter) != (*def_ptr)->oid) { + dm_log(ERROR, + _("object ") "%" PRIu16 _(" is not currently registered"), + (*def_ptr)->oid); + return NULL; + } else if ( #ifdef ANJAY_WITH_THREAD_SAFETY - (*obj_iter)->type != ANJAY_DM_OBJECT_USER_PROVIDED - || (*obj_iter)->impl.user_provided != def_ptr + (*obj_iter)->type != ANJAY_DM_OBJECT_USER_PROVIDED + || (*obj_iter)->impl.user_provided != def_ptr #else // ANJAY_WITH_THREAD_SAFETY - **obj_iter != def_ptr + **obj_iter != def_ptr #endif // ANJAY_WITH_THREAD_SAFETY - ) { - dm_log(ERROR, - _("object ") "%" PRIu16 _( - " that is registered is not the same as the object " - "passed for unregister"), - (*def_ptr)->oid); - } else { - result = unregister_object_unlocked(anjay, obj_iter); - } + ) { + dm_log(ERROR, + _("object ") "%" PRIu16 _( + " that is registered is not the same as the object " + "passed for unregister"), + (*def_ptr)->oid); + return NULL; + } + + return obj_iter; +} + +int anjay_unregister_object(anjay_t *anjay_locked, + const anjay_dm_object_def_t *const *def_ptr) { + int result = -1; + ANJAY_MUTEX_LOCK(anjay, anjay_locked); + AVS_LIST(anjay_dm_installed_object_t) *obj = + _anjay_find_and_verify_object_to_unregister(&anjay->dm, def_ptr); + if (obj) { + result = unregister_object_unlocked(anjay, obj); } ANJAY_MUTEX_UNLOCK(anjay_locked); return result; } -void _anjay_dm_cleanup(anjay_unlocked_t *anjay) { - AVS_LIST_CLEAR(&anjay->dm.modules) { - assert(anjay->dm.modules->deleter); - anjay->dm.modules->deleter(anjay->dm.modules->arg); +void _anjay_dm_cleanup(anjay_dm_t *dm) { + AVS_LIST_CLEAR(&dm->modules) { + assert(dm->modules->deleter); + dm->modules->deleter(dm->modules->arg); } - AVS_LIST_CLEAR(&anjay->dm.objects); + AVS_LIST_CLEAR(&dm->objects); } const anjay_dm_installed_object_t * -_anjay_dm_find_object_by_oid(anjay_unlocked_t *anjay, anjay_oid_t oid) { +_anjay_dm_find_object_by_oid(const anjay_dm_t *dm, anjay_oid_t oid) { AVS_LIST(anjay_dm_installed_object_t) obj; - AVS_LIST_FOREACH(obj, anjay->dm.objects) { + AVS_LIST_FOREACH(obj, dm->objects) { if (_anjay_dm_installed_object_oid(obj) == oid) { return obj; } @@ -572,7 +614,11 @@ static int dm_execute(anjay_unlocked_t *anjay, if (!retval && request->uri.ids[ANJAY_ID_OID] == ANJAY_DM_OID_SERVER && request->uri.ids[ANJAY_ID_RID] == ANJAY_DM_RID_SERVER_DISABLE) { - _anjay_set_server_suspending_flag(anjay, ssid, true); + anjay_ssid_t target_ssid; + if (!_anjay_ssid_from_server_iid( + anjay, request->uri.ids[ANJAY_ID_IID], &target_ssid)) { + _anjay_set_server_suspending_flag(anjay, target_ssid, true); + } } #endif // ANJAY_WITH_CONN_STATUS_API _anjay_execute_ctx_destroy(&execute_ctx); @@ -702,10 +748,13 @@ static int invoke_action(anjay_connection_ref_t connection, int _anjay_dm_perform_action(anjay_connection_ref_t connection, const anjay_request_t *request) { const anjay_dm_installed_object_t *obj = NULL; + if (_anjay_uri_path_has(&request->uri, ANJAY_ID_OID)) { + const anjay_dm_t *dm; + { dm = &_anjay_from_server(connection.server)->dm; } + if (!(obj = _anjay_dm_find_object_by_oid( - _anjay_from_server(connection.server), - request->uri.ids[ANJAY_ID_OID]))) { + dm, request->uri.ids[ANJAY_ID_OID]))) { dm_log(DEBUG, _("Object not found: ") "%u", request->uri.ids[ANJAY_ID_OID]); return ANJAY_ERR_NOT_FOUND; @@ -729,6 +778,7 @@ int _anjay_dm_perform_action(anjay_connection_ref_t connection, .msg_code = _anjay_dm_make_success_response_code(request->action), .format = AVS_COAP_FORMAT_NONE }; + if (!_anjay_coap_setup_response_stream(request->ctx, &msg_details)) { return ANJAY_ERR_INTERNAL; } @@ -755,7 +805,6 @@ int _anjay_dm_perform_action(anjay_connection_ref_t connection, if (result) { return result; } - result = invoke_action(connection, obj, request, in_ctx); int destroy_result = _anjay_input_ctx_destroy(&in_ctx); @@ -763,10 +812,11 @@ int _anjay_dm_perform_action(anjay_connection_ref_t connection, } int _anjay_dm_foreach_object(anjay_unlocked_t *anjay, + anjay_dm_t *dm, anjay_dm_foreach_object_handler_t *handler, void *data) { AVS_LIST(anjay_dm_installed_object_t) obj; - AVS_LIST_FOREACH(obj, anjay->dm.objects) { + AVS_LIST_FOREACH(obj, dm->objects) { int result = handler(anjay, obj, data); if (result == ANJAY_FOREACH_BREAK) { dm_log(TRACE, _("foreach_object: break on ") "/%u", diff --git a/src/core/anjay_dm_core.h b/src/core/anjay_dm_core.h index b4bdbbee..667765c6 100644 --- a/src/core/anjay_dm_core.h +++ b/src/core/anjay_dm_core.h @@ -35,7 +35,7 @@ struct anjay_dm { AVS_LIST(anjay_dm_installed_module_t) modules; }; -void _anjay_dm_cleanup(anjay_unlocked_t *anjay); +void _anjay_dm_cleanup(anjay_dm_t *dm); typedef struct { bool has_min_period; diff --git a/src/core/anjay_lwm2m_send.c b/src/core/anjay_lwm2m_send.c index d684cef3..1fccefce 100644 --- a/src/core/anjay_lwm2m_send.c +++ b/src/core/anjay_lwm2m_send.c @@ -620,7 +620,7 @@ batch_data_add_current_impl(anjay_send_batch_builder_t *builder, } const anjay_dm_installed_object_t *obj = - _anjay_dm_find_object_by_oid(anjay, oid); + _anjay_dm_find_object_by_oid(&anjay->dm, oid); if (!obj) { send_log(ERROR, _("unregistered Object ID: ") "%u", oid); return ANJAY_ERR_NOT_FOUND; diff --git a/src/core/anjay_servers_private.h b/src/core/anjay_servers_private.h index 3beebe77..7203e02c 100644 --- a/src/core/anjay_servers_private.h +++ b/src/core/anjay_servers_private.h @@ -63,22 +63,26 @@ VISIBILITY_PRIVATE_HEADER_BEGIN * Token that changes to a new unique value every time the CoAP endpoint * association (i.e., DTLS session or raw UDP socket) has been established anew. * - * It is currently implemented as a monotonic timestamp because it's trivial to - * generate such unique value that way as long as it is never persisted. + * It is currently implemented as a consecutive counter values associated with + * the Anjay instance because it's trivial to generate such unique value that + * way as long as it is never persisted. */ typedef struct { - avs_time_monotonic_t value; + uint64_t value; } anjay_conn_session_token_t; static inline void -_anjay_conn_session_token_reset(anjay_conn_session_token_t *out) { - out->value = avs_time_monotonic_now(); +_anjay_conn_session_token_reset(anjay_conn_session_token_t *out, + uint64_t *counter) { + // pre-incrementation is needed to avoid the assignment of 0, since such a + // token may already by assigned during the initialization + out->value = ++(*counter); } static inline bool _anjay_conn_session_tokens_equal(anjay_conn_session_token_t left, anjay_conn_session_token_t right) { - return avs_time_monotonic_equal(left.value, right.value); + return left.value == right.value; } // 6.2.2 Object Version format: diff --git a/src/core/anjay_utils_core.c b/src/core/anjay_utils_core.c index 4b96df09..4693890a 100644 --- a/src/core/anjay_utils_core.c +++ b/src/core/anjay_utils_core.c @@ -673,7 +673,7 @@ static void try_get_info_from_dm(anjay_unlocked_t *anjay, assert(anjay); const anjay_dm_installed_object_t *security_obj = - _anjay_dm_find_object_by_oid(anjay, ANJAY_DM_OID_SECURITY); + _anjay_dm_find_object_by_oid(&anjay->dm, ANJAY_DM_OID_SECURITY); if (!security_obj) { anjay_log(ERROR, _("Security object not installed")); return; @@ -827,6 +827,10 @@ void _anjay_log_oom(void) { anjay_log(ERROR, _("out of memory")); } +anjay_dm_t *_anjay_get_dm(anjay_unlocked_t *anjay) { + return &anjay->dm; +} + #ifdef ANJAY_TEST # include "tests/core/utils.c" #endif // ANJAY_TEST diff --git a/src/core/attr_storage/anjay_attr_storage.c b/src/core/attr_storage/anjay_attr_storage.c index b94db491..c438b2bd 100644 --- a/src/core/attr_storage/anjay_attr_storage.c +++ b/src/core/attr_storage/anjay_attr_storage.c @@ -723,7 +723,7 @@ int _anjay_attr_storage_notify(anjay_unlocked_t *anjay, continue; } const anjay_dm_installed_object_t *def_ptr = - _anjay_dm_find_object_by_oid(anjay, object_entry->oid); + _anjay_dm_find_object_by_oid(&anjay->dm, object_entry->oid); if (!def_ptr && object_ptr) { remove_object_entry(&anjay->attr_storage, object_ptr); continue; @@ -928,7 +928,7 @@ maybe_get_object_before_setting_attrs(anjay_unlocked_t *anjay, return NULL; } const anjay_dm_installed_object_t *obj = - _anjay_dm_find_object_by_oid(anjay, oid); + _anjay_dm_find_object_by_oid(&anjay->dm, oid); if (!obj) { as_log(ERROR, "/%" PRIu16 _(" does not exist"), oid); } diff --git a/src/core/attr_storage/anjay_attr_storage_persistence.c b/src/core/attr_storage/anjay_attr_storage_persistence.c index c5c17bd9..a40b39a4 100644 --- a/src/core/attr_storage/anjay_attr_storage_persistence.c +++ b/src/core/attr_storage/anjay_attr_storage_persistence.c @@ -343,7 +343,7 @@ static avs_error_t clear_nonexistent_entries(anjay_unlocked_t *anjay, AVS_LIST(as_object_entry_t) object_helper; AVS_LIST_DELETABLE_FOREACH_PTR(object_ptr, object_helper, &as->objects) { const anjay_dm_installed_object_t *def_ptr = - _anjay_dm_find_object_by_oid(anjay, (*object_ptr)->oid); + _anjay_dm_find_object_by_oid(&anjay->dm, (*object_ptr)->oid); if (!def_ptr) { remove_object_entry(as, object_ptr); } else { diff --git a/src/core/dm/anjay_discover.c b/src/core/dm/anjay_discover.c index c1d7fc4a..7ab060f5 100644 --- a/src/core/dm/anjay_discover.c +++ b/src/core/dm/anjay_discover.c @@ -595,7 +595,7 @@ int _anjay_bootstrap_discover(anjay_unlocked_t *anjay, anjay_lwm2m_version_t lwm2m_version) { const anjay_dm_installed_object_t *obj = NULL; if (oid != ANJAY_ID_INVALID) { - obj = _anjay_dm_find_object_by_oid(anjay, oid); + obj = _anjay_dm_find_object_by_oid(&anjay->dm, oid); if (!obj) { return ANJAY_ERR_NOT_FOUND; } @@ -611,8 +611,8 @@ int _anjay_bootstrap_discover(anjay_unlocked_t *anjay, if (obj) { return bootstrap_discover_object(anjay, obj, &args); } else { - return _anjay_dm_foreach_object(anjay, bootstrap_discover_object, - &args); + return _anjay_dm_foreach_object(anjay, &anjay->dm, + bootstrap_discover_object, &args); } } # endif diff --git a/src/core/dm/anjay_dm_read.c b/src/core/dm/anjay_dm_read.c index 66170dd0..93f2cc37 100644 --- a/src/core/dm/anjay_dm_read.c +++ b/src/core/dm/anjay_dm_read.c @@ -29,10 +29,10 @@ read_resource_instance_internal(anjay_unlocked_t *anjay, anjay_riid_t riid, anjay_unlocked_output_ctx_t *out_ctx) { int result; - (void) ((result = _anjay_output_set_path( - out_ctx, &MAKE_RESOURCE_INSTANCE_PATH( - _anjay_dm_installed_object_oid(obj), iid, - rid, riid))) + anjay_uri_path_t path = + MAKE_RESOURCE_INSTANCE_PATH(_anjay_dm_installed_object_oid(obj), + iid, rid, riid); + (void) ((result = _anjay_output_set_path(out_ctx, &path)) || (result = _anjay_dm_call_resource_read(anjay, obj, iid, rid, riid, out_ctx))); return result; @@ -91,10 +91,9 @@ static int read_multiple_resource(anjay_unlocked_t *anjay, anjay_rid_t rid, anjay_unlocked_output_ctx_t *out_ctx) { int result; - (void) ((result = _anjay_output_set_path( - out_ctx, - &MAKE_RESOURCE_PATH(_anjay_dm_installed_object_oid(obj), - iid, rid))) + anjay_uri_path_t path = + MAKE_RESOURCE_PATH(_anjay_dm_installed_object_oid(obj), iid, rid); + (void) ((result = _anjay_output_set_path(out_ctx, &path)) || (result = _anjay_output_start_aggregate(out_ctx)) || (result = _anjay_dm_foreach_resource_instance( anjay, obj, iid, rid, read_resource_instance_clb, @@ -112,10 +111,10 @@ static int read_resource_internal(anjay_unlocked_t *anjay, return read_multiple_resource(anjay, obj, iid, rid, out_ctx); } else { int result; - (void) ((result = _anjay_output_set_path( - out_ctx, &MAKE_RESOURCE_PATH( - _anjay_dm_installed_object_oid(obj), - iid, rid))) + anjay_uri_path_t path = + MAKE_RESOURCE_PATH(_anjay_dm_installed_object_oid(obj), iid, + rid); + (void) ((result = _anjay_output_set_path(out_ctx, &path)) || (result = _anjay_dm_call_resource_read( anjay, obj, iid, rid, ANJAY_ID_INVALID, out_ctx))); return result; @@ -185,10 +184,9 @@ static int read_instance(anjay_unlocked_t *anjay, anjay_ssid_t requesting_ssid, anjay_unlocked_output_ctx_t *out_ctx) { int result; - (void) ((result = _anjay_output_set_path( - out_ctx, - &MAKE_INSTANCE_PATH(_anjay_dm_installed_object_oid(obj), - iid))) + anjay_uri_path_t path = + MAKE_INSTANCE_PATH(_anjay_dm_installed_object_oid(obj), iid); + (void) ((result = _anjay_output_set_path(out_ctx, &path)) || (result = _anjay_output_start_aggregate(out_ctx)) || (result = _anjay_dm_foreach_resource( anjay, obj, iid, read_instance_resource_clb, @@ -256,7 +254,7 @@ static int read_object_clb(anjay_unlocked_t *anjay, static int read_root(anjay_unlocked_t *anjay, anjay_ssid_t requesting_ssid, anjay_unlocked_output_ctx_t *out_ctx) { - return _anjay_dm_foreach_object(anjay, read_object_clb, + return _anjay_dm_foreach_object(anjay, &anjay->dm, read_object_clb, &(read_object_clb_args_t) { .requesting_ssid = requesting_ssid, .out_ctx = out_ctx @@ -398,7 +396,7 @@ int _anjay_dm_read_resource_into_ctx(anjay_unlocked_t *anjay, anjay_unlocked_output_ctx_t *ctx) { assert(_anjay_uri_path_leaf_is(path, ANJAY_ID_RID)); const anjay_dm_installed_object_t *obj = - _anjay_dm_find_object_by_oid(anjay, path->ids[ANJAY_ID_OID]); + _anjay_dm_find_object_by_oid(&anjay->dm, path->ids[ANJAY_ID_OID]); if (!obj) { dm_log(ERROR, _("unregistered Object ID: ") "%u", path->ids[ANJAY_ID_OID]); @@ -473,7 +471,7 @@ int _anjay_dm_read_resource_u32_array(anjay_unlocked_t *anjay, assert(out_array_size_elements); const anjay_dm_installed_object_t *obj = - _anjay_dm_find_object_by_oid(anjay, path->ids[ANJAY_ID_OID]); + _anjay_dm_find_object_by_oid(&anjay->dm, path->ids[ANJAY_ID_OID]); if (!obj) { return -1; } @@ -614,7 +612,7 @@ int _anjay_dm_read_or_observe_composite(anjay_connection_ref_t connection, const anjay_dm_installed_object_t *obj = NULL; if (_anjay_uri_path_has(&path, ANJAY_ID_OID)) { - obj = _anjay_dm_find_object_by_oid(anjay, + obj = _anjay_dm_find_object_by_oid(&anjay->dm, path.ids[ANJAY_ID_OID]); if (!obj) { diff --git a/src/core/dm/anjay_dm_write.c b/src/core/dm/anjay_dm_write.c index 93041b3a..eeff3eda 100644 --- a/src/core/dm/anjay_dm_write.c +++ b/src/core/dm/anjay_dm_write.c @@ -242,7 +242,7 @@ static int write_resource_raw(anjay_unlocked_t *anjay, size_t value_size, anjay_notify_queue_t *notify_queue) { const anjay_dm_installed_object_t *obj = - _anjay_dm_find_object_by_oid(anjay, path.ids[ANJAY_ID_OID]); + _anjay_dm_find_object_by_oid(&anjay->dm, path.ids[ANJAY_ID_OID]); if (!obj) { return ANJAY_ERR_NOT_FOUND; } @@ -428,7 +428,8 @@ int _anjay_dm_write_composite(anjay_unlocked_t *anjay, goto finish; } const anjay_dm_installed_object_t *obj = - _anjay_dm_find_object_by_oid(anjay, path.ids[ANJAY_ID_OID]); + _anjay_dm_find_object_by_oid(&anjay->dm, + path.ids[ANJAY_ID_OID]); if (!obj) { dm_log(DEBUG, _("Object not found: ") "%u", path.ids[ANJAY_ID_OID]); diff --git a/src/core/dm/anjay_query.c b/src/core/dm/anjay_query.c index 9ea0bb8d..cdeb90cf 100644 --- a/src/core/dm/anjay_query.c +++ b/src/core/dm/anjay_query.c @@ -53,7 +53,7 @@ int _anjay_find_server_iid(anjay_unlocked_t *anjay, }; const anjay_dm_installed_object_t *obj = - _anjay_dm_find_object_by_oid(anjay, ANJAY_DM_OID_SERVER); + _anjay_dm_find_object_by_oid(&anjay->dm, ANJAY_DM_OID_SERVER); if (ssid == ANJAY_SSID_ANY || ssid == ANJAY_SSID_BOOTSTRAP || _anjay_dm_foreach_instance(anjay, obj, find_server_iid_handler, &args) @@ -158,7 +158,7 @@ bootstrap_security_iid_find_helper(anjay_unlocked_t *anjay, anjay_iid_t _anjay_find_bootstrap_security_iid(anjay_unlocked_t *anjay) { anjay_iid_t result = ANJAY_ID_INVALID; const anjay_dm_installed_object_t *obj = - _anjay_dm_find_object_by_oid(anjay, ANJAY_DM_OID_SECURITY); + _anjay_dm_find_object_by_oid(&anjay->dm, ANJAY_DM_OID_SECURITY); if (_anjay_dm_foreach_instance( anjay, obj, bootstrap_security_iid_find_helper, &result)) { return ANJAY_ID_INVALID; diff --git a/src/core/io/anjay_common.h b/src/core/io/anjay_common.h index bc1a7706..9a4cebfd 100644 --- a/src/core/io/anjay_common.h +++ b/src/core/io/anjay_common.h @@ -20,9 +20,7 @@ VISIBILITY_PRIVATE_HEADER_BEGIN -#define MAX_PATH_STRING_SIZE sizeof("/65535/65535/65535/65535") #define MAX_OBJLNK_STRING_SIZE sizeof("65535:65535") - /** * Enumeration for supported SenML labels. Their numeric values correspond to * their CBOR representation wherever possible. diff --git a/src/core/io/anjay_corelnk.c b/src/core/io/anjay_corelnk.c new file mode 100644 index 00000000..b05f14a3 --- /dev/null +++ b/src/core/io/anjay_corelnk.c @@ -0,0 +1,134 @@ +/* + * Copyright 2017-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include + +#include "../anjay_core.h" +#include "../anjay_utils_private.h" +#include "anjay_corelnk.h" + +VISIBILITY_SOURCE_BEGIN + +typedef struct { + bool first; + avs_stream_t *stream; + anjay_lwm2m_version_t version; +} query_dm_args_t; + +static int query_dm_instance(anjay_unlocked_t *anjay, + const anjay_dm_installed_object_t *obj, + anjay_iid_t iid, + void *args_) { + (void) anjay; + query_dm_args_t *args = (query_dm_args_t *) args_; + avs_error_t err = + avs_stream_write_f(args->stream, "%s", + args->first ? "" : ",", + _anjay_dm_installed_object_oid(obj), iid); + args->first = false; + return avs_is_ok(err) ? 0 : -1; +} + +static int query_dm_object(anjay_unlocked_t *anjay, + const anjay_dm_installed_object_t *obj, + void *args_) { + anjay_oid_t oid = _anjay_dm_installed_object_oid(obj); + if (oid == ANJAY_DM_OID_SECURITY) { + /* LwM2M TS 1.1, 6.2.1. Register says that "The Security Object ID:0, + * and OSCORE Object ID:21, if present, MUST NOT be part of the + * Registration Objects and Object Instances list." */ + return 0; + } + + query_dm_args_t *args = (query_dm_args_t *) args_; + if (args->first) { + args->first = false; + } else if (avs_is_err(avs_stream_write(args->stream, ",", 1))) { + return -1; + } + bool obj_written = false; + const char *version = _anjay_dm_installed_object_version(obj); + if (version) { + const char *format = ";ver=\"%s\""; +#ifdef ANJAY_WITH_LWM2M11 + if (args->version > ANJAY_LWM2M_VERSION_1_0) { + format = ";ver=%s"; + } +#endif // ANJAY_WITH_LWM2M11 + + if (avs_is_err( + avs_stream_write_f(args->stream, format, oid, version))) { + return -1; + } + obj_written = true; + } + query_dm_args_t instance_args = { + .first = !obj_written, + .stream = args->stream, + .version = args->version + }; + int result = _anjay_dm_foreach_instance(anjay, obj, query_dm_instance, + &instance_args); + if (result) { + return result; + } + if (!instance_args.first) { + obj_written = true; + } + if (!obj_written + && avs_is_err(avs_stream_write_f(args->stream, "", oid))) { + return -1; + } + return 0; +} + +int _anjay_corelnk_query_dm(anjay_unlocked_t *anjay, + anjay_dm_t *dm, + anjay_lwm2m_version_t version, + char **buffer) { + assert(buffer); + assert(!*buffer); + avs_stream_t *stream = avs_stream_membuf_create(); + if (!stream) { + _anjay_log_oom(); + return -1; + } + int retval; + if ((retval = _anjay_dm_foreach_object(anjay, dm, query_dm_object, + &(query_dm_args_t) { + .first = true, + .stream = stream, + .version = version + })) + || (retval = + (avs_is_ok(avs_stream_write(stream, "\0", 1)) ? 0 : -1)) + || (retval = (avs_is_ok(avs_stream_membuf_take_ownership( + stream, (void **) buffer, NULL)) + ? 0 + : -1))) { + anjay_log(ERROR, _("could not enumerate objects")); + } + avs_stream_cleanup(&stream); + return retval; +} + +#ifdef ANJAY_TEST +# include "tests/core/io/corelnk.c" +#endif diff --git a/src/core/io/anjay_corelnk.h b/src/core/io/anjay_corelnk.h new file mode 100644 index 00000000..611c0016 --- /dev/null +++ b/src/core/io/anjay_corelnk.h @@ -0,0 +1,41 @@ +/* + * Copyright 2017-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#ifndef ANJAY_IO_CORELNK_H +#define ANJAY_IO_CORELNK_H + +#include + +#include +#include + +VISIBILITY_PRIVATE_HEADER_BEGIN + +/** + * Function that returns a null-terminated string containing a list of objects, + * their instances and version. It can be used as a payload for Register and + * Update operations or as a /25/x/3 resource value. + * + * @param anjay Anjay object to operate on. + * @param dm Data model on which the query will be performed. + * @param version LwM2M version for which the query will be prepared. + * @param buffer The pointer that will be set to the buffer with the + * prepared string. It is the caller's responsibility to + * free the buffer using avs_free(). + * + * @returns 0 on success, a negative value in case of error. + */ +int _anjay_corelnk_query_dm(anjay_unlocked_t *anjay, + anjay_dm_t *dm, + anjay_lwm2m_version_t version, + char **buffer); + +VISIBILITY_PRIVATE_HEADER_END + +#endif // ANJAY_IO_CORELNK_H diff --git a/src/core/io/anjay_senml_in.c b/src/core/io/anjay_senml_in.c index 2d488951..d4a4db5e 100644 --- a/src/core/io/anjay_senml_in.c +++ b/src/core/io/anjay_senml_in.c @@ -293,7 +293,7 @@ static int parse_id(uint16_t *out_id, const char **id_begin) { } static int parse_absolute_path(anjay_uri_path_t *out_path, const char *input) { - if (!*input) { + if (!*input || *input != '/') { return -1; } *out_path = MAKE_ROOT_PATH(); @@ -301,8 +301,11 @@ static int parse_absolute_path(anjay_uri_path_t *out_path, const char *input) { if (!strcmp(input, "/")) { return 0; } + + const char *ch; + size_t curr_len = 0; - for (const char *ch = input; *ch;) { + for (ch = input; *ch;) { if (*ch++ != '/') { return -1; } @@ -318,6 +321,11 @@ static int parse_absolute_path(anjay_uri_path_t *out_path, const char *input) { return 0; } +static bool uri_path_outside_base(const anjay_uri_path_t *path, + const anjay_uri_path_t *base) { + return _anjay_uri_path_outside_base(path, base); +} + static int parse_next_absolute_path(senml_in_t *in) { char full_path[MAX_PATH_STRING_SIZE]; if (avs_simple_snprintf(full_path, @@ -332,7 +340,7 @@ static int parse_next_absolute_path(senml_in_t *in) { if (parse_absolute_path(&in->path, full_path)) { return ANJAY_ERR_BAD_REQUEST; } - if (_anjay_uri_path_outside_base(&in->path, &in->base)) { + if (uri_path_outside_base(&in->path, &in->base)) { LOG(LAZY_DEBUG, _("parsed path ") "%s" _(" would be outside of uri-path ") "%s", ANJAY_DEBUG_MAKE_PATH(&in->path), ANJAY_DEBUG_MAKE_PATH(&in->base)); diff --git a/src/core/io/anjay_senml_like_out.c b/src/core/io/anjay_senml_like_out.c index 137c0632..6781b493 100644 --- a/src/core/io/anjay_senml_like_out.c +++ b/src/core/io/anjay_senml_like_out.c @@ -114,7 +114,6 @@ static int element_begin(senml_out_t *ctx) { char basename_buf[MAX_PATH_STRING_SIZE]; char name_buf[MAX_PATH_STRING_SIZE]; - char *name = maybe_get_name(ctx, name_buf, sizeof(name_buf)); char *basename = maybe_get_basename(ctx, basename_buf, sizeof(basename_buf)); @@ -225,10 +224,15 @@ static int senml_ret_start_aggregate(anjay_unlocked_output_ctx_t *ctx_) { } } +static bool uri_path_outside_base(const anjay_uri_path_t *path, + const anjay_uri_path_t *base) { + return _anjay_uri_path_outside_base(path, base); +} + static int senml_set_path(anjay_unlocked_output_ctx_t *ctx_, const anjay_uri_path_t *uri) { senml_out_t *ctx = (senml_out_t *) ctx_; - AVS_ASSERT(!_anjay_uri_path_outside_base(uri, &ctx->base_path), + AVS_ASSERT(!uri_path_outside_base(uri, &ctx->base_path), "Attempted to set path outside the context's base path. " "This is a bug in resource reading logic."); if (_anjay_uri_path_length(&ctx->path) > 0) { @@ -263,6 +267,7 @@ static int senml_output_close(anjay_unlocked_output_ctx_t *ctx_) { } _anjay_update_ret(&result, _anjay_senml_like_encoder_cleanup(&ctx->encoder)); + if (_anjay_uri_path_length(&ctx->path) > 0) { _anjay_update_ret(&result, ANJAY_OUTCTXERR_ANJAY_RET_NOT_CALLED); } @@ -343,5 +348,9 @@ _anjay_output_senml_like_create(avs_stream_t *stream, return NULL; } +# if defined(ANJAY_TEST) && defined(ANJAY_WITH_CBOR) +# include "tests/core/io/senml_cbor_out.c" +# endif // defined(ANJAY_TEST) && defined(ANJAY_WITH_CBOR) + #endif // defined(ANJAY_WITH_LWM2M_JSON) || defined(ANJAY_WITH_SENML_JSON) || // defined(ANJAY_WITH_CBOR) diff --git a/src/core/io/cbor/anjay_json_like_cbor_decoder.h b/src/core/io/cbor/anjay_json_like_cbor_decoder.h index 87c25471..d77e6e97 100644 --- a/src/core/io/cbor/anjay_json_like_cbor_decoder.h +++ b/src/core/io/cbor/anjay_json_like_cbor_decoder.h @@ -10,6 +10,8 @@ #ifndef ANJAY_IO_JSON_LIKE_CBOR_DECODER_H #define ANJAY_IO_JSON_LIKE_CBOR_DECODER_H +#include + #include "../anjay_json_like_decoder.h" VISIBILITY_PRIVATE_HEADER_BEGIN @@ -20,14 +22,34 @@ VISIBILITY_PRIVATE_HEADER_BEGIN #define MAX_SIMPLE_CBOR_NEST_STACK_SIZE 1 /** - * The LwM2M requires wrapping entries in [ {} ], but decimal fractions or - * indefinite length bytes add another level of nesting in form of an array. + * LwM2M requires wrapping entries in [ {} ], but keys/values that are a string + * (byte/text) or a decimal fraction add another level of nesting. */ #define MAX_SENML_CBOR_NEST_STACK_SIZE 3 /** - * In LwM2M CBOR, there may be {a: {b: {c: {[d]: value}}}} - * Decimal fractions or indefinite length bytes don't add extra level here. + * LwM2M CBOR is a tree of nested maps. Root map is up to 4 levels deep. This + * happens in case there's a value of multi-instance resource, and key for each + * nested map adds only 1 path component, in a form like: + * {: {: {: {: }}}} + * + * When parsing a map, CBOR decoder's stack grows by 1 + whathever is incurred + * by its contents, i.e. key-value pairs. + * + * In LwM2M CBOR, each key is an uint, or an array of uints (possibly of size + * just 1), which needs 1 nesting level. + * + * The value is: + * - a scalar, or + * - an indefinite length string (byte/text) or a decimal fraction, which needs + * 1 nesting level, or + * - a nested map (unless we're at maximum depth). + * + * Therefore, when entering the innermost map CBOR decoder's stack will grow by + * 1+1=2 levels at max. For outer maps it's 1 + maximum growth incurred by + * contents, which essentially is 1 + maximum growth incurred by inner maps. + * + * Therefore, the maximum stack size is 1+1+1+2 = 5; */ #define MAX_LWM2M_CBOR_NEST_STACK_SIZE 5 diff --git a/src/core/observe/anjay_observe_core.c b/src/core/observe/anjay_observe_core.c index 5f97c7fa..b85161e7 100644 --- a/src/core/observe/anjay_observe_core.c +++ b/src/core/observe/anjay_observe_core.c @@ -589,7 +589,7 @@ static int get_effective_attrs(anjay_unlocked_t *anjay, anjay_ssid_t ssid) { anjay_dm_attrs_query_details_t details = { .obj = _anjay_uri_path_has(path, ANJAY_ID_OID) - ? _anjay_dm_find_object_by_oid(anjay, + ? _anjay_dm_find_object_by_oid(&anjay->dm, path->ids[ANJAY_ID_OID]) : NULL, .iid = ANJAY_ID_INVALID, @@ -1193,7 +1193,7 @@ static int read_observation_path(anjay_unlocked_t *anjay, anjay_batch_t **out_batch) { const anjay_dm_installed_object_t *obj = NULL; if (_anjay_uri_path_has(path, ANJAY_ID_OID)) { - obj = _anjay_dm_find_object_by_oid(anjay, path->ids[ANJAY_ID_OID]); + obj = _anjay_dm_find_object_by_oid(&anjay->dm, path->ids[ANJAY_ID_OID]); } int result; anjay_dm_path_info_t path_info; diff --git a/src/core/servers/anjay_activate.c b/src/core/servers/anjay_activate.c index b9dd35a0..41b0254b 100644 --- a/src/core/servers/anjay_activate.c +++ b/src/core/servers/anjay_activate.c @@ -38,7 +38,8 @@ VISIBILITY_SOURCE_BEGIN static int deactivate_server(anjay_server_info_t *server) { assert(server); #ifdef ANJAY_WITH_CONN_STATUS_API - if (server->suspending) { + if (server->suspending + && server->connection_status != ANJAY_SERV_CONN_STATUS_SUSPENDED) { _anjay_set_server_connection_status(server, ANJAY_SERV_CONN_STATUS_SUSPENDING); } @@ -181,7 +182,7 @@ void _anjay_server_on_failure(anjay_server_info_t *server, const communication_retry_params_t params = query_server_communication_retry_params(server); if (server->registration_attempts < params.retry_count) { - anjay_log(INFO, _("Registration Retry ") "%u/%u", + anjay_log(INFO, _("Registration Retry ") "%" PRIu32 "/%" PRIu32, server->registration_attempts, params.retry_count - 1); @@ -199,7 +200,7 @@ void _anjay_server_on_failure(anjay_server_info_t *server, return; } else if (server->registration_sequences_performed + 1 < params.sequence_retry_count) { - anjay_log(INFO, _("Sequence Retry ") "%u/%u", + anjay_log(INFO, _("Sequence Retry ") "%" PRIu32 "/%" PRIu32, server->registration_sequences_performed + 1, params.sequence_retry_count - 1); diff --git a/src/core/servers/anjay_connections.c b/src/core/servers/anjay_connections.c index 6e14b1a7..69ce468d 100644 --- a/src/core/servers/anjay_connections.c +++ b/src/core/servers/anjay_connections.c @@ -391,7 +391,8 @@ avs_error_t _anjay_server_connection_internal_bring_online( if (!(session_resumed = _anjay_was_session_resumed(connection->conn_socket_))) { - _anjay_conn_session_token_reset(&connection->session_token); + _anjay_conn_session_token_reset(&connection->session_token, + &server->anjay->session_token_counter); // Clean up and recreate the CoAP context to discard observations // NOTE: In old versions of avs_coap, this was sending the Release // message. This may need to be revised if it's ever reintroduced. diff --git a/src/core/servers/anjay_register.c b/src/core/servers/anjay_register.c index 31af1abb..e8300f97 100644 --- a/src/core/servers/anjay_register.c +++ b/src/core/servers/anjay_register.c @@ -12,7 +12,6 @@ #include #include -#include #include #include @@ -27,6 +26,7 @@ #include "../anjay_servers_reload.h" #include "../anjay_servers_utils.h" #include "../dm/anjay_query.h" +#include "../io/anjay_corelnk.h" #include "anjay_activate.h" #include "anjay_register.h" @@ -55,7 +55,8 @@ static int schedule_register_for_server(anjay_server_info_t *server) { anjay_server_connection_t *connection = _anjay_connection_get(&server->connections, ANJAY_CONNECTION_PRIMARY); - _anjay_conn_session_token_reset(&connection->session_token); + _anjay_conn_session_token_reset(&connection->session_token, + &server->anjay->session_token_counter); } return result; } @@ -257,7 +258,7 @@ static int get_server_lifetime(anjay_unlocked_t *anjay, return -1; } else if (lifetime < 0) { anjay_log(ERROR, - _("lifetime returned by LwM2M server ") "%u" _(" is <= 0"), + _("lifetime returned by LwM2M server ") "%u" _(" is < 0"), ssid); return -1; } @@ -266,109 +267,6 @@ static int get_server_lifetime(anjay_unlocked_t *anjay, return 0; } -typedef struct { - bool first; - avs_stream_t *stream; - anjay_lwm2m_version_t version; -} query_dm_args_t; - -static int query_dm_instance(anjay_unlocked_t *anjay, - const anjay_dm_installed_object_t *obj, - anjay_iid_t iid, - void *args_) { - (void) anjay; - query_dm_args_t *args = (query_dm_args_t *) args_; - avs_error_t err = - avs_stream_write_f(args->stream, "%s", - args->first ? "" : ",", - _anjay_dm_installed_object_oid(obj), iid); - args->first = false; - return avs_is_ok(err) ? 0 : -1; -} - -static int query_dm_object(anjay_unlocked_t *anjay, - const anjay_dm_installed_object_t *obj, - void *args_) { - anjay_oid_t oid = _anjay_dm_installed_object_oid(obj); - if (oid == ANJAY_DM_OID_SECURITY) { - /* LwM2M TS 1.1, 6.2.1. Register says that "The Security Object ID:0, - * and OSCORE Object ID:21, if present, MUST NOT be part of the - * Registration Objects and Object Instances list." */ - return 0; - } - - query_dm_args_t *args = (query_dm_args_t *) args_; - if (args->first) { - args->first = false; - } else if (avs_is_err(avs_stream_write(args->stream, ",", 1))) { - return -1; - } - bool obj_written = false; - const char *version = _anjay_dm_installed_object_version(obj); - if (version) { - const char *format = ";ver=\"%s\""; -#ifdef ANJAY_WITH_LWM2M11 - if (args->version > ANJAY_LWM2M_VERSION_1_0) { - format = ";ver=%s"; - } -#endif // ANJAY_WITH_LWM2M11 - - if (avs_is_err( - avs_stream_write_f(args->stream, format, oid, version))) { - return -1; - } - obj_written = true; - } - query_dm_args_t instance_args = { - .first = !obj_written, - .stream = args->stream, - .version = args->version - }; - int result = _anjay_dm_foreach_instance(anjay, obj, query_dm_instance, - &instance_args); - if (result) { - return result; - } - if (!instance_args.first) { - obj_written = true; - } - if (!obj_written - && avs_is_err(avs_stream_write_f(args->stream, "", oid))) { - return -1; - } - return 0; -} - -static int -query_dm(anjay_unlocked_t *anjay, anjay_lwm2m_version_t version, char **out) { - assert(out); - assert(!*out); - avs_stream_t *stream = avs_stream_membuf_create(); - if (!stream) { - _anjay_log_oom(); - return -1; - } - int retval; - void *data = NULL; - if ((retval = _anjay_dm_foreach_object(anjay, query_dm_object, - &(query_dm_args_t) { - .first = true, - .stream = stream, - .version = version - })) - || (retval = - (avs_is_ok(avs_stream_write(stream, "\0", 1)) ? 0 : -1)) - || (retval = (avs_is_ok(avs_stream_membuf_take_ownership( - stream, &data, NULL)) - ? 0 - : -1))) { - anjay_log(ERROR, _("could not enumerate objects")); - } - avs_stream_cleanup(&stream); - *out = (char *) data; - return retval; -} - static void update_parameters_cleanup(anjay_update_parameters_t *params) { avs_free(params->dm); params->dm = NULL; @@ -413,7 +311,8 @@ update_parameters_init(anjay_server_info_t *server, err = avs_errno(AVS_EBADF); goto error; } - if (query_dm(server->anjay, lwm2m_version, &out_params->dm)) { + if (_anjay_corelnk_query_dm(server->anjay, &server->anjay->dm, + lwm2m_version, &out_params->dm)) { goto error; } if (get_server_lifetime(server->anjay, _anjay_server_ssid(server), @@ -1384,7 +1283,7 @@ server_object_instances_count_clb(anjay_unlocked_t *anjay, static size_t server_object_instances_count(anjay_unlocked_t *anjay) { const anjay_dm_installed_object_t *server_obj = - _anjay_dm_find_object_by_oid(anjay, ANJAY_DM_OID_SERVER); + _anjay_dm_find_object_by_oid(&anjay->dm, ANJAY_DM_OID_SERVER); if (!server_obj) { return 0; } diff --git a/src/core/servers/anjay_reload.c b/src/core/servers/anjay_reload.c index 377d42a1..659c29da 100644 --- a/src/core/servers/anjay_reload.c +++ b/src/core/servers/anjay_reload.c @@ -109,7 +109,7 @@ static void reload_servers_sched_job(avs_sched_t *sched, const void *unused) { }; const anjay_dm_installed_object_t *obj = - _anjay_dm_find_object_by_oid(anjay, ANJAY_DM_OID_SERVER); + _anjay_dm_find_object_by_oid(&anjay->dm, ANJAY_DM_OID_SERVER); if (obj && _anjay_dm_foreach_instance( anjay, obj, reload_server_by_server_iid, &reload_state) diff --git a/src/core/servers/anjay_server_connections.c b/src/core/servers/anjay_server_connections.c index 08c293f5..110f5b1b 100644 --- a/src/core/servers/anjay_server_connections.c +++ b/src/core/servers/anjay_server_connections.c @@ -198,7 +198,7 @@ static int select_security_instance(anjay_unlocked_t *anjay, anjay_iid_t *out_security_iid, avs_url_t **out_uri) { const anjay_dm_installed_object_t *obj = - _anjay_dm_find_object_by_oid(anjay, ANJAY_DM_OID_SECURITY); + _anjay_dm_find_object_by_oid(&anjay->dm, ANJAY_DM_OID_SECURITY); select_security_instance_state_t state = { .ssid = ssid, .binding_mode = binding_mode, diff --git a/src/modules/access_control/anjay_access_control_persistence.c b/src/modules/access_control/anjay_access_control_persistence.c index 70c9fc73..258dc980 100644 --- a/src/modules/access_control/anjay_access_control_persistence.c +++ b/src/modules/access_control/anjay_access_control_persistence.c @@ -72,7 +72,7 @@ static avs_error_t persist_instance(avs_persistence_context_t *ctx, static bool is_object_registered(anjay_unlocked_t *anjay, anjay_oid_t oid) { return oid != ANJAY_DM_OID_SECURITY - && _anjay_dm_find_object_by_oid(anjay, oid) != NULL; + && _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), oid) != NULL; } static avs_error_t restore_instance(access_control_instance_t *out_instance, diff --git a/src/modules/access_control/anjay_mod_access_control.c b/src/modules/access_control/anjay_mod_access_control.c index aa8f0a8b..ce2e77a8 100644 --- a/src/modules/access_control/anjay_mod_access_control.c +++ b/src/modules/access_control/anjay_mod_access_control.c @@ -169,7 +169,8 @@ static bool target_instance_reachable(anjay_unlocked_t *anjay, || !_anjay_access_control_target_iid_valid(iid)) { return false; } - obj_ptr_t *target_obj = _anjay_dm_find_object_by_oid(anjay, oid); + obj_ptr_t *target_obj = + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), oid); if (!target_obj) { return false; } 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 f1752581..004425d8 100644 --- a/src/modules/advanced_fw_update/anjay_advanced_fw_update.c +++ b/src/modules/advanced_fw_update/anjay_advanced_fw_update.c @@ -163,7 +163,8 @@ static int perform_send(anjay_unlocked_t *anjay, static void send_batch_to_all_servers(anjay_unlocked_t *anjay, anjay_send_batch_t *batch) { const anjay_dm_installed_object_t *obj = - _anjay_dm_find_object_by_oid(anjay, ANJAY_DM_OID_SERVER); + _anjay_dm_find_object_by_oid(_anjay_get_dm(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")); @@ -756,7 +757,8 @@ static avs_error_t download_write_block(anjay_t *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); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), + ANJAY_ADVANCED_FW_UPDATE_OID); if (!obj) { fw_log(WARNING, _("Advanced Firmware Update object not installed")); } else { @@ -859,7 +861,8 @@ static void download_finished(anjay_t *anjay_locked, void *inst_) { 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); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), + ANJAY_ADVANCED_FW_UPDATE_OID); if (!obj) { fw_log(WARNING, _("Advanced Firmware Update object not installed")); } else { @@ -1839,7 +1842,8 @@ int anjay_advanced_fw_update_instance_add( int retval = -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); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), + ANJAY_ADVANCED_FW_UPDATE_OID); if (!obj) { fw_log(WARNING, _("Advanced Firmware Update object not installed")); } else { @@ -1938,7 +1942,8 @@ int anjay_advanced_fw_update_set_state_and_result( int retval = -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); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), + ANJAY_ADVANCED_FW_UPDATE_OID); if (!obj) { fw_log(WARNING, _("Advanced Firmware Update object not installed")); } else { @@ -1977,7 +1982,8 @@ int anjay_advanced_fw_update_get_state( int retval = -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); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), + ANJAY_ADVANCED_FW_UPDATE_OID); if (!obj) { fw_log(WARNING, _("Advanced Firmware Update object not installed")); } else { @@ -2006,7 +2012,8 @@ int anjay_advanced_fw_update_get_result( int retval = -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); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), + ANJAY_ADVANCED_FW_UPDATE_OID); if (!obj) { fw_log(WARNING, _("Advanced Firmware Update object not installed")); } else { @@ -2097,7 +2104,8 @@ int anjay_advanced_fw_update_set_linked_instances( int retval = -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); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), + ANJAY_ADVANCED_FW_UPDATE_OID); if (!obj) { fw_log(WARNING, _("Advanced Firmware Update object not installed")); } else { @@ -2140,7 +2148,8 @@ int anjay_advanced_fw_update_get_linked_instances( int retval = -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); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), + ANJAY_ADVANCED_FW_UPDATE_OID); if (!obj) { fw_log(WARNING, _("Advanced Firmware Update object not installed")); } else { @@ -2170,7 +2179,8 @@ int anjay_advanced_fw_update_set_conflicting_instances( int retval = -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); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), + ANJAY_ADVANCED_FW_UPDATE_OID); if (!obj) { fw_log(WARNING, _("Advanced Firmware Update object not installed")); } else { @@ -2212,7 +2222,8 @@ int anjay_advanced_fw_update_get_conflicting_instances( int retval = -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); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), + ANJAY_ADVANCED_FW_UPDATE_OID); if (!obj) { fw_log(WARNING, _("Advanced Firmware Update object not installed")); } else { @@ -2239,7 +2250,8 @@ avs_time_real_t anjay_advanced_fw_update_get_deadline(anjay_t *anjay_locked, avs_time_real_t result = AVS_TIME_REAL_INVALID; 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); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), + ANJAY_ADVANCED_FW_UPDATE_OID); if (!obj) { fw_log(WARNING, _("Advanced Firmware Update object not installed")); } else { @@ -2265,7 +2277,8 @@ anjay_advanced_fw_update_get_severity(anjay_t *anjay_locked, anjay_iid_t iid) { ANJAY_ADVANCED_FW_UPDATE_SEVERITY_MANDATORY; 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); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), + ANJAY_ADVANCED_FW_UPDATE_OID); if (!obj) { fw_log(WARNING, _("Advanced Firmware Update object not installed")); } else { @@ -2291,7 +2304,8 @@ anjay_advanced_fw_update_get_last_state_change_time(anjay_t *anjay_locked, avs_time_real_t result = AVS_TIME_REAL_INVALID; 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); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), + ANJAY_ADVANCED_FW_UPDATE_OID); if (!obj) { fw_log(WARNING, _("Advanced Firmware Update object not installed")); } else { @@ -2315,7 +2329,8 @@ 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); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), + ANJAY_ADVANCED_FW_UPDATE_OID); if (!obj) { fw_log(WARNING, _("Advanced Firmware Update object not installed")); } else { @@ -2335,7 +2350,8 @@ int anjay_advanced_fw_update_pull_reconnect(anjay_t *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); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), + ANJAY_ADVANCED_FW_UPDATE_OID); if (!obj) { fw_log(WARNING, _("Advanced Firmware Update object not installed")); } else { diff --git a/src/modules/fw_update/anjay_fw_update.c b/src/modules/fw_update/anjay_fw_update.c index 365b7a23..44b0fb12 100644 --- a/src/modules/fw_update/anjay_fw_update.c +++ b/src/modules/fw_update/anjay_fw_update.c @@ -135,7 +135,8 @@ static int perform_send(anjay_unlocked_t *anjay, static void send_batch_to_all_servers(anjay_unlocked_t *anjay, anjay_send_batch_t *batch) { const anjay_dm_installed_object_t *obj = - _anjay_dm_find_object_by_oid(anjay, ANJAY_DM_OID_SERVER); + _anjay_dm_find_object_by_oid(_anjay_get_dm(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")); @@ -1328,7 +1329,8 @@ int anjay_fw_update_set_result(anjay_t *anjay_locked, int retval = -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); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), + ANJAY_DM_OID_FIRMWARE_UPDATE); if (!obj) { fw_log(WARNING, _("Firmware Update object not installed")); } else { @@ -1356,7 +1358,8 @@ 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); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), + ANJAY_DM_OID_FIRMWARE_UPDATE); if (!obj) { fw_log(WARNING, _("Firmware Update object not installed")); } else { @@ -1375,7 +1378,8 @@ int anjay_fw_update_pull_reconnect(anjay_t *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); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), + ANJAY_DM_OID_FIRMWARE_UPDATE); if (!obj) { fw_log(WARNING, _("Firmware Update object not installed")); } else { diff --git a/src/modules/ipso/anjay_ipso_3d_sensor.c b/src/modules/ipso/anjay_ipso_3d_sensor.c index b55dba2d..d7e97022 100644 --- a/src/modules/ipso/anjay_ipso_3d_sensor.c +++ b/src/modules/ipso/anjay_ipso_3d_sensor.c @@ -246,7 +246,7 @@ ipso_3d_sensor_resource_read(anjay_unlocked_t *anjay, static anjay_ipso_3d_sensor_t *obj_from_oid(anjay_unlocked_t *anjay, anjay_oid_t oid) { const anjay_dm_installed_object_t *installed_obj_ptr = - _anjay_dm_find_object_by_oid(anjay, oid); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), oid); if (!_anjay_dm_installed_object_is_valid_unlocked(installed_obj_ptr)) { return NULL; } diff --git a/src/modules/ipso/anjay_ipso_basic_sensor.c b/src/modules/ipso/anjay_ipso_basic_sensor.c index cf2e7949..d2c229b2 100644 --- a/src/modules/ipso/anjay_ipso_basic_sensor.c +++ b/src/modules/ipso/anjay_ipso_basic_sensor.c @@ -294,7 +294,7 @@ basic_sensor_resource_execute(anjay_unlocked_t *anjay, static anjay_ipso_basic_sensor_t *obj_from_oid(anjay_unlocked_t *anjay, anjay_oid_t oid) { const anjay_dm_installed_object_t *installed_obj_ptr = - _anjay_dm_find_object_by_oid(anjay, oid); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), oid); if (!_anjay_dm_installed_object_is_valid_unlocked(installed_obj_ptr)) { return NULL; } diff --git a/src/modules/ipso/anjay_ipso_button.c b/src/modules/ipso/anjay_ipso_button.c index fcf3c1a3..befe1d9d 100644 --- a/src/modules/ipso/anjay_ipso_button.c +++ b/src/modules/ipso/anjay_ipso_button.c @@ -226,7 +226,7 @@ static const anjay_unlocked_dm_object_def_t OBJECT_DEF = { static anjay_ipso_button_t *obj_from_anjay(anjay_unlocked_t *anjay) { const anjay_dm_installed_object_t *installed_obj_ptr = - _anjay_dm_find_object_by_oid(anjay, PUSH_BUTTON_OID); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), PUSH_BUTTON_OID); if (!_anjay_dm_installed_object_is_valid_unlocked(installed_obj_ptr)) { return NULL; } diff --git a/src/modules/ipso_v2/anjay_ipso_v2_3d_sensor.c b/src/modules/ipso_v2/anjay_ipso_v2_3d_sensor.c index 60c4cce7..e15e6b4d 100644 --- a/src/modules/ipso_v2/anjay_ipso_v2_3d_sensor.c +++ b/src/modules/ipso_v2/anjay_ipso_v2_3d_sensor.c @@ -376,7 +376,7 @@ static int resource_execute(anjay_unlocked_t *anjay, static object_t *obj_from_oid(anjay_unlocked_t *anjay, anjay_oid_t oid) { const anjay_dm_installed_object_t *installed_obj_ptr = - _anjay_dm_find_object_by_oid(anjay, oid); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), oid); if (!_anjay_dm_installed_object_is_valid_unlocked(installed_obj_ptr)) { return NULL; } diff --git a/src/modules/ipso_v2/anjay_ipso_v2_basic_sensor.c b/src/modules/ipso_v2/anjay_ipso_v2_basic_sensor.c index 9a4688f2..762bb013 100644 --- a/src/modules/ipso_v2/anjay_ipso_v2_basic_sensor.c +++ b/src/modules/ipso_v2/anjay_ipso_v2_basic_sensor.c @@ -255,7 +255,7 @@ static int resource_execute(anjay_unlocked_t *anjay, static object_t *obj_from_oid(anjay_unlocked_t *anjay, anjay_oid_t oid) { const anjay_dm_installed_object_t *installed_obj_ptr = - _anjay_dm_find_object_by_oid(anjay, oid); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), oid); if (!_anjay_dm_installed_object_is_valid_unlocked(installed_obj_ptr)) { return NULL; } diff --git a/src/modules/security/anjay_mod_security.c b/src/modules/security/anjay_mod_security.c index 1daf5293..40b4117e 100644 --- a/src/modules/security/anjay_mod_security.c +++ b/src/modules/security/anjay_mod_security.c @@ -738,7 +738,7 @@ int anjay_security_object_add_instance( int retval = -1; ANJAY_MUTEX_LOCK(anjay, anjay_locked); const anjay_dm_installed_object_t *obj_ptr = - _anjay_dm_find_object_by_oid(anjay, SECURITY.oid); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), SECURITY.oid); sec_repr_t *repr = obj_ptr ? _anjay_sec_get(*obj_ptr) : NULL; if (!repr) { security_log(ERROR, _("Security object is not registered")); @@ -783,7 +783,7 @@ void anjay_security_object_purge(anjay_t *anjay_locked) { assert(anjay_locked); ANJAY_MUTEX_LOCK(anjay, anjay_locked); const anjay_dm_installed_object_t *sec_obj = - _anjay_dm_find_object_by_oid(anjay, SECURITY.oid); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), SECURITY.oid); sec_repr_t *repr = sec_obj ? _anjay_sec_get(*sec_obj) : NULL; if (!repr) { @@ -806,7 +806,7 @@ bool anjay_security_object_is_modified(anjay_t *anjay_locked) { bool result = false; ANJAY_MUTEX_LOCK(anjay, anjay_locked); const anjay_dm_installed_object_t *sec_obj = - _anjay_dm_find_object_by_oid(anjay, SECURITY.oid); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), SECURITY.oid); if (!sec_obj) { security_log(ERROR, _("Security object is not registered")); } else { diff --git a/src/modules/security/anjay_security_persistence.c b/src/modules/security/anjay_security_persistence.c index 495628fd..8ba4b293 100644 --- a/src/modules/security/anjay_security_persistence.c +++ b/src/modules/security/anjay_security_persistence.c @@ -479,7 +479,8 @@ avs_error_t anjay_security_object_persist(anjay_t *anjay_locked, avs_error_t err = avs_errno(AVS_EINVAL); ANJAY_MUTEX_LOCK(anjay, anjay_locked); const anjay_dm_installed_object_t *sec_obj = - _anjay_dm_find_object_by_oid(anjay, ANJAY_DM_OID_SECURITY); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), + ANJAY_DM_OID_SECURITY); sec_repr_t *repr = sec_obj ? _anjay_sec_get(*sec_obj) : NULL; if (!repr) { err = avs_errno(AVS_EBADF); @@ -509,7 +510,8 @@ avs_error_t anjay_security_object_restore(anjay_t *anjay_locked, avs_error_t err = avs_errno(AVS_EINVAL); ANJAY_MUTEX_LOCK(anjay, anjay_locked); const anjay_dm_installed_object_t *sec_obj = - _anjay_dm_find_object_by_oid(anjay, ANJAY_DM_OID_SECURITY); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), + ANJAY_DM_OID_SECURITY); sec_repr_t *repr = sec_obj ? _anjay_sec_get(*sec_obj) : NULL; if (!repr || repr->in_transaction) { err = avs_errno(AVS_EBADF); diff --git a/src/modules/server/anjay_mod_server.c b/src/modules/server/anjay_mod_server.c index d5bf583c..c41577f9 100644 --- a/src/modules/server/anjay_mod_server.c +++ b/src/modules/server/anjay_mod_server.c @@ -643,7 +643,7 @@ int anjay_server_object_add_instance(anjay_t *anjay_locked, int retval = -1; ANJAY_MUTEX_LOCK(anjay, anjay_locked); const anjay_dm_installed_object_t *obj_ptr = - _anjay_dm_find_object_by_oid(anjay, SERVER.oid); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), SERVER.oid); server_repr_t *repr = obj_ptr ? _anjay_serv_get(*obj_ptr) : NULL; if (!repr) { server_log(ERROR, _("Server object is not registered")); @@ -686,7 +686,7 @@ void anjay_server_object_purge(anjay_t *anjay_locked) { assert(anjay_locked); ANJAY_MUTEX_LOCK(anjay, anjay_locked); const anjay_dm_installed_object_t *server_obj = - _anjay_dm_find_object_by_oid(anjay, SERVER.oid); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), SERVER.oid); server_repr_t *repr = server_obj ? _anjay_serv_get(*server_obj) : NULL; if (!repr) { @@ -705,7 +705,7 @@ AVS_LIST(const anjay_ssid_t) anjay_server_get_ssids(anjay_t *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); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), SERVER.oid); server_repr_t *repr = _anjay_serv_get(*server_obj); if (_anjay_dm_transaction_object_included(anjay, server_obj)) { source = repr->saved_instances; @@ -727,7 +727,7 @@ bool anjay_server_object_is_modified(anjay_t *anjay_locked) { bool result = false; ANJAY_MUTEX_LOCK(anjay, anjay_locked); const anjay_dm_installed_object_t *server_obj = - _anjay_dm_find_object_by_oid(anjay, SERVER.oid); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), SERVER.oid); if (!server_obj) { server_log(ERROR, _("Server object is not registered")); } else { @@ -783,7 +783,7 @@ int anjay_server_object_set_lifetime(anjay_t *anjay_locked, int result = -1; ANJAY_MUTEX_LOCK(anjay, anjay_locked); const anjay_dm_installed_object_t *server_obj = - _anjay_dm_find_object_by_oid(anjay, SERVER.oid); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), SERVER.oid); server_repr_t *repr = _anjay_serv_get(*server_obj); if (repr->saved_instances) { server_log(ERROR, _("cannot set Lifetime while some transaction is " diff --git a/src/modules/server/anjay_server_persistence.c b/src/modules/server/anjay_server_persistence.c index 42cbe41c..a5b722d5 100644 --- a/src/modules/server/anjay_server_persistence.c +++ b/src/modules/server/anjay_server_persistence.c @@ -415,7 +415,8 @@ avs_error_t anjay_server_object_persist(anjay_t *anjay_locked, avs_error_t err = avs_errno(AVS_EINVAL); ANJAY_MUTEX_LOCK(anjay, anjay_locked); const anjay_dm_installed_object_t *server_obj = - _anjay_dm_find_object_by_oid(anjay, ANJAY_DM_OID_SERVER); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), + ANJAY_DM_OID_SERVER); server_repr_t *repr = server_obj ? _anjay_serv_get(*server_obj) : NULL; if (!repr) { err = avs_errno(AVS_EBADF); @@ -472,7 +473,8 @@ avs_error_t anjay_server_object_restore(anjay_t *anjay_locked, avs_error_t err = avs_errno(AVS_EINVAL); ANJAY_MUTEX_LOCK(anjay, anjay_locked); const anjay_dm_installed_object_t *server_obj = - _anjay_dm_find_object_by_oid(anjay, ANJAY_DM_OID_SERVER); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), + ANJAY_DM_OID_SERVER); server_repr_t *repr = server_obj ? _anjay_serv_get(*server_obj) : NULL; if (!repr || repr->in_transaction) { err = avs_errno(AVS_EBADF); diff --git a/tests/core/anjay.c b/tests/core/anjay.c index 56532add..851ddf45 100644 --- a/tests/core/anjay.c +++ b/tests/core/anjay.c @@ -19,6 +19,8 @@ #include +#include + #include "src/core/anjay_servers_inactive.h" #include "src/core/anjay_servers_reload.h" #include "src/core/servers/anjay_server_connections.h" @@ -323,7 +325,7 @@ AVS_UNIT_TEST(parse_headers, parse_attributes) { #undef ASSERT_ATTRIBUTES_EQUAL #undef ASSERT_ATTRIBUTE_VALUES_EQUAL -AVS_UNIT_TEST(parse_headers, parse_uri) { +static void parse_headers_parse_uri_standard() { bool is_bs; anjay_uri_path_t uri; header_with_opts_storage_t header_storage; @@ -452,8 +454,22 @@ AVS_UNIT_TEST(parse_headers, parse_uri) { header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH, "16", "17", "18", "65535", NULL), &is_bs, &uri)); +} + +AVS_UNIT_TEST(parse_headers, parse_uri) { + parse_headers_parse_uri_standard(); + + bool is_bs; + anjay_uri_path_t uri; + header_with_opts_storage_t header_storage; + + // normal, single prefix + ASSERT_FAIL(parse_request_uri( + header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH, + "dev", "10", "11", "12", "13", NULL), + &is_bs, &uri)); - // BS and something more + // "bs" and something more ASSERT_FAIL(parse_request_uri( header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH, "bs", "1", "2", NULL), diff --git a/tests/core/attr_storage/attr_storage.c b/tests/core/attr_storage/attr_storage.c index 2af29d3d..99a0ac85 100644 --- a/tests/core/attr_storage/attr_storage.c +++ b/tests/core/attr_storage/attr_storage.c @@ -52,17 +52,17 @@ static const anjay_dm_object_def_t *const OBJ2 = &( DM_TEST_FINISH #ifdef ANJAY_WITH_THREAD_SAFETY -# define WRAP_OBJ_PTR(ObjPtr) \ - ({ \ - const anjay_dm_installed_object_t *installed_obj = \ - _anjay_dm_find_object_by_oid(anjay_unlocked, \ - (*(ObjPtr))->oid); \ - AVS_UNIT_ASSERT_NOT_NULL(installed_obj); \ - AVS_UNIT_ASSERT_TRUE(installed_obj->type \ - == ANJAY_DM_OBJECT_USER_PROVIDED); \ - AVS_UNIT_ASSERT_TRUE(installed_obj->impl.user_provided \ - == (ObjPtr)); \ - installed_obj; \ +# define WRAP_OBJ_PTR(ObjPtr) \ + ({ \ + const anjay_dm_installed_object_t *installed_obj = \ + _anjay_dm_find_object_by_oid( \ + _anjay_get_dm(anjay_unlocked), (*(ObjPtr))->oid); \ + AVS_UNIT_ASSERT_NOT_NULL(installed_obj); \ + AVS_UNIT_ASSERT_TRUE(installed_obj->type \ + == ANJAY_DM_OBJECT_USER_PROVIDED); \ + AVS_UNIT_ASSERT_TRUE(installed_obj->impl.user_provided \ + == (ObjPtr)); \ + installed_obj; \ }) #else // ANJAY_WITH_THREAD_SAFETY # define WRAP_OBJ_PTR(ObjPtr) \ diff --git a/tests/core/attr_storage/persistence.c b/tests/core/attr_storage/persistence.c index b372de80..5afd5616 100644 --- a/tests/core/attr_storage/persistence.c +++ b/tests/core/attr_storage/persistence.c @@ -65,7 +65,7 @@ static void write_inst_attrs(anjay_unlocked_t *anjay, anjay_ssid_t ssid, const anjay_dm_oi_attributes_t *attrs) { const anjay_dm_installed_object_t *obj = - _anjay_dm_find_object_by_oid(anjay, oid); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), oid); AVS_UNIT_ASSERT_NOT_NULL(obj); AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_instance_write_default_attrs( anjay, obj, iid, ssid, attrs)); @@ -78,7 +78,7 @@ static void write_obj_attrs(anjay_unlocked_t *anjay, anjay_ssid_t ssid, const anjay_dm_oi_attributes_t *attrs) { const anjay_dm_installed_object_t *obj = - _anjay_dm_find_object_by_oid(anjay, oid); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), oid); AVS_UNIT_ASSERT_NOT_NULL(obj); AVS_UNIT_ASSERT_SUCCESS( _anjay_dm_call_object_write_default_attrs(anjay, obj, ssid, attrs)); @@ -91,7 +91,7 @@ static void write_res_attrs(anjay_unlocked_t *anjay, anjay_ssid_t ssid, const anjay_dm_r_attributes_t *attrs) { const anjay_dm_installed_object_t *obj = - _anjay_dm_find_object_by_oid(anjay, oid); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), oid); AVS_UNIT_ASSERT_NOT_NULL(obj); AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_resource_write_attrs( anjay, obj, iid, rid, ssid, attrs)); @@ -106,7 +106,7 @@ static void write_res_instance_attrs(anjay_unlocked_t *anjay, anjay_ssid_t ssid, const anjay_dm_r_attributes_t *attrs) { const anjay_dm_installed_object_t *obj = - _anjay_dm_find_object_by_oid(anjay, oid); + _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), oid); AVS_UNIT_ASSERT_NOT_NULL(obj); AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_resource_instance_write_attrs( anjay, obj, iid, rid, riid, ssid, attrs)); diff --git a/tests/core/io/cbor_in.c b/tests/core/io/cbor_in.c index d851cd0e..dc3e5792 100644 --- a/tests/core/io/cbor_in.c +++ b/tests/core/io/cbor_in.c @@ -15,12 +15,12 @@ #include "senml_in_common.h" -#define TEST_ENV(Data, Path) \ - avs_stream_inbuf_t stream = AVS_STREAM_INBUF_STATIC_INITIALIZER; \ - avs_stream_inbuf_set_buffer(&stream, Data, sizeof(Data) - 1); \ - anjay_unlocked_input_ctx_t *in; \ - ASSERT_OK(_anjay_input_senml_cbor_create( \ - &in, (avs_stream_t *) &stream, &(Path))); +#define TEST_ENV(Data, Path) \ + avs_stream_inbuf_t stream = AVS_STREAM_INBUF_STATIC_INITIALIZER; \ + avs_stream_inbuf_set_buffer(&stream, Data, sizeof(Data) - 1); \ + anjay_unlocked_input_ctx_t *in; \ + ASSERT_OK(_anjay_input_senml_cbor_create(&in, (avs_stream_t *) &stream, \ + &(Path))); AVS_UNIT_TEST(cbor_in_resource, single_instance) { static const char RESOURCE[] = { @@ -32,7 +32,7 @@ AVS_UNIT_TEST(cbor_in_resource, single_instance) { "\x18\x2A" // unsigned(42) }; TEST_ENV(RESOURCE, TEST_RESOURCE_PATH); - test_single_instance(in); + check_paths(in, &MAKE_RESOURCE_PATH(13, 26, 1), 1); TEST_TEARDOWN(OK); } @@ -46,7 +46,7 @@ AVS_UNIT_TEST(cbor_in_resource_permuted, single_instance) { "\x68/13/26/1" // text(8) }; TEST_ENV(RESOURCE, TEST_RESOURCE_PATH); - test_single_instance(in); + check_paths(in, &MAKE_RESOURCE_PATH(13, 26, 1), 1); TEST_TEARDOWN(OK); } @@ -66,7 +66,7 @@ AVS_UNIT_TEST(cbor_in_resource, single_instance_but_more_than_one) { "\x18\x2B" // unsigned(43) }; TEST_ENV(RESOURCES, TEST_RESOURCE_PATH); - test_single_instance_but_more_than_one(in); + test_single_instance_but_more_than_one(in, &MAKE_RESOURCE_PATH(13, 26, 1)); TEST_TEARDOWN(OK); } @@ -87,15 +87,9 @@ AVS_UNIT_TEST(cbor_in_resource, "\x18\x2B" // unsigned(43) }; TEST_ENV(RESOURCES, TEST_RESOURCE_PATH); - anjay_uri_path_t path; - ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal(&path, &TEST_RESOURCE_PATH)); + check_path(in, &MAKE_RESOURCE_PATH(13, 26, 1), 42); - int64_t value; - ASSERT_OK(_anjay_get_i64_unlocked(in, &value)); - ASSERT_EQ(value, 42); - - // Context is restirected to /13/26/1, but it has more data to obtain, + // Context is restricted to /13/26/1, but it has more data to obtain, // which means the request is broken. TEST_TEARDOWN(FAIL); } @@ -142,7 +136,7 @@ AVS_UNIT_TEST(cbor_in_resource_permuted, single_instance_but_more_than_one) { "\x68/13/26/2" // text(8) }; TEST_ENV(RESOURCES, MAKE_RESOURCE_PATH(13, 26, 1)); - test_single_instance_but_more_than_one(in); + test_single_instance_but_more_than_one(in, &MAKE_RESOURCE_PATH(13, 26, 1)); TEST_TEARDOWN(OK); } @@ -162,7 +156,11 @@ AVS_UNIT_TEST(cbor_in_resource, multiple_instance) { "\x18\x2B" // unsigned(43) }; TEST_ENV(RESOURCES, TEST_RESOURCE_PATH); - test_multiple_instance(in); + check_paths(in, + (const anjay_uri_path_t[2]) { + MAKE_RESOURCE_INSTANCE_PATH(13, 26, 1, 4), + MAKE_RESOURCE_INSTANCE_PATH(13, 26, 1, 5) }, + 2); TEST_TEARDOWN(OK); } @@ -182,7 +180,11 @@ AVS_UNIT_TEST(cbor_in_resource_permuted, multiple_instance) { "\x6A/13/26/1/5" // text(10) }; TEST_ENV(RESOURCES, TEST_RESOURCE_PATH); - test_multiple_instance(in); + check_paths(in, + (const anjay_uri_path_t[2]) { + MAKE_RESOURCE_INSTANCE_PATH(13, 26, 1, 4), + MAKE_RESOURCE_INSTANCE_PATH(13, 26, 1, 5) }, + 2); TEST_TEARDOWN(OK); } @@ -196,30 +198,7 @@ AVS_UNIT_TEST(cbor_in_instance, with_simple_resource) { "\x18\x2A" // unsigned(42) }; TEST_ENV(RESOURCE, TEST_INSTANCE_PATH); - - anjay_uri_path_t path; - ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal( - &path, - &MAKE_RESOURCE_PATH(TEST_INSTANCE_PATH.ids[ANJAY_ID_OID], - TEST_INSTANCE_PATH.ids[ANJAY_ID_IID], - 1))); - - // cached value - ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal( - &path, - &MAKE_RESOURCE_PATH(TEST_INSTANCE_PATH.ids[ANJAY_ID_OID], - TEST_INSTANCE_PATH.ids[ANJAY_ID_IID], - 1))); - - int64_t value; - ASSERT_OK(_anjay_get_i64_unlocked(in, &value)); - ASSERT_EQ(value, 42); - - ASSERT_OK(_anjay_input_next_entry(in)); - ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_GET_PATH_END); - + check_paths(in, &MAKE_RESOURCE_PATH(13, 26, 1), 1); TEST_TEARDOWN(OK); } @@ -239,33 +218,10 @@ AVS_UNIT_TEST(cbor_in_instance, with_more_than_one_resource) { "\x18\x2B" // unsigned(43) }; TEST_ENV(RESOURCES, TEST_INSTANCE_PATH); - - anjay_uri_path_t path; - ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal( - &path, - &MAKE_RESOURCE_PATH(TEST_INSTANCE_PATH.ids[ANJAY_ID_OID], - TEST_INSTANCE_PATH.ids[ANJAY_ID_IID], - 1))); - - int64_t value; - ASSERT_OK(_anjay_get_i64_unlocked(in, &value)); - ASSERT_EQ(value, 42); - - ASSERT_OK(_anjay_input_next_entry(in)); - ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal( - &path, - &MAKE_RESOURCE_PATH(TEST_INSTANCE_PATH.ids[ANJAY_ID_OID], - TEST_INSTANCE_PATH.ids[ANJAY_ID_IID], - 2))); - - ASSERT_OK(_anjay_get_i64_unlocked(in, &value)); - ASSERT_EQ(value, 43); - - ASSERT_OK(_anjay_input_next_entry(in)); - ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_GET_PATH_END); - + check_paths(in, + (const anjay_uri_path_t[2]) { MAKE_RESOURCE_PATH(13, 26, 1), + MAKE_RESOURCE_PATH(13, 26, 2) }, + 2); TEST_TEARDOWN(OK); } @@ -285,7 +241,10 @@ AVS_UNIT_TEST(cbor_in_instance, resource_skipping) { "\x18\x2B" // unsigned(43) }; TEST_ENV(RESOURCES, TEST_INSTANCE_PATH); - test_resource_skipping(in); + test_skipping(in, + (const anjay_uri_path_t[2]) { MAKE_RESOURCE_PATH(13, 26, 1), + MAKE_RESOURCE_PATH(13, 26, 2) }, + 2); TEST_TEARDOWN(OK); } @@ -305,7 +264,10 @@ AVS_UNIT_TEST(cbor_in_instance_permuted, resource_skipping) { "\x68/13/26/2" // text(8) }; TEST_ENV(RESOURCES, TEST_INSTANCE_PATH); - test_resource_skipping(in); + test_skipping(in, + (const anjay_uri_path_t[2]) { MAKE_RESOURCE_PATH(13, 26, 1), + MAKE_RESOURCE_PATH(13, 26, 2) }, + 2); TEST_TEARDOWN(OK); } @@ -325,34 +287,11 @@ AVS_UNIT_TEST(cbor_in_instance, multiple_resource_skipping) { "\x18\x2B" // unsigned(43) }; TEST_ENV(RESOURCES, TEST_INSTANCE_PATH); - - anjay_uri_path_t path; - ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal( - &path, - &MAKE_RESOURCE_INSTANCE_PATH(TEST_INSTANCE_PATH.ids[ANJAY_ID_OID], - TEST_INSTANCE_PATH.ids[ANJAY_ID_IID], - 1, - 4))); - - // we may not like this resource for some reason, let's skip its value - ASSERT_OK(_anjay_input_next_entry(in)); - - ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal( - &path, - &MAKE_RESOURCE_INSTANCE_PATH(TEST_INSTANCE_PATH.ids[ANJAY_ID_OID], - TEST_INSTANCE_PATH.ids[ANJAY_ID_IID], - 2, - 5))); - - int64_t value; - ASSERT_OK(_anjay_get_i64_unlocked(in, &value)); - ASSERT_EQ(value, 43); - - ASSERT_OK(_anjay_input_next_entry(in)); - ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_GET_PATH_END); - + test_skipping(in, + (const anjay_uri_path_t[2]) { + MAKE_RESOURCE_INSTANCE_PATH(13, 26, 1, 4), + MAKE_RESOURCE_INSTANCE_PATH(13, 26, 2, 5) }, + 2); TEST_TEARDOWN(OK); } @@ -372,25 +311,10 @@ AVS_UNIT_TEST(cbor_in_object, with_single_instance_and_some_resources) { "\x18\x2B" // unsigned(43) }; TEST_ENV(RESOURCES, MAKE_OBJECT_PATH(13)); - - anjay_uri_path_t path; - ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal(&path, &MAKE_RESOURCE_PATH(13, 26, 1))); - - int64_t value; - ASSERT_OK(_anjay_get_i64_unlocked(in, &value)); - ASSERT_EQ(value, 42); - - ASSERT_OK(_anjay_input_next_entry(in)); - ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal(&path, &MAKE_RESOURCE_PATH(13, 26, 2))); - - ASSERT_OK(_anjay_get_i64_unlocked(in, &value)); - ASSERT_EQ(value, 43); - - ASSERT_OK(_anjay_input_next_entry(in)); - ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_GET_PATH_END); - + check_paths(in, + (const anjay_uri_path_t[2]) { MAKE_RESOURCE_PATH(13, 26, 1), + MAKE_RESOURCE_PATH(13, 26, 2) }, + 2); TEST_TEARDOWN(OK); } @@ -420,57 +344,28 @@ AVS_UNIT_TEST(cbor_in_object, with_some_instances_and_some_resources) { "\x68/13/27/4" // text(8) "\x02" // unsigned(2) => SenML Value "\x18\x2D" // unsigned(45) - }; TEST_ENV(RESOURCES, MAKE_OBJECT_PATH(13)); - - anjay_uri_path_t path; - ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal(&path, &MAKE_RESOURCE_PATH(13, 26, 1))); - - int64_t value; - ASSERT_OK(_anjay_get_i64_unlocked(in, &value)); - ASSERT_EQ(value, 42); - - ASSERT_OK(_anjay_input_next_entry(in)); - ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal(&path, &MAKE_RESOURCE_PATH(13, 26, 2))); - - ASSERT_OK(_anjay_get_i64_unlocked(in, &value)); - ASSERT_EQ(value, 43); - - ASSERT_OK(_anjay_input_next_entry(in)); - ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal(&path, &MAKE_RESOURCE_PATH(13, 27, 3))); - - ASSERT_OK(_anjay_get_i64_unlocked(in, &value)); - ASSERT_EQ(value, 44); - - ASSERT_OK(_anjay_input_next_entry(in)); - ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal(&path, &MAKE_RESOURCE_PATH(13, 27, 4))); - - ASSERT_OK(_anjay_get_i64_unlocked(in, &value)); - ASSERT_EQ(value, 45); - - ASSERT_OK(_anjay_input_next_entry(in)); - ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_GET_PATH_END); - + check_paths(in, + (const anjay_uri_path_t[4]) { MAKE_RESOURCE_PATH(13, 26, 1), + MAKE_RESOURCE_PATH(13, 26, 2), + MAKE_RESOURCE_PATH(13, 27, 3), + MAKE_RESOURCE_PATH(13, 27, 4) }, + 4); TEST_TEARDOWN(OK); } -#define TEST_VALUE_ENV(TypeAndValue) \ - static const char RESOURCE[] = { "\x81" /* array(1) */ \ - "\xA2" /* map(2) */ \ - "\x00" /* unsigned(0) => SenML Name */ \ - "\x68/13/26/1" /* text(8) */ \ - TypeAndValue }; \ - TEST_ENV(RESOURCE, MAKE_RESOURCE_PATH(13, 26, 1)); \ - { \ - anjay_uri_path_t path; \ - ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); \ - ASSERT_TRUE( \ - _anjay_uri_path_equal(&path, &MAKE_RESOURCE_PATH(13, 26, 1))); \ +#define TEST_VALUE_ENV(TypeAndValue) \ + static const char RESOURCE[] = { "\x81" /* array(1) */ \ + "\xA2" /* map(2) */ \ + "\x00" /* unsigned(0) => SenML Name */ \ + "\x68/13/26/1" /* text(8) */ \ + TypeAndValue }; \ + TEST_ENV(RESOURCE, MAKE_RESOURCE_PATH(13, 26, 1)); \ + { \ + anjay_uri_path_t path; \ + ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); \ + URI_EQUAL(&path, &MAKE_RESOURCE_PATH(13, 26, 1)); \ } AVS_UNIT_TEST(cbor_in_value, string_with_zero_length_buffer) { @@ -493,8 +388,8 @@ AVS_UNIT_TEST(cbor_in_value, bytes_with_too_short_buffer) { char buf[16] = "nothing"; size_t bytes_read; bool message_finished; - ASSERT_OK(_anjay_get_bytes_unlocked( - in, &bytes_read, &message_finished, buf, 0)); + ASSERT_OK(_anjay_get_bytes_unlocked(in, &bytes_read, &message_finished, buf, + 0)); ASSERT_EQ(bytes_read, 0); ASSERT_EQ(message_finished, false); ASSERT_EQ(buf[0], 'n'); @@ -680,6 +575,7 @@ AVS_UNIT_TEST(cbor_in, valid_paths) { AVS_UNIT_TEST(cbor_in, invalid_paths) { anjay_uri_path_t path; ASSERT_FAIL(parse_absolute_path(&path, "")); + ASSERT_FAIL(parse_absolute_path(&path, "1")); ASSERT_FAIL(parse_absolute_path(&path, "//")); ASSERT_FAIL(parse_absolute_path(&path, "/1/")); ASSERT_FAIL(parse_absolute_path(&path, "/1/2/")); @@ -718,8 +614,8 @@ AVS_UNIT_TEST(cbor_in, get_bytes_before_get_id) { // unsigned(8) => SenML Data & bytes(foobar) TEST_VALUE_ENV("\x08\x46" "foobar"); - ASSERT_FAIL(_anjay_get_bytes_unlocked( - in, &(size_t) { 0 }, &(bool) { false }, (char[32]){}, 32)); + ASSERT_FAIL(_anjay_get_bytes_unlocked(in, &(size_t) { 0 }, + &(bool) { false }, (char[32]){}, 32)); TEST_TEARDOWN(FAIL); } @@ -744,8 +640,8 @@ AVS_UNIT_TEST(cbor_in, get_objlnk_before_get_id) { "vlo" "\x68" "32:42532"); - ASSERT_FAIL(_anjay_get_objlnk_unlocked( - in, &(anjay_oid_t) { 0 }, &(anjay_iid_t) { 0 })); + ASSERT_FAIL(_anjay_get_objlnk_unlocked(in, &(anjay_oid_t) { 0 }, + &(anjay_iid_t) { 0 })); TEST_TEARDOWN(FAIL); } @@ -759,8 +655,7 @@ AVS_UNIT_TEST(cbor_in, get_path_for_resource_instance_path) { TEST_ENV(RESOURCE_INSTANCE_PATH, MAKE_RESOURCE_INSTANCE_PATH(3, 0, 0, 1)); anjay_uri_path_t path; ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal( - &path, &MAKE_RESOURCE_INSTANCE_PATH(3, 0, 0, 1))); + URI_EQUAL(&path, &MAKE_RESOURCE_INSTANCE_PATH(3, 0, 0, 1)); TEST_TEARDOWN(OK); } diff --git a/tests/core/io/corelnk.c b/tests/core/io/corelnk.c new file mode 100644 index 00000000..68f04734 --- /dev/null +++ b/tests/core/io/corelnk.c @@ -0,0 +1,91 @@ +/* + * Copyright 2017-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#define AVS_UNIT_ENABLE_SHORT_ASSERTS +#include +#include + +#include "tests/utils/dm.h" + +static const anjay_dm_object_def_t *const OBJ2 = + &(const anjay_dm_object_def_t) { + .oid = 69, + .version = "21.37", + .handlers = { ANJAY_MOCK_DM_HANDLERS_BASIC } + }; + +static const anjay_dm_object_def_t *const FAKE_SERVER_WITH_VER = + &(const anjay_dm_object_def_t) { + .oid = 1, + .version = "1.1", + .handlers = { ANJAY_MOCK_DM_HANDLERS } + }; + +#define PREPARE_DM() \ + /* Security and OSCORE objects should be omitted */ \ + _anjay_mock_dm_expect_list_instances( \ + anjay, &FAKE_SERVER_WITH_VER, 0, \ + (const anjay_iid_t[]) { 14, 42, 69, ANJAY_ID_INVALID }); \ + _anjay_mock_dm_expect_list_instances(anjay, &OBJ_WITH_RESET, 0, \ + (const anjay_iid_t[]) { \ + ANJAY_ID_INVALID }); \ + _anjay_mock_dm_expect_list_instances( \ + anjay, &OBJ, 0, (const anjay_iid_t[]) { 14, ANJAY_ID_INVALID }); \ + _anjay_mock_dm_expect_list_instances( \ + anjay, &OBJ2, 0, \ + (const anjay_iid_t[]) { 14, 42, 69, ANJAY_ID_INVALID }); \ + _anjay_mock_dm_expect_list_instances( \ + anjay, (const anjay_dm_object_def_t *const *) &EXECUTE_OBJ, 0, \ + (const anjay_iid_t[]) { 14, 42, 69, ANJAY_ID_INVALID }); + +AVS_UNIT_TEST(io_corelnk, test_corelnk_output) { + DM_TEST_INIT_WITH_OBJECTS( + &OBJ2, &OBJ, &FAKE_SECURITY, &FAKE_SERVER_WITH_VER, + (const anjay_dm_object_def_t *const *) &EXECUTE_OBJ, + (const anjay_dm_object_def_t *const *) &OBJ_WITH_RESET); + + char *buf = NULL; + PREPARE_DM(); + + ANJAY_MUTEX_LOCK(anjay_unlocked, anjay); + AVS_UNIT_ASSERT_SUCCESS( + _anjay_corelnk_query_dm(anjay_unlocked, &anjay_unlocked->dm, + ANJAY_LWM2M_VERSION_1_0, &buf)); + ANJAY_MUTEX_UNLOCK(anjay); + AVS_UNIT_ASSERT_EQUAL_STRING( + buf, + ";ver=\"1.1\",,,,,,;ver=\"21.37\",,,,,,"); + avs_free(buf); + buf = NULL; +#ifdef ANJAY_WITH_LWM2M11 + PREPARE_DM(); + ANJAY_MUTEX_LOCK(anjay_unlocked, anjay); + AVS_UNIT_ASSERT_SUCCESS( + _anjay_corelnk_query_dm(anjay_unlocked, &anjay_unlocked->dm, + ANJAY_LWM2M_VERSION_1_1, &buf)); + ANJAY_MUTEX_UNLOCK(anjay); + + // both versions are valid + char *with_version = ";ver=1.1,,,,,,;ver=21.37,,,,,,"; + char *without_version = + ",,,,,;ver=21.37,,,,,,"; + ASSERT_TRUE(strcmp(buf, with_version) == 0 + || strcmp(buf, without_version) == 0); + avs_free(buf); + buf = NULL; +#endif // ANJAY_WITH_LWM2M11 + + DM_TEST_FINISH; + avs_free(buf); +} diff --git a/tests/core/io/json_in.c b/tests/core/io/json_in.c index e570003c..7de8621c 100644 --- a/tests/core/io/json_in.c +++ b/tests/core/io/json_in.c @@ -24,14 +24,14 @@ AVS_UNIT_TEST(json_in_resource, single_instance) { static const char RESOURCE[] = "[ { \"n\": \"/13/26/1\", \"v\": 42 } ]"; TEST_ENV(RESOURCE, TEST_RESOURCE_PATH); - test_single_instance(in); + check_paths(in, &MAKE_RESOURCE_PATH(13, 26, 1), 1); TEST_TEARDOWN(OK); } AVS_UNIT_TEST(json_in_resource_permuted, single_instance) { static const char RESOURCE[] = "[ { \"v\": 42, \"n\": \"/13/26/1\" } ]"; TEST_ENV(RESOURCE, TEST_RESOURCE_PATH); - test_single_instance(in); + check_paths(in, &MAKE_RESOURCE_PATH(13, 26, 1), 1); TEST_TEARDOWN(OK); } @@ -61,7 +61,7 @@ AVS_UNIT_TEST(json_in_resource, single_instance_but_more_than_one) { static const char RESOURCES[] = "[ { \"n\": \"/13/26/1\", \"v\": 42 }, " "{ \"n\": \"/13/26/2\", \"v\": 43 } ]"; TEST_ENV(RESOURCES, TEST_RESOURCE_PATH); - test_single_instance_but_more_than_one(in); + test_single_instance_but_more_than_one(in, &MAKE_RESOURCE_PATH(13, 26, 1)); TEST_TEARDOWN(OK); } @@ -70,15 +70,9 @@ AVS_UNIT_TEST(json_in_resource, static const char RESOURCES[] = "[ { \"n\": \"/13/26/1\", \"v\": 42 }, " "{ \"n\": \"/13/26/2\", \"v\": 43 } ]"; TEST_ENV(RESOURCES, TEST_RESOURCE_PATH); - anjay_uri_path_t path; - ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal(&path, &TEST_RESOURCE_PATH)); - - int64_t value; - ASSERT_OK(_anjay_get_i64_unlocked(in, &value)); - ASSERT_EQ(value, 42); + check_path(in, &MAKE_RESOURCE_PATH(13, 26, 1), 42); - // Context is restirected to /13/26/1, but it has more data to obtain, + // Context is restricted to /13/26/1, but it has more data to obtain, // which means the request is broken. TEST_TEARDOWN(FAIL); } @@ -100,8 +94,8 @@ AVS_UNIT_TEST(json_in_resource, single_instance_with_first_resource_unrelated) { AVS_UNIT_TEST(json_in_resource_permuted, single_instance_but_more_than_one) { static const char RESOURCES[] = "[ { \"v\": 42, \"n\": \"/13/26/1\" }, " "{ \"v\": 43, \"n\": \"/13/26/2\" } ]"; - TEST_ENV(RESOURCES, MAKE_RESOURCE_PATH(13, 26, 1)); - test_single_instance_but_more_than_one(in); + TEST_ENV(RESOURCES, TEST_RESOURCE_PATH); + test_single_instance_but_more_than_one(in, &MAKE_RESOURCE_PATH(13, 26, 1)); TEST_TEARDOWN(OK); } @@ -109,7 +103,11 @@ AVS_UNIT_TEST(json_in_resource, multiple_instance) { static const char RESOURCES[] = "[ { \"n\": \"/13/26/1/4\", \"v\": 42 }, " "{ \"n\": \"/13/26/1/5\", \"v\": 43 } ]"; TEST_ENV(RESOURCES, TEST_RESOURCE_PATH); - test_multiple_instance(in); + check_paths(in, + (const anjay_uri_path_t[2]) { + MAKE_RESOURCE_INSTANCE_PATH(13, 26, 1, 4), + MAKE_RESOURCE_INSTANCE_PATH(13, 26, 1, 5) }, + 2); TEST_TEARDOWN(OK); } @@ -117,37 +115,18 @@ AVS_UNIT_TEST(json_in_resource_permuted, multiple_instance) { static const char RESOURCES[] = "[ { \"v\": 42, \"n\": \"/13/26/1/4\" }, " "{ \"v\": 43, \"n\": \"/13/26/1/5\" } ]"; TEST_ENV(RESOURCES, TEST_RESOURCE_PATH); - test_multiple_instance(in); + check_paths(in, + (const anjay_uri_path_t[2]) { + MAKE_RESOURCE_INSTANCE_PATH(13, 26, 1, 4), + MAKE_RESOURCE_INSTANCE_PATH(13, 26, 1, 5) }, + 2); TEST_TEARDOWN(OK); } AVS_UNIT_TEST(json_in_instance, with_simple_resource) { static const char RESOURCE[] = "[ { \"n\": \"/13/26/1\", \"v\": 42 } ]"; TEST_ENV(RESOURCE, TEST_INSTANCE_PATH); - - anjay_uri_path_t path; - ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal( - &path, - &MAKE_RESOURCE_PATH(TEST_INSTANCE_PATH.ids[ANJAY_ID_OID], - TEST_INSTANCE_PATH.ids[ANJAY_ID_IID], - 1))); - - // cached value - ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal( - &path, - &MAKE_RESOURCE_PATH(TEST_INSTANCE_PATH.ids[ANJAY_ID_OID], - TEST_INSTANCE_PATH.ids[ANJAY_ID_IID], - 1))); - - int64_t value; - ASSERT_OK(_anjay_get_i64_unlocked(in, &value)); - ASSERT_EQ(value, 42); - - ASSERT_OK(_anjay_input_next_entry(in)); - ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_GET_PATH_END); - + check_paths(in, &MAKE_RESOURCE_PATH(13, 26, 1), 1); TEST_TEARDOWN(OK); } @@ -155,33 +134,10 @@ AVS_UNIT_TEST(json_in_instance, with_more_than_one_resource) { static const char RESOURCES[] = "[ { \"n\": \"/13/26/1\", \"v\": 42 }, " "{ \"n\": \"/13/26/2\", \"v\": 43 } ]"; TEST_ENV(RESOURCES, TEST_INSTANCE_PATH); - - anjay_uri_path_t path; - ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal( - &path, - &MAKE_RESOURCE_PATH(TEST_INSTANCE_PATH.ids[ANJAY_ID_OID], - TEST_INSTANCE_PATH.ids[ANJAY_ID_IID], - 1))); - - int64_t value; - ASSERT_OK(_anjay_get_i64_unlocked(in, &value)); - ASSERT_EQ(value, 42); - - ASSERT_OK(_anjay_input_next_entry(in)); - ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal( - &path, - &MAKE_RESOURCE_PATH(TEST_INSTANCE_PATH.ids[ANJAY_ID_OID], - TEST_INSTANCE_PATH.ids[ANJAY_ID_IID], - 2))); - - ASSERT_OK(_anjay_get_i64_unlocked(in, &value)); - ASSERT_EQ(value, 43); - - ASSERT_OK(_anjay_input_next_entry(in)); - ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_GET_PATH_END); - + check_paths(in, + (const anjay_uri_path_t[2]) { MAKE_RESOURCE_PATH(13, 26, 1), + MAKE_RESOURCE_PATH(13, 26, 2) }, + 2); TEST_TEARDOWN(OK); } @@ -189,7 +145,10 @@ AVS_UNIT_TEST(json_in_instance, resource_skipping) { static const char RESOURCES[] = "[ { \"n\": \"/13/26/1\", \"v\": 42 }, " "{ \"n\": \"/13/26/2\", \"v\": 43 } ]"; TEST_ENV(RESOURCES, TEST_INSTANCE_PATH); - test_resource_skipping(in); + test_skipping(in, + (const anjay_uri_path_t[2]) { MAKE_RESOURCE_PATH(13, 26, 1), + MAKE_RESOURCE_PATH(13, 26, 2) }, + 2); TEST_TEARDOWN(OK); } @@ -225,7 +184,10 @@ AVS_UNIT_TEST(json_in_instance_permuted, resource_skipping) { static const char RESOURCES[] = "[ { \"v\": 42, \"n\": \"/13/26/1\" }, " "{ \"v\": 43, \"n\": \"/13/26/2\" } ]"; TEST_ENV(RESOURCES, TEST_INSTANCE_PATH); - test_resource_skipping(in); + test_skipping(in, + (const anjay_uri_path_t[2]) { MAKE_RESOURCE_PATH(13, 26, 1), + MAKE_RESOURCE_PATH(13, 26, 2) }, + 2); TEST_TEARDOWN(OK); } @@ -233,34 +195,11 @@ AVS_UNIT_TEST(json_in_instance, multiple_resource_skipping) { static const char RESOURCES[] = "[ { \"n\": \"/13/26/1/4\", \"v\": 42 }, " "{ \"n\": \"/13/26/2/5\", \"v\": 43 } ]"; TEST_ENV(RESOURCES, TEST_INSTANCE_PATH); - - anjay_uri_path_t path; - ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal( - &path, - &MAKE_RESOURCE_INSTANCE_PATH(TEST_INSTANCE_PATH.ids[ANJAY_ID_OID], - TEST_INSTANCE_PATH.ids[ANJAY_ID_IID], - 1, - 4))); - - // we may not like this resource for some reason, let's skip its value - ASSERT_OK(_anjay_input_next_entry(in)); - - ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal( - &path, - &MAKE_RESOURCE_INSTANCE_PATH(TEST_INSTANCE_PATH.ids[ANJAY_ID_OID], - TEST_INSTANCE_PATH.ids[ANJAY_ID_IID], - 2, - 5))); - - int64_t value; - ASSERT_OK(_anjay_get_i64_unlocked(in, &value)); - ASSERT_EQ(value, 43); - - ASSERT_OK(_anjay_input_next_entry(in)); - ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_GET_PATH_END); - + test_skipping(in, + (const anjay_uri_path_t[2]) { + MAKE_RESOURCE_INSTANCE_PATH(13, 26, 1, 4), + MAKE_RESOURCE_INSTANCE_PATH(13, 26, 2, 5) }, + 2); TEST_TEARDOWN(OK); } @@ -268,25 +207,10 @@ AVS_UNIT_TEST(json_in_object, with_single_instance_and_some_resources) { static const char RESOURCES[] = "[ { \"n\": \"/13/26/1\", \"v\": 42 }, " "{ \"n\": \"/13/26/2\", \"v\": 43 } ]"; TEST_ENV(RESOURCES, MAKE_OBJECT_PATH(13)); - - anjay_uri_path_t path; - ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal(&path, &MAKE_RESOURCE_PATH(13, 26, 1))); - - int64_t value; - ASSERT_OK(_anjay_get_i64_unlocked(in, &value)); - ASSERT_EQ(value, 42); - - ASSERT_OK(_anjay_input_next_entry(in)); - ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal(&path, &MAKE_RESOURCE_PATH(13, 26, 2))); - - ASSERT_OK(_anjay_get_i64_unlocked(in, &value)); - ASSERT_EQ(value, 43); - - ASSERT_OK(_anjay_input_next_entry(in)); - ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_GET_PATH_END); - + check_paths(in, + (const anjay_uri_path_t[2]) { MAKE_RESOURCE_PATH(13, 26, 1), + MAKE_RESOURCE_PATH(13, 26, 2) }, + 2); TEST_TEARDOWN(OK); } @@ -306,39 +230,12 @@ AVS_UNIT_TEST(json_in_object, with_some_instances_and_some_resources) { "{ \"n\": \"/13/27/3\", \"v\": 44 }, " "{ \"n\": \"/13/27/4\", \"v\": 45 } ]"; TEST_ENV(RESOURCES, MAKE_OBJECT_PATH(13)); - - anjay_uri_path_t path; - ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal(&path, &MAKE_RESOURCE_PATH(13, 26, 1))); - - int64_t value; - ASSERT_OK(_anjay_get_i64_unlocked(in, &value)); - ASSERT_EQ(value, 42); - - ASSERT_OK(_anjay_input_next_entry(in)); - ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal(&path, &MAKE_RESOURCE_PATH(13, 26, 2))); - - ASSERT_OK(_anjay_get_i64_unlocked(in, &value)); - ASSERT_EQ(value, 43); - - ASSERT_OK(_anjay_input_next_entry(in)); - ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal(&path, &MAKE_RESOURCE_PATH(13, 27, 3))); - - ASSERT_OK(_anjay_get_i64_unlocked(in, &value)); - ASSERT_EQ(value, 44); - - ASSERT_OK(_anjay_input_next_entry(in)); - ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal(&path, &MAKE_RESOURCE_PATH(13, 27, 4))); - - ASSERT_OK(_anjay_get_i64_unlocked(in, &value)); - ASSERT_EQ(value, 45); - - ASSERT_OK(_anjay_input_next_entry(in)); - ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_GET_PATH_END); - + check_paths(in, + (const anjay_uri_path_t[4]) { MAKE_RESOURCE_PATH(13, 26, 1), + MAKE_RESOURCE_PATH(13, 26, 2), + MAKE_RESOURCE_PATH(13, 27, 3), + MAKE_RESOURCE_PATH(13, 27, 4) }, + 4); TEST_TEARDOWN(OK); } diff --git a/tests/core/io/lwm2m_cbor_out.c b/tests/core/io/lwm2m_cbor_out.c new file mode 100644 index 00000000..744edfe9 --- /dev/null +++ b/tests/core/io/lwm2m_cbor_out.c @@ -0,0 +1,173 @@ +/* + * Copyright 2017-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#include + +#include +#include +#include + +#define AVS_UNIT_ENABLE_SHORT_ASSERTS +#include + +#define BUFFER_SIZE 64 + +#define TEST_ROOT_PATH (anjay_uri_path_t) ROOT_PATH_INITIALIZER() +#define TEST_OBJ_INST(Obj, Inst) \ + (anjay_uri_path_t) INSTANCE_PATH_INITIALIZER(Obj, Inst) +#define TEST_OBJ_INST_RES(Obj, Inst, Res) \ + (anjay_uri_path_t) RESOURCE_PATH_INITIALIZER(Obj, Inst, Res) +#define TEST_OBJ_INST_RES_INST(Obj, Inst, Res, ResInst) \ + (anjay_uri_path_t) \ + RESOURCE_INSTANCE_PATH_INITIALIZER(Obj, Inst, Res, ResInst) + +#define TEST_ENV(Path) \ + static char stream_buffer[BUFFER_SIZE] = { 0 }; \ + avs_stream_outbuf_t stream = AVS_STREAM_OUTBUF_STATIC_INITIALIZER; \ + avs_stream_outbuf_set_buffer(&stream, stream_buffer, BUFFER_SIZE); \ + anjay_unlocked_output_ctx_t *out; \ + out = _anjay_output_lwm2m_cbor_create((avs_stream_t *) &stream, &(Path)); \ + ASSERT_NOT_NULL(out); \ + ASSERT_OK(_anjay_output_set_path(out, &(Path))); + +#define TEST_TEARDOWN(ExpectedData) \ + do { \ + ASSERT_OK(_anjay_output_ctx_destroy(&out)); \ + ASSERT_EQ_BYTES_SIZED( \ + stream_buffer, (ExpectedData), sizeof(ExpectedData) - 1); \ + } while (0) + +AVS_UNIT_TEST(lwm2m_cbor_out, single_resource) { + TEST_ENV(TEST_OBJ_INST_RES(13, 26, 1)); + + ASSERT_OK(_anjay_ret_i64_unlocked(out, 42)); + + // clang-format off + static const char EXPECTED_DATA[] = { + // {[13, 26, 1]: 42} + "\xBF" // map(*) + "\x83" // array(3) + "\x0D" // unsigned(13) + "\x18\x1A" // unsigned(26) + "\x01" // unsigned(1) + "\x18\x2A" // unsigned(42) + "\xFF" // primitive(*) + }; + // clang-format on + TEST_TEARDOWN(EXPECTED_DATA); +} + +AVS_UNIT_TEST(lwm2m_cbor_out, two_resources) { + TEST_ENV(TEST_OBJ_INST(13, 26)); + + ASSERT_OK(_anjay_output_start_aggregate(out)); + ASSERT_OK(_anjay_output_set_path(out, &TEST_OBJ_INST_RES(13, 26, 1))); + ASSERT_OK(_anjay_ret_i64_unlocked(out, 42)); + ASSERT_OK(_anjay_output_set_path(out, &TEST_OBJ_INST_RES(13, 26, 2))); + ASSERT_OK(_anjay_ret_i64_unlocked(out, 21)); + + // clang-format off + static const char EXPECTED_DATA[] = { + // {[13, 26]: {1: 42, 2: 21}} + "\xBF" // map(*) + "\x82" // array(2) + "\x0D" // unsigned(13) + "\x18\x1A" // unsigned(26) + "\xBF" // map(*) + "\x01" // unsigned(1) + "\x18\x2A" // unsigned(42) + "\x02" // unsigned(2) + "\x15" // unsigned(21) + "\xFF" // primitive(*) + "\xFF" // primitive(*) + }; + // clang-format on + TEST_TEARDOWN(EXPECTED_DATA); +} + +AVS_UNIT_TEST(lwm2m_cbor_out, resource_instances_nested_maps) { + TEST_ENV(TEST_OBJ_INST(13, 26)); + + ASSERT_OK(_anjay_output_start_aggregate(out)); + ASSERT_OK(_anjay_output_set_path(out, &TEST_OBJ_INST_RES(13, 26, 1))); + ASSERT_OK(_anjay_ret_i64_unlocked(out, 42)); + ASSERT_OK(_anjay_output_set_path(out, + &TEST_OBJ_INST_RES_INST(13, 26, 3, 21))); + ASSERT_OK(_anjay_ret_double_unlocked(out, 69.68)); + ASSERT_OK(_anjay_output_set_path(out, + &TEST_OBJ_INST_RES_INST(13, 26, 3, 37))); + ASSERT_OK(_anjay_ret_bool_unlocked(out, false)); + + // clang-format off + static const char EXPECTED_DATA[] = { + // {[13, 26]: {1: 42, 3: {21: 69.68, 37: false}}} + "\xBF" // map(*) + "\x82" // array(2) + "\x0D" // unsigned(13) + "\x18\x1A" // unsigned(26) + "\xBF" // map(*) + "\x01" // unsigned(1) + "\x18\x2A" // unsigned(42) + "\x03" // unsigned(2) + "\xBF" // map(*) + "\x15" // unsigned(21) + "\xFB\x40\x51\x6B\x85\x1E\xB8\x51\xEC" // primitive(69.68) + "\x18\x25" // unsigned(37) + "\xF4" // primitive(false) + "\xFF" // primitive(*) + "\xFF" // primitive(*) + "\xFF" // primitive(*) + }; + // clang-format on + TEST_TEARDOWN(EXPECTED_DATA); +} + +AVS_UNIT_TEST(lwm2m_cbor_out, two_objects_one_instance_two_resources) { + TEST_ENV(TEST_ROOT_PATH); + + ASSERT_OK(_anjay_output_set_path(out, &TEST_OBJ_INST_RES(13, 26, 1))); + ASSERT_OK(_anjay_ret_i64_unlocked(out, 42)); + ASSERT_OK(_anjay_output_set_path(out, &TEST_OBJ_INST_RES(13, 26, 2))); + ASSERT_OK(_anjay_ret_i64_unlocked(out, 21)); + + ASSERT_OK(_anjay_output_set_path(out, &TEST_OBJ_INST_RES(14, 27, 1))); + ASSERT_OK(_anjay_ret_i64_unlocked(out, 43)); + ASSERT_OK(_anjay_output_set_path(out, &TEST_OBJ_INST_RES(14, 27, 2))); + ASSERT_OK(_anjay_ret_i64_unlocked(out, 22)); + + // clang-format off + static const char EXPECTED_DATA[] = { + // {13: {26: {1: 42, 2: 21}}, 14: {27: {1: 43, 2: 22}}} + "\xBF" // map(*) + "\x0D" // unsigned(13) + "\xBF" // map(*) + "\x18\x1A" // unsigned(26) + "\xBF" // map(*) + "\x01" // unsigned(1) + "\x18\x2A" // unsigned(42) + "\x02" // unsigned(2) + "\x15" // unsigned(21) + "\xFF" // primitive(*) + "\xFF" // primitive(*) + "\x0E" // unsigned(14) + "\xBF" // map(*) + "\x18\x1B" // unsigned(27) + "\xBF" // map(*) + "\x01" // unsigned(1) + "\x18\x2B" // unsigned(43) + "\x02" // unsigned(2) + "\x16" // unsigned(22) + "\xFF" // primitive(*) + "\xFF" // primitive(*) + "\xFF" // primitive(*) + + }; + // clang-format on + TEST_TEARDOWN(EXPECTED_DATA); +} diff --git a/tests/core/io/senml_cbor_out.c b/tests/core/io/senml_cbor_out.c new file mode 100644 index 00000000..8595223e --- /dev/null +++ b/tests/core/io/senml_cbor_out.c @@ -0,0 +1,205 @@ +/* + * Copyright 2017-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#include + +#include +#include +#include + +#define AVS_UNIT_ENABLE_SHORT_ASSERTS +#include + +#define BUFFER_SIZE 128 + +#define TEST_ROOT_PATH (anjay_uri_path_t) ROOT_PATH_INITIALIZER() +#define TEST_OBJ_INST(Obj, Inst) \ + (anjay_uri_path_t) INSTANCE_PATH_INITIALIZER(Obj, Inst) +#define TEST_OBJ_INST_RES(Obj, Inst, Res) \ + (anjay_uri_path_t) RESOURCE_PATH_INITIALIZER(Obj, Inst, Res) +#define TEST_OBJ_INST_RES_INST(Obj, Inst, Res, ResInst) \ + (anjay_uri_path_t) \ + RESOURCE_INSTANCE_PATH_INITIALIZER(Obj, Inst, Res, ResInst) + +#define TEST_ENV(Path, ItemsCount) \ + static char stream_buffer[BUFFER_SIZE] = { 0 }; \ + avs_stream_outbuf_t stream = AVS_STREAM_OUTBUF_STATIC_INITIALIZER; \ + avs_stream_outbuf_set_buffer(&stream, stream_buffer, BUFFER_SIZE); \ + anjay_unlocked_output_ctx_t *out; \ + const size_t items_size = (ItemsCount); \ + out = _anjay_output_senml_like_create((avs_stream_t *) &stream, \ + &(Path), \ + AVS_COAP_FORMAT_SENML_CBOR, \ + &items_size); \ + ASSERT_NOT_NULL(out); \ + ASSERT_OK(_anjay_output_set_path(out, &(Path))); + +#define TEST_TEARDOWN(ExpectedData) \ + do { \ + ASSERT_OK(_anjay_output_ctx_destroy(&out)); \ + ASSERT_EQ_BYTES_SIZED( \ + stream_buffer, (ExpectedData), sizeof(ExpectedData) - 1); \ + } while (0) + +AVS_UNIT_TEST(senml_cbor_out, single_resource) { + TEST_ENV(TEST_OBJ_INST_RES(13, 26, 1), 1); + + ASSERT_OK(_anjay_ret_i64_unlocked(out, 42)); + + // clang-format off + static const char EXPECTED_DATA[] = { + // [ + // {"bn": "/13/26/1", "v": 42} + // ] + "\x81" // array(1) + "\xA2" // map(2) + "\x21" // negative(1) "bn" + "\x68" // text(8) + "\x2F\x31\x33\x2F\x32\x36\x2F\x31" // "/13/26/1" + "\x02" // unsigned(2) "v" + "\x18\x2A" // unsigned(42) + }; + // clang-format on + TEST_TEARDOWN(EXPECTED_DATA); +} + +AVS_UNIT_TEST(senml_cbor_out, two_resources) { + TEST_ENV(TEST_OBJ_INST(13, 26), 2); + + ASSERT_OK(_anjay_output_start_aggregate(out)); + ASSERT_OK(_anjay_output_set_path(out, &TEST_OBJ_INST_RES(13, 26, 1))); + ASSERT_OK(_anjay_ret_i64_unlocked(out, 42)); + ASSERT_OK(_anjay_output_set_path(out, &TEST_OBJ_INST_RES(13, 26, 2))); + ASSERT_OK(_anjay_ret_i64_unlocked(out, 21)); + + // clang-format off + static const char EXPECTED_DATA[] = { + // [ + // {"bn": "/13/26", "n": "/1", "v": 42}, + // {"n": "/2", "v": 21} + // ] + "\x82" // array(2) + "\xA3" // map(3) + "\x21" // negative(1) "bn" + "\x66" // text(6) + "\x2F\x31\x33\x2F\x32\x36" // "/13/26" + "\x00" // unsigned(0) "n" + "\x62" // text(2) + "\x2F\x31" // "/1" + "\x02" // unsigned(2) "v" + "\x18\x2A" // unsigned(42) + "\xA2" // map(2) + "\x00" // unsigned(0) "n" + "\x62" // text(2) + "\x2F\x32" // "/2" + "\x02" // unsigned(2) "v" + "\x15" // unsigned(21) + + }; + // clang-format on + TEST_TEARDOWN(EXPECTED_DATA); +} + +AVS_UNIT_TEST(senml_cbor_out, resource_instances_nested_maps) { + TEST_ENV(TEST_OBJ_INST(13, 26), 3); + + ASSERT_OK(_anjay_output_start_aggregate(out)); + ASSERT_OK(_anjay_output_set_path(out, &TEST_OBJ_INST_RES(13, 26, 1))); + ASSERT_OK(_anjay_ret_i64_unlocked(out, 42)); + ASSERT_OK(_anjay_output_set_path(out, + &TEST_OBJ_INST_RES_INST(13, 26, 3, 21))); + ASSERT_OK(_anjay_ret_double_unlocked(out, 69.68)); + ASSERT_OK(_anjay_output_set_path(out, + &TEST_OBJ_INST_RES_INST(13, 26, 3, 37))); + ASSERT_OK(_anjay_ret_bool_unlocked(out, false)); + + // clang-format off + static const char EXPECTED_DATA[] = { + // [ + // {"bn": "/13/26", "n": "/1", "v": 42}, + // {"n": "/3/21", "v": 69.68}, + // {"n": "/3/37", "vb": false} + // ] + "\x83" // array(3) + "\xA3" // map(3) + "\x21" // negative(1) "bn" + "\x66" // text(6) + "\x2F\x31\x33\x2F\x32\x36" // "/13/26" + "\x00" // unsigned(0) "n" + "\x62" // text(2) + "\x2F\x31" // "/1" + "\x02" // unsigned(2) "v" + "\x18\x2A" // unsigned(42) + "\xA2" // map(2) + "\x00" // unsigned(0) "n" + "\x65" // text(5) + "\x2F\x33\x2F\x32\x31" // "/3/21" + "\x02" // unsigned(2) "v" + "\xFB\x40\x51\x6B\x85\x1E\xB8\x51\xEC" // primitive(4634603711031169516) + "\xA2" // map(2) + "\x00" // unsigned(0) "n" + "\x65" // text(5) + "\x2F\x33\x2F\x33\x37" // "/3/37" + "\x04" // unsigned(4) "vb" + "\xF4" // primitive(20) + }; + // clang-format on + TEST_TEARDOWN(EXPECTED_DATA); +} + +AVS_UNIT_TEST(senml_cbor_out, two_objects_one_instance_two_resources) { + TEST_ENV(TEST_ROOT_PATH, 4); + + ASSERT_OK(_anjay_output_set_path(out, &TEST_OBJ_INST_RES(13, 26, 1))); + ASSERT_OK(_anjay_ret_i64_unlocked(out, 42)); + ASSERT_OK(_anjay_output_set_path(out, &TEST_OBJ_INST_RES(13, 26, 2))); + ASSERT_OK(_anjay_ret_i64_unlocked(out, 21)); + + ASSERT_OK(_anjay_output_set_path(out, &TEST_OBJ_INST_RES(14, 27, 1))); + ASSERT_OK(_anjay_ret_i64_unlocked(out, 43)); + ASSERT_OK(_anjay_output_set_path(out, &TEST_OBJ_INST_RES(14, 27, 2))); + ASSERT_OK(_anjay_ret_i64_unlocked(out, 22)); + + // clang-format off + static const char EXPECTED_DATA[] = { + // [ + // {"n": "/13/26/1", "v": 42}, + // {"n": "/13/26/2", "v": 21}, + // {"n": "/14/27/1", "v": 43}, + // {"n": "/14/27/2", "v": 22} + // ] + "\x84" // array(4) + "\xA2" // map(2) + "\x00" // unsigned(0) "n" + "\x68" // text(8) + "\x2F\x31\x33\x2F\x32\x36\x2F\x31" // "/13/26/1" + "\x02" // unsigned(2) "v" + "\x18\x2A" // unsigned(42) + "\xA2" // map(2) + "\x00" // unsigned(0) "n" + "\x68" // text(8) + "\x2F\x31\x33\x2F\x32\x36\x2F\x32" // "/13/26/2" + "\x02" // unsigned(2) "v" + "\x15" // unsigned(21) + "\xA2" // map(2) + "\x00" // unsigned(0) "n" + "\x68" // text(8) + "\x2F\x31\x34\x2F\x32\x37\x2F\x31" // "/14/27/1" + "\x02" // unsigned(2) "v" + "\x18\x2B" // unsigned(43) + "\xA2" // map(2) + "\x00" // unsigned(0) "n" + "\x68" // text(8) + "\x2F\x31\x34\x2F\x32\x37\x2F\x32" // "/14/27/2" + "\x02" // unsigned(2) "v" + "\x16" // unsigned(22) + }; + // clang-format on + TEST_TEARDOWN(EXPECTED_DATA); +} diff --git a/tests/core/io/senml_in_common.h b/tests/core/io/senml_in_common.h index 1eb3a6ba..85527af9 100644 --- a/tests/core/io/senml_in_common.h +++ b/tests/core/io/senml_in_common.h @@ -20,22 +20,38 @@ AVS_CONCAT(ASSERT_, ExpectedResult)(_anjay_input_ctx_destroy(&in)); \ } while (0) +#define URI_EQUAL(path, expected_path) \ + ASSERT_TRUE(_anjay_uri_path_equal(path, expected_path)); + static const anjay_uri_path_t TEST_RESOURCE_PATH = RESOURCE_PATH_INITIALIZER(13, 26, 1); -static void test_single_instance(anjay_unlocked_input_ctx_t *in) { +static const anjay_uri_path_t TEST_INSTANCE_PATH = + INSTANCE_PATH_INITIALIZER(13, 26); + +static void check_path(anjay_unlocked_input_ctx_t *in, + const anjay_uri_path_t *expected_path, + int64_t expected_value) { anjay_uri_path_t path; + int64_t value; ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal(&path, &TEST_RESOURCE_PATH)); + URI_EQUAL(&path, expected_path); // cached value ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal(&path, &TEST_RESOURCE_PATH)); + URI_EQUAL(&path, expected_path); - int64_t value; ASSERT_OK(_anjay_get_i64_unlocked(in, &value)); - ASSERT_EQ(value, 42); + ASSERT_EQ(value, expected_value); +} +static void check_paths(anjay_unlocked_input_ctx_t *in, + const anjay_uri_path_t *expected_paths, + const size_t paths_count) { + for (size_t i = 0; i < paths_count; i++) { + check_path(in, &expected_paths[i], 42 + (int) i); + ASSERT_OK(_anjay_input_next_entry(in)); + } ASSERT_OK(_anjay_input_next_entry(in)); ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_GET_PATH_END); ASSERT_EQ(_anjay_json_like_decoder_state(((senml_in_t *) in)->ctx), @@ -43,78 +59,31 @@ static void test_single_instance(anjay_unlocked_input_ctx_t *in) { } static void -test_single_instance_but_more_than_one(anjay_unlocked_input_ctx_t *in) { - anjay_uri_path_t path; - ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal(&path, &TEST_RESOURCE_PATH)); - - int64_t value; - ASSERT_OK(_anjay_get_i64_unlocked(in, &value)); - ASSERT_EQ(value, 42); - +test_single_instance_but_more_than_one(anjay_unlocked_input_ctx_t *in, + const anjay_uri_path_t *expected_path) { + check_path(in, expected_path, 42); ASSERT_OK(_anjay_input_next_entry(in)); - // The resource is there, but the context doesn't return it because it is - // not related to the request resource path /13/26/1. In order to actually - // get it, we would have to do a request on an instance. Because the context - // top-level path is restricted, obtaining next id results in error. + // The resource is there, but the context doesn't return it because it + // is not related to the request resource path /13/26/1. In order to + // actually get it, we would have to do a request on an instance. + // Because the context top-level path is restricted, obtaining next id + // results in error. ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_ERR_BAD_REQUEST); } -static void test_multiple_instance(anjay_unlocked_input_ctx_t *in) { - anjay_uri_path_t path; - ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal( - &path, - &MAKE_RESOURCE_INSTANCE_PATH(TEST_RESOURCE_PATH.ids[ANJAY_ID_OID], - TEST_RESOURCE_PATH.ids[ANJAY_ID_IID], - TEST_RESOURCE_PATH.ids[ANJAY_ID_RID], - 4))); - - int64_t value; - ASSERT_OK(_anjay_get_i64_unlocked(in, &value)); - ASSERT_EQ(value, 42); - - ASSERT_OK(_anjay_input_next_entry(in)); - ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal( - &path, - &MAKE_RESOURCE_INSTANCE_PATH(TEST_RESOURCE_PATH.ids[ANJAY_ID_OID], - TEST_RESOURCE_PATH.ids[ANJAY_ID_IID], - TEST_RESOURCE_PATH.ids[ANJAY_ID_RID], - 5))); - - ASSERT_OK(_anjay_get_i64_unlocked(in, &value)); - ASSERT_EQ(value, 43); - - ASSERT_OK(_anjay_input_next_entry(in)); - ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_GET_PATH_END); -} - -static const anjay_uri_path_t TEST_INSTANCE_PATH = - INSTANCE_PATH_INITIALIZER(13, 26); +static void test_skipping(anjay_unlocked_input_ctx_t *in, + const anjay_uri_path_t *expected_paths, + size_t paths_count) { + ASSERT_EQ(paths_count, 2); -static void test_resource_skipping(anjay_unlocked_input_ctx_t *in) { anjay_uri_path_t path; ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal( - &path, - &MAKE_RESOURCE_PATH(TEST_INSTANCE_PATH.ids[ANJAY_ID_OID], - TEST_INSTANCE_PATH.ids[ANJAY_ID_IID], - 1))); + URI_EQUAL(&path, &expected_paths[0]); // we may not like this resource for some reason, let's skip its value ASSERT_OK(_anjay_input_next_entry(in)); - ASSERT_OK(_anjay_input_get_path(in, &path, NULL)); - ASSERT_TRUE(_anjay_uri_path_equal( - &path, - &MAKE_RESOURCE_PATH(TEST_INSTANCE_PATH.ids[ANJAY_ID_OID], - TEST_INSTANCE_PATH.ids[ANJAY_ID_IID], - 2))); - - int64_t value; - ASSERT_OK(_anjay_get_i64_unlocked(in, &value)); - ASSERT_EQ(value, 43); + check_path(in, &expected_paths[1], 43); ASSERT_OK(_anjay_input_next_entry(in)); ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_GET_PATH_END); diff --git a/tests/integration/framework/lwm2m/path.py b/tests/integration/framework/lwm2m/path.py index f452af04..1e767fe1 100644 --- a/tests/integration/framework/lwm2m/path.py +++ b/tests/integration/framework/lwm2m/path.py @@ -32,39 +32,64 @@ def to_uri_options(self, opt=coap.Option.URI_PATH): class Lwm2mPath(CoapPath): def __init__(self, text): + if not text.startswith('/'): + raise ValueError('not a valid LwM2M path: %s' % (text,)) + + self.numeric_segment_offset = 0 + if text.count('/') > 1: + prefix, path = text[1:].split('/', maxsplit=1) + + # Try to detect if the first segement contains a non-numerical value + if not prefix.isdigit() and prefix != "": + self.numeric_segment_offset = 1 + super().__init__(text) - if len(self.segments) > 4: - raise ValueError('LWM2M path must not have more than 4 segments') + if len(self.segments) > 4 + self.numeric_segment_offset: + raise ValueError( + 'LwM2M path must not have more than 4 numeric segments and 1 non-numeric prefix') - for segment in self.segments: + for segment in self.segments[self.numeric_segment_offset:]: try: int(segment) except ValueError as e: - raise ValueError('LWM2M path segment is not an integer: %s' % (segment,), e) + raise ValueError( + 'LWM2M path segment is not an integer: %s' % (segment,), e) + + # The first segment can hold either a prefix or a object id, this function + # helps extract the object, instance, resource and resource instance id from + # the segment list. It is not intended to extract the prefix from the + # segment list. + def __get_segment_wrapper(self, idx): + idx += self.numeric_segment_offset + return int(self.segments[idx]) if len(self.segments) > idx else None + + @property + def path_prefix(self): + return self.segments[0] if self.numeric_segment_offset > 0 else None @property def object_id(self): - return int(self.segments[0]) if len(self.segments) > 0 else None + return self.__get_segment_wrapper(0) @property def instance_id(self): - return int(self.segments[1]) if len(self.segments) > 1 else None + return self.__get_segment_wrapper(1) @property def resource_id(self): - return int(self.segments[2]) if len(self.segments) > 2 else None + return self.__get_segment_wrapper(2) @property def resource_instance_id(self): - return int(self.segments[3]) if len(self.segments) > 3 else None + return self.__get_segment_wrapper(3) class Lwm2mNonemptyPath(Lwm2mPath): def __init__(self, text): super().__init__(text) - if len(self.segments) == 0: + if len(self.segments) == 0 + self.numeric_segment_offset: raise ValueError('this LWM2M path requires at least Object ID') @@ -72,7 +97,7 @@ class Lwm2mObjectPath(Lwm2mNonemptyPath): def __init__(self, text): super().__init__(text) - if len(self.segments) != 1: + if len(self.segments) != 1 + self.numeric_segment_offset: raise ValueError('not a LWM2M Object path: %s' % (text,)) @@ -80,7 +105,7 @@ class Lwm2mInstancePath(Lwm2mNonemptyPath): def __init__(self, text): super().__init__(text) - if len(self.segments) != 2: + if len(self.segments) != 2 + self.numeric_segment_offset: raise ValueError('not a LWM2M Instance path: %s' % (text,)) @@ -88,5 +113,5 @@ class Lwm2mResourcePath(Lwm2mNonemptyPath): def __init__(self, text): super().__init__(text) - if len(self.segments) != 3: + if len(self.segments) != 3 + self.numeric_segment_offset: raise ValueError('not a LWM2M Resource path: %s' % (text,)) diff --git a/tests/integration/framework/nsh-lwm2m/pymbedtls/setup.py b/tests/integration/framework/nsh-lwm2m/pymbedtls/setup.py index 4acac45c..1dab110a 100644 --- a/tests/integration/framework/nsh-lwm2m/pymbedtls/setup.py +++ b/tests/integration/framework/nsh-lwm2m/pymbedtls/setup.py @@ -55,13 +55,16 @@ def library_exists(lib_name): library_dirs=library_dirs, libraries=['mbedtls', 'mbedcrypto', 'mbedx509'], include_dirs=include_dirs, - extra_compile_args=['-std=c++1y', '-isystem', '/usr/local/include']) + extra_compile_args=['-std=c++14', + '-isystem', + '/usr/local/include', + '-fvisibility=hidden']) ] setup( name='pymbedtls', - version='0.3.0', - description='''DTLS-PSK socket classes''', + version='0.4.0', + description='''MbedTLS wrapper for Python''', author='AVSystem', author_email='avsystem@avsystem.com', license='Commercial', diff --git a/tests/integration/framework/nsh-lwm2m/pymbedtls/src/common.hpp b/tests/integration/framework/nsh-lwm2m/pymbedtls/src/common.hpp index 21e0ec9b..6e06b89a 100644 --- a/tests/integration/framework/nsh-lwm2m/pymbedtls/src/common.hpp +++ b/tests/integration/framework/nsh-lwm2m/pymbedtls/src/common.hpp @@ -25,4 +25,35 @@ class mbedtls_error : public std::runtime_error { } // namespace ssl +namespace helpers { + +template +class defer_obj { +private: + Callable deferred; + +public: + defer_obj(defer_obj &&other) noexcept + : deferred(std::move(other.deferred)) {} + defer_obj(const defer_obj &) = delete; + defer_obj &operator=(const defer_obj &) = delete; + defer_obj &operator=(defer_obj &&) = delete; + + template + defer_obj(TempCallable &&deferred) + : deferred(std::forward(deferred)) {} + ~defer_obj() { + deferred(); + } +}; + +// This helper ensures that some code will be called on destruction, i.e. exit +// from the scope, no matter if it's a return, an exception or a normal exit. +template +defer_obj defer(Callable &&to_defer) { + return { std::forward(to_defer) }; +} + +}; // namespace helpers + #endif // PYMBEDTLS_COMMON_HPP diff --git a/tests/integration/framework/nsh-lwm2m/pymbedtls/src/context.cpp b/tests/integration/framework/nsh-lwm2m/pymbedtls/src/context.cpp index cfde262c..c7244bf5 100644 --- a/tests/integration/framework/nsh-lwm2m/pymbedtls/src/context.cpp +++ b/tests/integration/framework/nsh-lwm2m/pymbedtls/src/context.cpp @@ -10,6 +10,12 @@ #include #include +#include + +#if defined(MBEDTLS_USE_PSA_CRYPTO) || defined(MBEDTLS_PSA_CRYPTO_C) +# include +#endif // defined(MBEDTLS_USE_PSA_CRYPTO) || defined(MBEDTLS_PSA_CRYPTO_C) + #include "context.hpp" using namespace std; @@ -20,11 +26,11 @@ Context::Context(std::shared_ptr security, bool debug, std::string connection_id) : security_(security), debug_(debug), connection_id_(connection_id) { -#ifdef MBEDTLS_USE_PSA_CRYPTO +#if defined(MBEDTLS_USE_PSA_CRYPTO) || defined(MBEDTLS_PSA_CRYPTO_C) if (psa_crypto_init() != PSA_SUCCESS) { throw runtime_error("psa_crypto_init() failed"); } -#endif // MBEDTLS_USE_PSA_CRYPTO +#endif // defined(MBEDTLS_USE_PSA_CRYPTO) || defined(MBEDTLS_PSA_CRYPTO_C) memset(&session_cache_, 0, sizeof(session_cache_)); mbedtls_ssl_cache_init(&session_cache_); diff --git a/tests/integration/framework/nsh-lwm2m/pymbedtls/src/pymbedtls.cpp b/tests/integration/framework/nsh-lwm2m/pymbedtls/src/pymbedtls.cpp index 10b34de4..999d0f02 100644 --- a/tests/integration/framework/nsh-lwm2m/pymbedtls/src/pymbedtls.cpp +++ b/tests/integration/framework/nsh-lwm2m/pymbedtls/src/pymbedtls.cpp @@ -7,6 +7,8 @@ * See the attached LICENSE file for details. */ +#include +#include #include #include "context.hpp" @@ -88,7 +90,6 @@ PYBIND11_MODULE(pymbedtls, m) { .def("recv_into", &method_unimplemented) .def("recvfrom", &method_unimplemented) .def("recvfrom_into", &method_unimplemented) - .def("settimeout", &Socket::settimeout) .def("peer_cert", &Socket::peer_cert) .def("__getattr__", &Socket::__getattr__) .def("__setattr__", &Socket::__setattr__); @@ -99,4 +100,29 @@ PYBIND11_MODULE(pymbedtls, m) { .export_values(); // most verbose logs available mbedtls_debug_set_threshold(4); + + set_terminate([]() { + cerr << "Terminate called in pymbedtls. This almost certainly means " + "that an exception was thrown in a callback, that indirectly " + "was called by a callee not expecting an exception that would " + "require rethrowing. Consider analyzing the core dump in gdb " + "to determine the C++ stack trace leading to this point." + << endl; + exception_ptr eptr = current_exception(); + if (eptr) { + try { + rethrow_exception(eptr); + } catch (const exception &e) { + cerr << "Uncaught exception with reason: " << e.what() << endl; + } catch (...) { + cerr << "Uncaught unknown throwable" << endl; + } + } else { + cerr << "Couldn't determine the throwable that caused the " + "terminate call" + << endl; + } + cerr << flush; + std::abort(); + }); } diff --git a/tests/integration/framework/nsh-lwm2m/pymbedtls/src/security.cpp b/tests/integration/framework/nsh-lwm2m/pymbedtls/src/security.cpp index 88f984d5..b35f2986 100644 --- a/tests/integration/framework/nsh-lwm2m/pymbedtls/src/security.cpp +++ b/tests/integration/framework/nsh-lwm2m/pymbedtls/src/security.cpp @@ -47,7 +47,8 @@ CertSecurity::CertSecurity(const char *ca_path, const char *ca_file, const char *crt_file, const char *key_file) - : SecurityInfo({ MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 }), + : SecurityInfo({ MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, + ADDITIONAL_TLS1_3_CIPHERSUITES }), configure_ca_(ca_path || ca_file), configure_crt_(crt_file && key_file) { mbedtls_pk_init(&pk_ctx_); diff --git a/tests/integration/framework/nsh-lwm2m/pymbedtls/src/security.hpp b/tests/integration/framework/nsh-lwm2m/pymbedtls/src/security.hpp index df5f49df..bf0ae2db 100644 --- a/tests/integration/framework/nsh-lwm2m/pymbedtls/src/security.hpp +++ b/tests/integration/framework/nsh-lwm2m/pymbedtls/src/security.hpp @@ -15,6 +15,16 @@ #include #include +#ifdef MBEDTLS_SSL_PROTO_TLS1_3 +# define ADDITIONAL_TLS1_3_CIPHERSUITES \ + MBEDTLS_TLS1_3_AES_128_GCM_SHA256, MBEDTLS_TLS1_3_AES_256_GCM_SHA384, \ + MBEDTLS_TLS1_3_CHACHA20_POLY1305_SHA256, \ + MBEDTLS_TLS1_3_AES_128_CCM_SHA256, \ + MBEDTLS_TLS1_3_AES_128_CCM_8_SHA256 +#else // MBEDTLS_SSL_PROTO_TLS1_3 +# define ADDITIONAL_TLS1_3_CIPHERSUITES +#endif // MBEDTLS_SSL_PROTO_TLS1_3 + namespace ssl { class Socket; @@ -40,7 +50,8 @@ class PskSecurity : public SecurityInfo { public: PskSecurity(const std::string &key, const std::string &identity) - : SecurityInfo({ MBEDTLS_TLS_PSK_WITH_AES_128_CCM_8 }), + : SecurityInfo({ MBEDTLS_TLS_PSK_WITH_AES_128_CCM_8, + ADDITIONAL_TLS1_3_CIPHERSUITES }), key_(key), identity_(identity) {} diff --git a/tests/integration/framework/nsh-lwm2m/pymbedtls/src/socket.cpp b/tests/integration/framework/nsh-lwm2m/pymbedtls/src/socket.cpp index 987a562e..3cd7ae97 100644 --- a/tests/integration/framework/nsh-lwm2m/pymbedtls/src/socket.cpp +++ b/tests/integration/framework/nsh-lwm2m/pymbedtls/src/socket.cpp @@ -57,7 +57,9 @@ int get_socket_type(const py::object &py_socket) { namespace ssl { -int Socket::_send(void *self, const unsigned char *buf, size_t len) try { +int Socket::bio_send(void *self, + const unsigned char *buf, + size_t len) noexcept try { Socket *socket = reinterpret_cast(self); call_method( @@ -70,58 +72,54 @@ int Socket::_send(void *self, const unsigned char *buf, size_t len) try { } namespace { -std::tuple host_port_to_std_tuple(py::tuple host_port) { - return std::make_tuple(py::cast(host_port[0]), - py::cast(host_port[1])); +tuple host_port_to_std_tuple(py::tuple host_port) { + return make_tuple(py::cast(host_port[0]), + py::cast(host_port[1])); } } // namespace -int Socket::_recv(void *self, - unsigned char *buf, - size_t len, - uint32_t timeout_ms) { +bool py_timeout_finite(py::object timeout) { + return !timeout.is(py::none()); +} + +uint32_t to_millis_timeout(py::object timeout) { + if (!py_timeout_finite(timeout)) { + return UINT32_MAX; + } + return (uint32_t) (py::cast(timeout) * 1000.0); +} + +int Socket::bio_recv(void *self, + unsigned char *buf, + size_t len, + uint32_t mbedtls_timeout) noexcept { Socket *socket = reinterpret_cast(self); py::object py_buf = py::reinterpret_borrow( PyMemoryView_FromMemory((char *) buf, len, PyBUF_WRITE)); - class TimeoutRestorer { - Socket *socket_; - py::object orig_timeout_s_; - bool restored_; - - public: - TimeoutRestorer(Socket *socket) - : socket_(socket), - orig_timeout_s_(call_method(socket_->py_socket_, - "gettimeout")), - restored_(false) {} - - void restore() { - if (!restored_) { - call_method(socket_->py_socket_, "settimeout", - orig_timeout_s_); - restored_ = true; - } - } - - ~TimeoutRestorer() { - restore(); - } - } timeout_restorer{ socket }; - - if (timeout_ms == 0) { - timeout_ms = UINT32_MAX; - } else if (timeout_ms == UINT32_MAX) { - --timeout_ms; - } - + py::object py_timeout = + call_method(socket->py_socket_, "gettimeout"); + + // Since this method will possibly do multiple recv() calls, we'll be + // adjusting the underlying timeout in the runtime. When we're done, + // restore the original timeout. + const auto restore_timeout = helpers::defer([&] { + call_method(socket->py_socket_, "settimeout", py_timeout); + }); + + // If the timeout set by mbedTLS is 0 (infinite), assume the timeout we set + // to the underlying socket (as we do in avs_commons). Otherwise, timeout + // from mbedTLS gets precedence (this happens e.g. during handshake). + uint32_t timeout_ms = mbedtls_timeout > 0 ? mbedtls_timeout + : to_millis_timeout(py_timeout); + bool timeout_finite = mbedtls_timeout > 0 || py_timeout_finite(py_timeout); int socket_type = get_socket_type(socket->py_socket_); int bytes_received = 0; do { try { - if (timeout_ms != UINT32_MAX) { + if (timeout_finite) { call_method(socket->py_socket_, "settimeout", timeout_ms / 1000.0); } @@ -138,7 +136,7 @@ int Socket::_recv(void *self, "recv_into", py_buf); } - if (timeout_ms != UINT32_MAX) { + if (timeout_finite) { auto elapsed_ms = duration_cast( steady_clock::now() - before_recv) .count(); @@ -182,12 +180,13 @@ int Socket::_recv(void *self, bytes_received = process_python_socket_error(err, MBEDTLS_ERR_NET_RECV_FAILED); + + // when in handshake, Python exceptions are not rethrown if (!socket->in_handshake_) { - // HACK: it's there, explicitly called, because for some reason - // you can't call settimeout() when the "error is restored", and - // very weird things happen if you try to do it. - timeout_restorer.restore(); - err.restore(); + if (!socket->exception_capturer_) { + terminate(); + } + *socket->exception_capturer_ = current_exception(); } break; } @@ -197,18 +196,9 @@ int Socket::_recv(void *self, } Socket::HandshakeResult Socket::do_handshake() { - class HandshakeRaii { - Socket &self_; - - public: - HandshakeRaii(Socket &self) : self_(self) { - self_.in_handshake_ = true; - } - - ~HandshakeRaii() { - self_.in_handshake_ = false; - } - } handshake_raii_(*this); + in_handshake_ = true; + const auto clear_in_handshake = + helpers::defer([&] { in_handshake_ = false; }); for (;;) { int result = mbedtls_ssl_handshake(&mbedtls_context_); @@ -233,19 +223,20 @@ void debug_mbedtls(void * /*ctx*/, int /*level*/, const char *file, int line, - const char *str) { + const char *str) noexcept { fprintf(stderr, "%s:%04d: %s", file, line, str); } } // namespace -Socket::Socket(std::shared_ptr context, +Socket::Socket(shared_ptr context, py::object py_socket, SocketType type) : context_(context), type_(type), py_socket_(py_socket), in_handshake_(false), + exception_capturer_(nullptr), client_host_and_port_(), last_recv_host_and_port_() { mbedtls_ssl_init(&mbedtls_context_); @@ -279,7 +270,7 @@ Socket::Socket(std::shared_ptr context, mbedtls_ssl_conf_dbg(&config_, debug_mbedtls, NULL); } - // TODO + // Force (D)TLS 1.2 or higher mbedtls_ssl_conf_min_version(&config_, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3); mbedtls_ssl_conf_rng(&config_, mbedtls_ctr_drbg_random, &rng_); @@ -308,8 +299,8 @@ Socket::Socket(std::shared_ptr context, mbedtls_ssl_conf_session_cache(&config_, context_->session_cache(), mbedtls_ssl_cache_get, mbedtls_ssl_cache_set); - mbedtls_ssl_set_bio(&mbedtls_context_, this, &Socket::_send, NULL, - &Socket::_recv); + mbedtls_ssl_set_bio(&mbedtls_context_, this, &Socket::bio_send, NULL, + &Socket::bio_recv); mbedtls_ssl_set_timer_cb(&mbedtls_context_, &timer_, mbedtls_timing_set_delay, mbedtls_timing_get_delay); @@ -362,7 +353,7 @@ void Socket::perform_handshake(py::tuple host_port, if (result) { throw mbedtls_error("mbedtls_ssl_sssion_reset failed", result); } - string address = std::get<0>(client_host_and_port_); + string address = get<0>(client_host_and_port_); if (type_ == SocketType::Client) { result = mbedtls_ssl_set_hostname(&mbedtls_context_, address.c_str()); @@ -406,8 +397,13 @@ void Socket::send(const string &data) { py::bytes Socket::recv(int) { unsigned char buffer[65536]; - int result = 0; + + exception_ptr captured_exception; + exception_capturer_ = &captured_exception; + const auto clear_capturer = + helpers::defer([&] { exception_capturer_ = nullptr; }); + do { result = mbedtls_ssl_read(&mbedtls_context_, buffer, sizeof(buffer)); } while (result == MBEDTLS_ERR_SSL_WANT_READ @@ -416,7 +412,10 @@ py::bytes Socket::recv(int) { if (result < 0) { if (result == MBEDTLS_ERR_SSL_TIMEOUT || result == MBEDTLS_ERR_NET_RECV_FAILED) { - throw py::error_already_set(); + if (captured_exception) { + rethrow_exception(captured_exception); + } + throw runtime_error("Expected a Python exception to rethrow"); } else if (result == MBEDTLS_ERR_SSL_CLIENT_RECONNECT) { try { do_handshake(); @@ -427,6 +426,9 @@ py::bytes Socket::recv(int) { } throw mbedtls_error("mbedtls_ssl_read failed", result); } + if (captured_exception) { + throw runtime_error("Expected no Python exception to rethrow"); + } if (last_recv_host_and_port_ != client_host_and_port_) { // During Socket::_recv(), there had to be a message from a (host, port) @@ -439,17 +441,6 @@ py::bytes Socket::recv(int) { return py::bytes(reinterpret_cast(buffer), result); } -void Socket::settimeout(py::object timeout_s_or_none) { - uint32_t timeout_ms = 0; // no timeout - - if (!timeout_s_or_none.is(py::none())) { - timeout_ms = (uint32_t) (py::cast(timeout_s_or_none) * 1000.0); - } - - call_method(py_socket_, "settimeout", timeout_s_or_none); - mbedtls_ssl_conf_read_timeout(&config_, timeout_ms); -} - py::bytes Socket::peer_cert() { const mbedtls_x509_crt *cert = mbedtls_ssl_get_peer_cert(&mbedtls_context_); if (cert) { @@ -513,8 +504,7 @@ void enable_reuse(const py::object &socket) { } // namespace -ServerSocket::ServerSocket(std::shared_ptr context, - py::object py_socket) +ServerSocket::ServerSocket(shared_ptr context, py::object py_socket) : context_(context), py_socket_(py_socket) { enable_reuse(py_socket_); } @@ -556,10 +546,9 @@ unique_ptr ServerSocket::accept(py::object handshake_timeouts_s) { enable_reuse(client_py_sock); } - // Unfortunately C++11 is retarded and does not implement make_unique. unique_ptr client_sock = - unique_ptr(new Socket(context_, std::move(client_py_sock), - SocketType::Server)); + make_unique(context_, move(client_py_sock), + SocketType::Server); client_sock->perform_handshake(remote_addr, handshake_timeouts_s, false); return client_sock; } diff --git a/tests/integration/framework/nsh-lwm2m/pymbedtls/src/socket.hpp b/tests/integration/framework/nsh-lwm2m/pymbedtls/src/socket.hpp index 5d5a27cb..3e2d343d 100644 --- a/tests/integration/framework/nsh-lwm2m/pymbedtls/src/socket.hpp +++ b/tests/integration/framework/nsh-lwm2m/pymbedtls/src/socket.hpp @@ -24,6 +24,7 @@ #include #include +#include #include #include "pybind11_interop.hpp" @@ -53,6 +54,16 @@ class Socket { py::object py_socket_; bool in_handshake_; + // Used to capture exceptions that may be thrown in callbacks that are + // implemented in C++, but called from C code. As it's generally wrong to + // throw exception through the stack which incorporates C code, we capture + // the exception in case we expect some callback to generate it, and then + // rethrow it in a safe place. + // + // As we want to capture exceptions only when explicicitly requested, we use + // a pointer to std::exception_ptr for additional level of indirection. + std::exception_ptr *exception_capturer_; + // Used to match incoming packets with a client we initially are // connect()'ed to. It may change, if, for example connection_id extension // is used and we received a packet from a different endpoint but the @@ -64,9 +75,12 @@ class Socket { // (if any) to see if the packet is indeed valid and should be handled. std::tuple last_recv_host_and_port_; - static int _send(void *self, const unsigned char *buf, size_t len); static int - _recv(void *self, unsigned char *buf, size_t len, uint32_t timeout_ms); + bio_send(void *self, const unsigned char *buf, size_t len) noexcept; + static int bio_recv(void *self, + unsigned char *buf, + size_t len, + uint32_t timeout_ms) noexcept; HandshakeResult do_handshake(); @@ -82,8 +96,12 @@ class Socket { bool py_connect); void send(const std::string &data); py::bytes recv(int); - void settimeout(py::object timeout_s_or_none); py::bytes peer_cert(); + + // __getattr__ (and __setattr__) is called when Python is unable to directly + // find the attribute of an object. By redirecting this to __get_attribute__ + // of the py_socket_ field, we're essentially extending the class of + // py_socket_. py::object __getattr__(py::object name); void __setattr__(py::object name, py::object value); diff --git a/tests/integration/framework/test_suite.py b/tests/integration/framework/test_suite.py index 687c9c78..f7480824 100644 --- a/tests/integration/framework/test_suite.py +++ b/tests/integration/framework/test_suite.py @@ -389,11 +389,22 @@ def make_demo_args(self, afu_original_img_file_path=None, sw_mgmt_persistence_file=None, tls_version='TLSv1.2', - ciphersuites=(0xC030, 0xC0A8, 0xC0AE)): + ciphersuites=(0x1305, 0x1301, 0xC030, 0xC0A8, 0xC0AE)): """ Helper method for easy generation of demo executable arguments. """ - # 0xC030 = TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - used by TLS (over TCP, including HTTPS) in tests + # LwM2M 1.2 doesn't specify any TLS 1.3 ciphersuites, but + # draft-ietf-uta-tls13-iot-profile-09, section 17 suggests: + # 0x1305 = TLS_AES_128_CCM_8_SHA256 - although compatible with CoAP, + # prone to other issues - see the referenced document + + # Additionally, to support ssl Python library (used in tests that use + # ssl.SSLContext API): + # 0x1301 = TLS_AES_128_GCM_SHA256 - supported by default if TLS 1.3 is + # available + # 0xC030 = TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - compatible with + # shortened list of default TLS 1.2 ciphersuites (Python 3.10) + # Default ciphersuites mandated by LwM2M: # 0xC0A8 = TLS_PSK_WITH_AES_128_CCM_8 # 0xC0AE = TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 @@ -443,8 +454,12 @@ def logs_path(self, log_type, log_root=None, **kwargs): ensure_dir(os.path.dirname(log_path)) return log_path - def read_log_until_match(self, regex, timeout_s): + def read_log_until_match(self, regex, timeout_s, alt_offset=None): orig_offset = self.demo_process.log_file.tell() + + if alt_offset is not None: + self.demo_process.log_file.seek(alt_offset, io.SEEK_SET) + deadline = time.time() + timeout_s out = bytearray() while True: @@ -466,14 +481,20 @@ def read_log_until_match(self, regex, timeout_s): if match: # Move the file pointer to just after the match, in case we've read more move_offset = match.end() - len(out) + if move_offset != 0: assert move_offset < 0 self.demo_process.log_file.seek(move_offset, io.SEEK_CUR) + if alt_offset is not None: + new_alt_offset = self.demo_process.log_file.tell() + self.demo_process.log_file.seek(orig_offset, io.SEEK_SET) + return new_alt_offset, match + return match elif partial_timeout <= 0.0: self.demo_process.log_file.seek(orig_offset, io.SEEK_SET) - return None + return (alt_offset, None) if alt_offset is not None else None def _get_valgrind_args(self): import shlex @@ -823,8 +844,14 @@ def communicate(self, cmd, timeout=-1, match_regex=re.escape('(DEMO)>')): timeout = self.DEFAULT_COMM_TIMEOUT self.seek_demo_log_to_end() - self.demo_process.stdin.write((cmd.strip('\n') + '\n').encode()) - self.demo_process.stdin.flush() + # For some reason, writing to a closed pipe seems to behave a little + # differently for macOS and Linux. On macOS, Python receives a SIGPIPE + # and therefore throws a BrokenPipeError. Let's silence it. + try: + self.demo_process.stdin.write((cmd.strip('\n') + '\n').encode()) + self.demo_process.stdin.flush() + except BrokenPipeError: + pass if match_regex: result = self.read_log_until_match(match_regex.encode(), timeout_s=timeout) @@ -991,27 +1018,6 @@ def serv(self): del self.servers[0] -class Lwm2mSingleTcpServerTest(Lwm2mTest, SingleServerAccessor): - def runTest(self): - pass - - def setUp(self, extra_cmdline_args=None, *args, **kwargs): - extra_args = ['-q', 'T'] - coap_server = coap.Server(transport=Transport.TCP) - if extra_cmdline_args is not None: - extra_args += extra_cmdline_args - - if 'servers' not in kwargs: - kwargs['servers'] = [Lwm2mServer(coap_server)] - - self.setup_demo_with_servers(extra_cmdline_args=extra_args, - *args, - **kwargs) - - def tearDown(self, *args, **kwargs): - self.teardown_demo_with_servers(*args, **kwargs) - - class Lwm2mSingleServerTest(Lwm2mTest, SingleServerAccessor): def runTest(self): pass diff --git a/tests/integration/framework/test_utils.py b/tests/integration/framework/test_utils.py index 1de92058..6d188f80 100644 --- a/tests/integration/framework/test_utils.py +++ b/tests/integration/framework/test_utils.py @@ -92,6 +92,7 @@ class OID: Portfolio = 16 BinaryAppDataContainer = 19 EventLog = 20 + Lwm2mGateway = 25 Temperature = 3303 Accelerometer = 3313 PushButton = 3347 @@ -289,6 +290,14 @@ class ApnConnectionProfile: TotalPacketsSent = 23 PDNType = 24 APNRateControl = 25 + ServingPLMNRateControl = 26 + UplinkTimeUnit = 27 + APNRateControlForExceptionData = 28 + APNExceptionDataUplinkTimeUnit = 29 + SupportedRATTypes = 30 + RDSApplicationID = 31 + RDSDestinationPort = 32 + RDSSourcePort = 33 class GeoPoints: Latitude = 0 @@ -429,7 +438,7 @@ class EventLog: LogDataFormat = 4015 -class _Lwm2mResourcePathHelper: +class Lwm2mResourcePathHelper: @classmethod def from_rid_object(cls, rid_obj, oid, multi_instance=False, version=None): return cls(resources={k: v for k, v in rid_obj.__dict__.items() if isinstance(v, int)}, @@ -463,59 +472,59 @@ def __getattr__(self, name): class ResPath: - Security = _Lwm2mResourcePathHelper.from_rid_object( + Security = Lwm2mResourcePathHelper.from_rid_object( RID.Security, oid=OID.Security, multi_instance=True) - Server = _Lwm2mResourcePathHelper.from_rid_object( + Server = Lwm2mResourcePathHelper.from_rid_object( RID.Server, oid=OID.Server, multi_instance=True) - AccessControl = _Lwm2mResourcePathHelper.from_rid_object(RID.AccessControl, + AccessControl = Lwm2mResourcePathHelper.from_rid_object(RID.AccessControl, oid=OID.AccessControl, multi_instance=True) - Device = _Lwm2mResourcePathHelper.from_rid_object( + Device = Lwm2mResourcePathHelper.from_rid_object( RID.Device, oid=OID.Device) - ConnectivityMonitoring = _Lwm2mResourcePathHelper.from_rid_object(RID.ConnectivityMonitoring, + ConnectivityMonitoring = Lwm2mResourcePathHelper.from_rid_object(RID.ConnectivityMonitoring, oid=OID.ConnectivityMonitoring) - FirmwareUpdate = _Lwm2mResourcePathHelper.from_rid_object( + FirmwareUpdate = Lwm2mResourcePathHelper.from_rid_object( RID.FirmwareUpdate, oid=OID.FirmwareUpdate) - Location = _Lwm2mResourcePathHelper.from_rid_object( + Location = Lwm2mResourcePathHelper.from_rid_object( RID.Location, oid=OID.Location) - ConnectivityStatistics = _Lwm2mResourcePathHelper.from_rid_object(RID.ConnectivityStatistics, + ConnectivityStatistics = Lwm2mResourcePathHelper.from_rid_object(RID.ConnectivityStatistics, oid=OID.ConnectivityStatistics) - SoftwareManagement = _Lwm2mResourcePathHelper.from_rid_object(RID.SoftwareManagement, oid=OID.SoftwareManagement, multi_instance=True) - CellularConnectivity = _Lwm2mResourcePathHelper.from_rid_object(RID.CellularConnectivity, + SoftwareManagement = Lwm2mResourcePathHelper.from_rid_object(RID.SoftwareManagement, oid=OID.SoftwareManagement, multi_instance=True) + CellularConnectivity = Lwm2mResourcePathHelper.from_rid_object(RID.CellularConnectivity, oid=OID.CellularConnectivity, version='1.1') - ApnConnectionProfile = _Lwm2mResourcePathHelper.from_rid_object(RID.ApnConnectionProfile, + ApnConnectionProfile = Lwm2mResourcePathHelper.from_rid_object(RID.ApnConnectionProfile, oid=OID.ApnConnectionProfile, multi_instance=True) - Temperature = _Lwm2mResourcePathHelper.from_rid_object( + Temperature = Lwm2mResourcePathHelper.from_rid_object( RID.Temperature, oid=OID.Temperature) - Accelerometer = _Lwm2mResourcePathHelper.from_rid_object( + Accelerometer = Lwm2mResourcePathHelper.from_rid_object( RID.Accelerometer, oid=OID.Accelerometer) - PushButton = _Lwm2mResourcePathHelper.from_rid_object( + PushButton = Lwm2mResourcePathHelper.from_rid_object( RID.PushButton, oid=OID.PushButton) - Test = _Lwm2mResourcePathHelper.from_rid_object( + Test = Lwm2mResourcePathHelper.from_rid_object( RID.Test, oid=OID.Test, multi_instance=True) - Portfolio = _Lwm2mResourcePathHelper.from_rid_object( + Portfolio = Lwm2mResourcePathHelper.from_rid_object( RID.Portfolio, oid=OID.Portfolio, multi_instance=True) - ExtDevInfo = _Lwm2mResourcePathHelper.from_rid_object( + ExtDevInfo = Lwm2mResourcePathHelper.from_rid_object( RID.ExtDevInfo, oid=OID.ExtDevInfo) - IpPing = _Lwm2mResourcePathHelper.from_rid_object( + IpPing = Lwm2mResourcePathHelper.from_rid_object( RID.IpPing, oid=OID.IpPing) - GeoPoints = _Lwm2mResourcePathHelper.from_rid_object( + GeoPoints = Lwm2mResourcePathHelper.from_rid_object( RID.GeoPoints, oid=OID.GeoPoints, multi_instance=True) - DownloadDiagnostics = _Lwm2mResourcePathHelper.from_rid_object(RID.DownloadDiagnostics, + DownloadDiagnostics = Lwm2mResourcePathHelper.from_rid_object(RID.DownloadDiagnostics, oid=OID.DownloadDiagnostics) - AdvancedFirmwareUpdate = _Lwm2mResourcePathHelper.from_rid_object( + AdvancedFirmwareUpdate = Lwm2mResourcePathHelper.from_rid_object( RID.AdvancedFirmwareUpdate, oid=OID.AdvancedFirmwareUpdate, multi_instance=True) - BinaryAppDataContainer = _Lwm2mResourcePathHelper.from_rid_object( + BinaryAppDataContainer = Lwm2mResourcePathHelper.from_rid_object( RID.BinaryAppDataContainer, oid=OID.BinaryAppDataContainer, multi_instance=True) - EventLog = _Lwm2mResourcePathHelper.from_rid_object(RID.EventLog, oid=OID.EventLog) + EventLog = Lwm2mResourcePathHelper.from_rid_object(RID.EventLog, oid=OID.EventLog) @classmethod def objects(cls): results = [] for _, field in cls.__dict__.items(): - if isinstance(field, _Lwm2mResourcePathHelper): + if isinstance(field, Lwm2mResourcePathHelper): results.append(field) return sorted(results, key=lambda field: field.oid) diff --git a/tests/integration/suites/default/advanced_firmware_update.py b/tests/integration/suites/default/advanced_firmware_update.py index 41c8c84a..00f32493 100644 --- a/tests/integration/suites/default/advanced_firmware_update.py +++ b/tests/integration/suites/default/advanced_firmware_update.py @@ -4173,12 +4173,12 @@ def runTest(self): # This case finally tests if two strings occur one after another to ensure that # download schedule for TEE is done just after the APP finish downloading if self.read_log_until_match(regex=re.escape(b'instance /33629/0 downloaded successfully'), - timeout_s=5) is None: + timeout_s=15) is None: raise self.failureException( 'string not found') if self.read_log_until_match(regex=re.escape(b'download scheduled: ' + fw_uri.encode()), - timeout_s=1) is None: + timeout_s=3) is None: raise self.failureException( 'string not found') diff --git a/tests/integration/suites/default/conn_status_api.py b/tests/integration/suites/default/conn_status_api.py new file mode 100644 index 00000000..2b3e6c26 --- /dev/null +++ b/tests/integration/suites/default/conn_status_api.py @@ -0,0 +1,654 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2017-2024 AVSystem +# AVSystem Anjay LwM2M SDK +# All rights reserved. +# +# Licensed under the AVSystem-5-clause License. +# See the attached LICENSE file for details. + +from framework.lwm2m_test import * +from .access_control import AccessMask +from .bootstrap_client import BootstrapTest +from .register import RegisterUdp +from .retransmissions import RetransmissionTest, RegisterTimeout, DeregisterIcmp +from .queue_mode import QueueMode + +import time +import re +import enum + + +class ConnStatusAPI: + class Status(enum.Enum): + INVALID = enum.auto() + ERROR = enum.auto() + INITIAL = enum.auto() + CONNECTING = enum.auto() + BOOTSTRAPPING = enum.auto() + BOOTSTRAPPED = enum.auto() + REGISTERING = enum.auto() + REGISTERED = enum.auto() + REG_FAILURE = enum.auto() + DEREGISTERING = enum.auto() + DEREGISTERED = enum.auto() + SUSPENDING = enum.auto() + SUSPENDED = enum.auto() + REREGISTERING = enum.auto() + UPDATING = enum.auto() + + class TestMixin: + STATUS_CHANGE_REGEX = re.compile( + rb'Current status of the server with SSID (\d+) is: (.+)') + + # we need this to ensure that other commands which search through + # logs will not accidentally consume statements we're interested in + log_alt_offset = 0 + + def assertStatusChanges(self, statuses, default_ssid=1, timeout_s=0): + deadline = time.time() + timeout_s + + assert isinstance(statuses, list) + + for status in statuses: + if isinstance(status, ConnStatusAPI.Status): + expected_ssid = default_ssid + expected_status = status + else: + assert isinstance(status, tuple) + assert [type(field) for field in status] == [ + int, ConnStatusAPI.Status] + expected_ssid, expected_status = status + + self.log_alt_offset, match = self.read_log_until_match( + self.STATUS_CHANGE_REGEX, + timeout_s=max(0, deadline - time.time()), + alt_offset=self.log_alt_offset) + self.assertIsNotNone(match) + + actual_ssid = int(match.group(1)) + actual_status = ConnStatusAPI.Status[match.group(2).decode()] + + self.assertEqual(actual_ssid, expected_ssid) + self.assertEqual(actual_status, expected_status) + + def assertNoOutstandingStatusChanges(self): + _, match = self.read_log_until_match( + self.STATUS_CHANGE_REGEX, timeout_s=0, alt_offset=self.log_alt_offset) + self.assertIsNone(match) + + class AutoRegDeregTestMixin(TestMixin): + def assertDemoRegisters( + self, server=None, initial=True, first_attempt=True, reregister=False, respond=True, reject=False, *args, **kwargs): + pkt = super().assertDemoRegisters(server=server, + respond=respond, reject=reject, *args, **kwargs) + expected_statuses = [] + + if initial: + expected_statuses.append(ConnStatusAPI.Status.INITIAL) + + if first_attempt: + expected_statuses.append(ConnStatusAPI.Status.CONNECTING) + expected_statuses.append(ConnStatusAPI.Status.REGISTERING) + + if reregister: + expected_statuses.append(ConnStatusAPI.Status.REREGISTERING) + + if respond: + if reject: + expected_statuses.append(ConnStatusAPI.Status.REG_FAILURE) + else: + expected_statuses.append(ConnStatusAPI.Status.REGISTERED) + + self.assertStatusChanges(expected_statuses, timeout_s=2) + + return pkt + + def assertDemoUpdatesRegistration(self, first_attempt=True, respond=True, *args, **kwargs): + pkt = super().assertDemoUpdatesRegistration(respond=respond, *args, **kwargs) + expected_statuses = [] + + if first_attempt: + expected_statuses.append(ConnStatusAPI.Status.UPDATING) + + if respond: + expected_statuses.append(ConnStatusAPI.Status.REGISTERED) + + self.assertStatusChanges(expected_statuses, timeout_s=2) + + return pkt + + def assertDemoDeregisters(self, server=None, *args, **kwargs): + super().assertDemoDeregisters(server=server, *args, **kwargs) + self.assertStatusChanges([ + ConnStatusAPI.Status.DEREGISTERING, + ConnStatusAPI.Status.DEREGISTERED], timeout_s=2) + + def tearDown(self, *args, **kwargs): + self.assertNoOutstandingStatusChanges() + super().tearDown(*args, **kwargs) + + +class DefaultRegDeregUDP(ConnStatusAPI.AutoRegDeregTestMixin, + test_suite.Lwm2mSingleServerTest): + pass + + +class DefaultRegDeregDTLS(ConnStatusAPI.AutoRegDeregTestMixin, + test_suite.Lwm2mDtlsSingleServerTest): + pass + + +class DefaultRegDeregTCP(ConnStatusAPI.AutoRegDeregTestMixin, + test_suite.Lwm2mSingleTcpServerTest): + def setUp(self, *args, **kwargs): + super().setUp(binding='T', *args, **kwargs) + + +class DefaultRegDeregTLS(ConnStatusAPI.AutoRegDeregTestMixin, + test_suite.Lwm2mTlsSingleServerTest): + def setUp(self, *args, **kwargs): + super().setUp(binding='T', *args, **kwargs) + + +class ServerStaysInRegisteringStateDuringRetries( + ConnStatusAPI.AutoRegDeregTestMixin, RegisterUdp.TestCase): + def runTest(self): + self.assertDemoRegisters(respond=False) + self.assertNoOutstandingStatusChanges() + self.assertDemoRegisters( + initial=False, first_attempt=False, respond=False, timeout_s=6) + self.assertNoOutstandingStatusChanges() + self.assertDemoRegisters( + initial=False, first_attempt=False, timeout_s=12) + + +class RejectedRegisterChangesStateToRegFailure(ConnStatusAPI.AutoRegDeregTestMixin, + test_suite.Lwm2mSingleServerTest): + def setUp(self, *args, **kwargs): + super().setUp(auto_register=False, *args, **kwargs) + + def runTest(self): + self.assertDemoRegisters(reject=True) + + def tearDown(self): + super().tearDown(auto_deregister=False) + + +class ConnectFailureChangesStateToError(ConnStatusAPI.AutoRegDeregTestMixin, test_suite.Lwm2mSingleTcpServerTest): + def setUp(self, *args, **kwargs): + super().setUp(binding='T', auto_register=False, *args, **kwargs) + + def runTest(self): + self.assertStatusChanges( + [ConnStatusAPI.Status.INITIAL, ConnStatusAPI.Status.CONNECTING], timeout_s=2) + self.serv.close() + self.assertStatusChanges([ConnStatusAPI.Status.ERROR], timeout_s=2) + + def tearDown(self): + super().tearDown(auto_deregister=False) + + +class UpdatesChangeStateToUpdating(ConnStatusAPI.AutoRegDeregTestMixin, + test_suite.Lwm2mSingleServerTest): + def runTest(self): + self.assertNoOutstandingStatusChanges() + + self.communicate('send-update') + self.assertDemoUpdatesRegistration() + LIFETIME = 2 + + self.serv.send(Lwm2mWrite(ResPath.Server[1].Lifetime, str(LIFETIME))) + self.serv.recv() + + self.assertDemoUpdatesRegistration(lifetime=LIFETIME) + + self.assertNoOutstandingStatusChanges() + + # wait for auto-scheduled Update + self.assertDemoUpdatesRegistration(timeout_s=LIFETIME) + + +class UpdateFailureChangesStateToReregistering(ConnStatusAPI.AutoRegDeregTestMixin, test_suite.Lwm2mSingleServerTest): + LIFETIME = 4 + + def setUp(self): + super().setUp(auto_register=False, lifetime=self.LIFETIME, + extra_cmdline_args=['--ack-random-factor', '1', '--ack-timeout', '1', + '--max-retransmit', '1']) + self.assertDemoRegisters(lifetime=self.LIFETIME) + + def runTest(self): + self.assertDemoUpdatesRegistration(timeout_s=self.LIFETIME / 2 + 1) + + self.assertDemoUpdatesRegistration( + timeout_s=self.LIFETIME / 2 + 1, respond=False) + self.assertDemoUpdatesRegistration( + timeout_s=self.LIFETIME / 2 + 1, first_attempt=False, respond=False) + self.assertDemoRegisters(lifetime=self.LIFETIME, timeout_s=self.LIFETIME / + 2 + 1, initial=False, first_attempt=False, reregister=True) + + +class DtlsReregisterFailureDoesntReportRegFailure(ConnStatusAPI.AutoRegDeregTestMixin, + RetransmissionTest.TestMixin, + test_suite.Lwm2mDtlsSingleServerTest): + MAX_RETRANSMIT = 1 + ACK_TIMEOUT = 1 + + def tearDown(self): + super().teardown_demo_with_servers(auto_deregister=False) + + def runTest(self): + # force re-registration; + # Register only falls back to handshake if it's not performed immediately after one + self.communicate('send-update') + pkt = self.assertDemoUpdatesRegistration(respond=False) + self.serv.send(Lwm2mErrorResponse.matching(pkt) + (code=coap.Code.RES_NOT_FOUND)) + + # Ignore register requests. + for i in range(self.MAX_RETRANSMIT + 1): + self.assertDemoRegisters( + respond=False, + timeout_s=self.last_retransmission_timeout(), + initial=False, + first_attempt=False, + reregister=i == 0) + + self.assertNoOutstandingStatusChanges() + self.wait_for_retransmission_response_timeout() + + # Demo should fall back to DTLS handshake. + self.assertPktIsDtlsClientHello( + self.serv._raw_udp_socket.recv(4096), seq_number=0) + self.assertStatusChanges([ConnStatusAPI.Status.CONNECTING]) + + self.wait_for_retransmission_response_timeout() + # Ensure that the control is given back to the user. + self.assertTrue(self.get_all_connections_failed()) + self.assertStatusChanges([ConnStatusAPI.Status.ERROR]) + self.assertNoOutstandingStatusChanges() + + +class DeregisterFailure: + class TestMixin(ConnStatusAPI.AutoRegDeregTestMixin, DeregisterIcmp.TestMixin): + def tearDown(self): + self.assertStatusChanges( + [ConnStatusAPI.Status.DEREGISTERING, + ConnStatusAPI.Status.ERROR], timeout_s=2) + super().tearDown() + + +class DeregisterFailureChangesStateToError(DeregisterFailure.TestMixin, + test_suite.Lwm2mSingleServerTest): + pass + + +class DtlsDeregisterFailureChangesStateToError(DeregisterFailure.TestMixin, + test_suite.Lwm2mDtlsSingleServerTest): + pass + + +class DisablingServerRemotely( + ConnStatusAPI.AutoRegDeregTestMixin, test_suite.Lwm2mSingleServerTest): + def assertSocketsPolled(self, num): + self.assertEqual(num, self.get_socket_count()) + + def runTest(self): + self.assertNoOutstandingStatusChanges() + + # Write Disable Timeout + req = Lwm2mWrite(ResPath.Server[1].DisableTimeout, '6') + self.serv.send(req) + self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv()) + + self.assertNoOutstandingStatusChanges() + + # Execute Disable + req = Lwm2mExecute(ResPath.Server[1].Disable) + self.serv.send(req) + self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv()) + self.assertStatusChanges( + [ConnStatusAPI.Status.SUSPENDING], timeout_s=2) + + self.assertDemoDeregisters(timeout_s=5) + self.assertStatusChanges([ConnStatusAPI.Status.SUSPENDED], timeout_s=2) + + # no message for now + with self.assertRaises(socket.timeout): + print(self.serv.recv(timeout_s=5)) + + self.assertSocketsPolled(0) + self.assertFalse(self.ongoing_registration_exists()) + + # we should get another Register + self.assertDemoRegisters(initial=False, timeout_s=3) + + # no message for now + with self.assertRaises(socket.timeout): + print(self.serv.recv(timeout_s=2)) + + self.assertSocketsPolled(1) + + +class DisablingSecondTimeDoesntChangeState(ConnStatusAPI.TestMixin, + test_suite.Lwm2mTest, + test_suite.Lwm2mDmOperations): + def setUp(self, **kwargs): + super().setUp(servers=2, extra_cmdline_args=['--access-entry', + '/%d/1,2,%d' % (OID.Server, AccessMask.OWNER)]) + + def runTest(self): + expected_states = [] + + for ssid in range(1, 3): + expected_states.append((ssid, ConnStatusAPI.Status.INITIAL)) + for ssid in range(1, 3): + expected_states.append((ssid, ConnStatusAPI.Status.CONNECTING)) + expected_states.append((ssid, ConnStatusAPI.Status.REGISTERING)) + for ssid in range(1, 3): + expected_states.append((ssid, ConnStatusAPI.Status.REGISTERED)) + + self.assertStatusChanges(expected_states, timeout_s=2) + self.assertNoOutstandingStatusChanges() + + self.write_resource( + self.servers[1], OID.Server, 1, RID.Server.DisableTimeout, b'6') + self.execute_resource( + self.servers[1], OID.Server, 1, RID.Server.Disable) + first_disable_timestamp = time.time() + self.assertDemoDeregisters(self.servers[0]) + + self.assertStatusChanges([ + ConnStatusAPI.Status.SUSPENDING, + ConnStatusAPI.Status.DEREGISTERING, + ConnStatusAPI.Status.DEREGISTERED, + ConnStatusAPI.Status.SUSPENDED], timeout_s=2) + self.assertNoOutstandingStatusChanges() + + # no message for now + with self.assertRaises(socket.timeout): + print(self.servers[0].recv(timeout_s=3)) + + # execute Disable again, this should reset the timer + self.execute_resource( + self.servers[1], OID.Server, 1, RID.Server.Disable) + with self.assertRaises(socket.timeout): + print(self.servers[0].recv(timeout_s=5)) + + self.assertNoOutstandingStatusChanges() + + self.assertFalse(self.ongoing_registration_exists()) + + # only now the server should re-register + self.assertDemoRegisters(server=self.servers[0], timeout_s=3) + register_timestamp = time.time() + + self.assertGreater(register_timestamp - first_disable_timestamp, 8) + + self.assertStatusChanges([ + ConnStatusAPI.Status.CONNECTING, + ConnStatusAPI.Status.REGISTERING, + ConnStatusAPI.Status.REGISTERED], timeout_s=2) + self.assertNoOutstandingStatusChanges() + + +class ShutdownDuringQueueModeChangesStateDirectlyToDeregistered(ConnStatusAPI.AutoRegDeregTestMixin, + QueueMode.Test, + test_suite.Lwm2mSingleServerTest): + def setUp(self): + if QueueMode.autoclose_disabled(self): + self.skipTest( + 'If socket autoclose is disabled, the client deregisters as usual') + super().setUp() + + def runTest(self): + self.assertNoOutstandingStatusChanges() + + self.communicate('set-queue-mode-preference FORCE_QUEUE_MODE') + + self.communicate('send-update') + self.assertDemoUpdatesRegistration() + + # # await for the client to close the socket + time.sleep(self.max_transmit_wait() - 2) + self.assertEqual(self.get_socket_count(), 1) + time.sleep(4) + self.assertEqual(self.get_socket_count(), 0) + + def tearDown(self): + super().tearDown(auto_deregister=False) + + def _terminate_demo(self, *args, **kwargs): + # since the state change is induced during shutdown of the client, this + # is the only way to catch this state + self.assertStatusChanges( + [ConnStatusAPI.Status.DEREGISTERED], timeout_s=2) + super()._terminate_demo(*args, **kwargs) + + + + +class IcmpErrorDuringRegisterCausesError(ConnStatusAPI.AutoRegDeregTestMixin, + test_suite.PcapEnabledTest, + RetransmissionTest.TestMixin, + test_suite.Lwm2mSingleServerTest): + MAX_RETRANSMIT = 1 + ACK_TIMEOUT = 4 + + def setUp(self): + super().setUp(auto_register=False) + + def tearDown(self): + super().teardown_demo_with_servers(auto_deregister=False) + + def runTest(self): + self.assertDemoRegisters() + + # Close socket to induce ICMP port unreachable. + with self.serv.fake_close(): + # Force Register + self.communicate('reconnect') + self.assertStatusChanges([ + ConnStatusAPI.Status.CONNECTING, + ConnStatusAPI.Status.REGISTERING], timeout_s=2) + # Wait for ICMP port unreachable. + self.wait_until_icmp_unreachable_count( + 1, timeout_s=self.last_retransmission_timeout()) + + # Ensure that the control is given back to the user. + with self.assertRaises(socket.timeout, msg="unexpected packets from the client"): + self.serv.recv() + + self.assertStatusChanges([ConnStatusAPI.Status.ERROR], timeout_s=2) + + self.assertEqual(0, self.get_socket_count()) + self.assertTrue(self.get_all_connections_failed()) + + self.assertNoOutstandingStatusChanges() + + +class RegisterTimeoutRegFailure: + class TestMixin(ConnStatusAPI.AutoRegDeregTestMixin, RegisterTimeout.TestMixin): + MAX_RETRANSMIT = 3 + + def runTest(self): + # Required for DTLS variant, in which this completes a handshake + # which is a part of connect() call. + self.serv.listen(timeout_s=10) + + # Ignore register requests. + for i in range(self.MAX_RETRANSMIT + 1): + self.assertDemoRegisters(respond=False, + timeout_s=self.last_retransmission_timeout() + 5, + initial=i == 0, + first_attempt=i == 0) + + self.assertNoOutstandingStatusChanges() + self.wait_for_retransmission_response_timeout() + + self.assertStatusChanges( + [ConnStatusAPI.Status.REG_FAILURE], timeout_s=2) + + # Ensure that server is considered unreachable, and control given back to the user. + self.assertEqual(0, self.get_socket_count()) + self.assertTrue(self.get_all_connections_failed()) + + self.assertNoOutstandingStatusChanges() + + +class UdpRegisterTimeoutCausesRegFailure(RegisterTimeoutRegFailure.TestMixin, + test_suite.Lwm2mSingleServerTest): + pass + + +class DtlsRegisterTimeoutCausesRegFailure(RegisterTimeoutRegFailure.TestMixin, + test_suite.Lwm2mDtlsSingleServerTest): + pass + + +class ReportsErrorIfRegistrationFailsDueToNetworkIssues(ConnStatusAPI.AutoRegDeregTestMixin, + test_suite.PcapEnabledTest, + test_suite.Lwm2mDtlsSingleServerTest): + RETRY_COUNT = 3 + MAX_RETRANSMIT = 1 + + def setUp(self): + extra_cmdline_args = ['--retry-count', str(self.RETRY_COUNT), '--max-retransmit', + str(self.MAX_RETRANSMIT), '--ack-timeout', str(1)] + super().setUp(extra_cmdline_args=extra_cmdline_args, auto_register=False) + + def tearDown(self): + super().tearDown(auto_deregister=False) + + def runTest(self): + for i in range(self.MAX_RETRANSMIT + 1): + self.assertDemoRegisters(respond=False, timeout_s=5, initial=i == 0, first_attempt=i == 0) + + num_initial_dtls_hs_packets = self.count_dtls_client_hello_packets() + # Close socket to induce ICMP port unreachable. + self.serv.close() + + # Wait for ICMP port unreachable. + self.wait_until_icmp_unreachable_count(1, timeout_s=5) + + self.assertStatusChanges([ + ConnStatusAPI.Status.REG_FAILURE, + ConnStatusAPI.Status.CONNECTING, + ConnStatusAPI.Status.ERROR], timeout_s=2) + + time.sleep(5) + # Ensure that no more retransmissions occurred. + self.assertEqual(1, self.count_icmp_unreachable_packets()) + # Ensure that only one more dtls handshake messages occurred. + self.assertEqual(num_initial_dtls_hs_packets + 1, + self.count_dtls_client_hello_packets()) + + # Ensure that the control is given back to the user. + self.assertTrue(self.get_all_connections_failed()) + self.assertNoOutstandingStatusChanges() + + +class ReportsRegFailureIfConnectionErrorIsRegFailure(ConnStatusAPI.AutoRegDeregTestMixin, + test_suite.PcapEnabledTest, + test_suite.Lwm2mDtlsSingleServerTest): + RETRY_COUNT = 3 + MAX_RETRANSMIT = 1 + + def setUp(self): + extra_cmdline_args = ['--retry-count', str(self.RETRY_COUNT), '--max-retransmit', + str(self.MAX_RETRANSMIT), '--ack-timeout', str(1), + '--connection-error-is-registration-failure'] + super().setUp(extra_cmdline_args=extra_cmdline_args, auto_register=False) + + def tearDown(self): + super().tearDown(auto_deregister=False) + + def runTest(self): + for i in range(self.MAX_RETRANSMIT + 1): + self.assertDemoRegisters(respond=False, timeout_s=5, initial=i == 0, first_attempt=i == 0) + + num_initial_dtls_hs_packets = self.count_dtls_client_hello_packets() + # Close socket to induce ICMP port unreachable. + self.serv.close() + + # Wait for ICMP port unreachable. + self.wait_until_icmp_unreachable_count(1, timeout_s=5) + + self.assertStatusChanges([ + ConnStatusAPI.Status.REG_FAILURE, + ConnStatusAPI.Status.CONNECTING], timeout_s=2) + + # And another one - the third registration attempt + self.wait_until_icmp_unreachable_count(1, timeout_s=5) + + self.assertStatusChanges([ + ConnStatusAPI.Status.REG_FAILURE, + ConnStatusAPI.Status.CONNECTING], timeout_s=2) + + time.sleep(5) + # Ensure that no more retransmissions occurred. + self.assertEqual(2, self.count_icmp_unreachable_packets()) + # Ensure that only one more dtls handshake messages occurred. + self.assertEqual(num_initial_dtls_hs_packets + 2, + self.count_dtls_client_hello_packets()) + + self.assertStatusChanges([ConnStatusAPI.Status.REG_FAILURE]) + + # Ensure that the control is given back to the user. + self.assertTrue(self.get_all_connections_failed()) + self.assertNoOutstandingStatusChanges() + + +class Bootstrap: + class TestMixin(ConnStatusAPI.AutoRegDeregTestMixin): + def assertDemoRequestsBootstrap(self, *args, **kwargs): + super().assertDemoRequestsBootstrap(*args, **kwargs) + self.assertStatusChanges([ + ConnStatusAPI.Status.INITIAL, + ConnStatusAPI.Status.CONNECTING, + ConnStatusAPI.Status.BOOTSTRAPPING], default_ssid=65535, timeout_s=2) + + def perform_bootstrap_finish(self, *args, **kwargs): + super().perform_bootstrap_finish(*args, **kwargs) + self.assertStatusChanges( + [ConnStatusAPI.Status.BOOTSTRAPPED], default_ssid=65535, timeout_s=2) + + +class DefaultBootstrapTest(Bootstrap.TestMixin, BootstrapTest.Test): + def setUp(self): + super().setUp(holdoff_s=3, timeout_s=3) + + def runTest(self): + self.perform_typical_bootstrap(server_iid=1, + security_iid=2, + server_uri='coap://127.0.0.1:%d' % self.serv.get_listen_port(), + lifetime=60) + + # should now register with the non-bootstrap server + self.assertDemoRegisters(self.serv, lifetime=60) + + +class BootstrapNoResponseTest(Bootstrap.TestMixin, BootstrapTest.Test): + ACK_TIMEOUT = 1 + MAX_RETRANSMIT = 1 + + def setUp(self): + # Done to have a relatively short EXCHANGE_LIFETIME + super().setUp(extra_cmdline_args=['--ack-random-factor', '1', + '--ack-timeout', '%s' % self.ACK_TIMEOUT, + '--max-retransmit', '%s' % self.MAX_RETRANSMIT]) + + def tearDown(self): + super().tearDown(auto_deregister=False) + + def runTest(self): + # We should get Bootstrap Request now + self.assertDemoRequestsBootstrap() + + self.assertEqual(1, self.get_socket_count()) + self.advance_demo_time(TxParams(ack_timeout=self.ACK_TIMEOUT, + max_retransmit=self.MAX_RETRANSMIT).exchange_lifetime()) + self.wait_until_socket_count(0, timeout_s=5) + self.assertStatusChanges( + [ConnStatusAPI.Status.ERROR], default_ssid=65535) diff --git a/tests/integration/suites/default/notifications.py b/tests/integration/suites/default/notifications.py index ae29da2d..58bc3279 100644 --- a/tests/integration/suites/default/notifications.py +++ b/tests/integration/suites/default/notifications.py @@ -7,6 +7,7 @@ # Licensed under the AVSystem-5-clause License. # See the attached LICENSE file for details. from framework.lwm2m_test import * +from framework.lwm2m.coap.transport import Transport class CancellingConfirmableNotifications(test_suite.Lwm2mSingleServerTest, @@ -62,6 +63,93 @@ def runTest(self): self.serv.send(Lwm2mReset.matching(notify2)()) +class ClientInitiatedNotificationCancellation: + class Test(test_suite.Lwm2mDmOperations): + def make_cancellation_response(self, notify): + return None + + def assertConIfUdp(self, msg): + if self.serv.transport == Transport.UDP: + self.assertEqual(msg.type, coap.Type.CONFIRMABLE) + + def assertNonIfUdp(self, msg): + if self.serv.transport == Transport.UDP: + self.assertEqual(msg.type, coap.Type.NON_CONFIRMABLE) + + def setUp(self, *args, **kwargs): + super().setUp(*args, **kwargs) + + def runTest(self): + observe = self.observe(self.serv, oid=OID.Location, iid=0, rid=RID.Location.Latitude) + + notify = self.serv.recv(timeout_s=2) + self.assertIsInstance(notify, Lwm2mNotify) + self.assertNonIfUdp(notify) + self.assertEqual(bytes(notify.token), bytes(observe.token)) + self.assertEqual(len(notify.get_options(coap.Option.OBSERVE)), 1) + + # Unregister the Location object, this should make a read on the + # resource to fail, which should make the client cancel the + # observation on its initiative + self.communicate('unregister-object %d' % OID.Location) + self.assertDemoUpdatesRegistration(content=ANY) + + notify = self.serv.recv(timeout_s=2) + self.assertEqual(notify.code, coap.Code.RES_NOT_FOUND) + self.assertConIfUdp(notify) + self.assertEqual(bytes(notify.token), bytes(observe.token)) + self.assertEqual(len(notify.get_options(coap.Option.OBSERVE)), 0) + + res = self.make_cancellation_response(notify) + if res is not None: + self.serv.send(res) + + # We should not receive any more notifications + with self.assertRaises(socket.timeout): + self.serv.recv(timeout_s=5) + + class TestWithAck(Test): + def make_cancellation_response(self, notify): + return Lwm2mEmpty.matching(notify)() + + # Some servers answer such a notification with a RST, not an ACK. + class TestWithRst(Test): + def make_cancellation_response(self, notify): + return Lwm2mReset.matching(notify)() + + +class ClientInitiatedNotificationCancellationUdpAck(ClientInitiatedNotificationCancellation.TestWithAck, + test_suite.Lwm2mSingleServerTest): + pass + + +class ClientInitiatedNotificationCancellationDtlsAck(ClientInitiatedNotificationCancellation.TestWithAck, + test_suite.Lwm2mDtlsSingleServerTest): + pass + + +class ClientInitiatedNotificationCancellationUdpRst(ClientInitiatedNotificationCancellation.TestWithRst, + test_suite.Lwm2mSingleServerTest): + pass + + +class ClientInitiatedNotificationCancellationDtlsRst(ClientInitiatedNotificationCancellation.TestWithRst, + test_suite.Lwm2mDtlsSingleServerTest): + pass + + +class ClientInitiatedNotificationCancellationTcp(ClientInitiatedNotificationCancellation.Test, + test_suite.Lwm2mSingleTcpServerTest): + def setUp(self, *args, **kwargs): + super().setUp(binding='T', *args, **kwargs) + + +class ClientInitiatedNotificationCancellationTls(ClientInitiatedNotificationCancellation.Test, + test_suite.Lwm2mTlsSingleServerTest): + def setUp(self, *args, **kwargs): + super().setUp(binding='T', *args, **kwargs) + + class SelfNotifyDisabled(test_suite.Lwm2mSingleServerTest, test_suite.Lwm2mDmOperations): def runTest(self): self.create_instance(self.serv, oid=OID.Test, iid=42) diff --git a/tests/integration/suites/default/register.py b/tests/integration/suites/default/register.py index 5697cb60..389487da 100644 --- a/tests/integration/suites/default/register.py +++ b/tests/integration/suites/default/register.py @@ -298,8 +298,47 @@ def setUp(self): client_crt_file=None, client_key_file=None, server_crt_file='server.crt.der') +class RegisterMixin: + extra_objects = [] + def expected_content(self, version='1.0'): + result = [] + for obj in sorted(ResPath.objects() + self.extra_objects, key=lambda field: field.oid): + if obj.oid == OID.Security: + # Security (/0) instances MUST not be a part of the list + # see LwM2M spec, Register/Update operations description + continue + + if obj.oid == OID.Server: + result.append('' % (obj.oid,)) + elif obj.oid == OID.SoftwareManagement: + result.append('' % (obj.oid,)) + result.append('' % (obj.oid,)) + elif obj.oid == OID.Lwm2mGateway: + entry = '' % (obj.oid,) + if obj.version is not None: + if version == '1.0': + entry += ';ver="%s"' % (obj.version,) + else: + entry += ';ver=%s' % (obj.version,) + result.append(entry) + result.append('' % (obj.oid,)) + result.append('' % (obj.oid,)) + elif obj.is_multi_instance or obj.version is not None: + entry = '' % (obj.oid,) + if obj.version is not None: + if version == '1.0': + entry += ';ver="%s"' % (obj.version,) + else: + entry += ';ver=%s' % (obj.version,) + result.append(entry) + if not obj.is_multi_instance: + result.append('' % (obj.oid,)) + + return ','.join(result).encode() + + class BlockRegister: - class Test(unittest.TestCase): + class Test(RegisterMixin, unittest.TestCase): def __call__(self, server, timeout_s=None, verify=True, version='1.0'): register_content = b'' while True: @@ -316,16 +355,19 @@ def __call__(self, server, timeout_s=None, verify=True, version='1.0'): server.send(Lwm2mContinue.matching(pkt)(options=block1)) if verify: - self.assertEqual(expected_content(version), register_content) + self.assertEqual(self.expected_content(version), register_content) server.send(Lwm2mCreated.matching(pkt)(location='/rd/demo', options=block1)) + class Register: - class TestCase(test_suite.Lwm2mDmOperations): - def setUp(self): + class TestCase(RegisterMixin, test_suite.Lwm2mDmOperations): + def setUp(self, *args, **kwargs): + self.extra_objects = [] # skip initial registration - super().setUp(auto_register=False) + super().setUp(auto_register=False, *args, **kwargs) + class RegisterUdp: @@ -367,39 +409,13 @@ def tearDown(self): super().tearDown(auto_deregister=False) -def expected_content(version='1.0'): - result = [] - for obj in ResPath.objects(): - if obj.oid == OID.Security: - # Security (/0) instances MUST not be a part of the list - # see LwM2M spec, Register/Update operations description - continue - - if obj.oid == OID.Server: - result.append('' % (obj.oid,)) - elif obj.oid == OID.SoftwareManagement: - result.append('' % (obj.oid,)) - result.append('' % (obj.oid,)) - elif obj.is_multi_instance or obj.version is not None: - entry = '' % (obj.oid,) - if obj.version is not None: - if version == '1.0': - entry += ';ver="%s"' % (obj.version,) - else: - entry += ';ver=%s' % (obj.version,) - result.append(entry) - if not obj.is_multi_instance: - result.append('' % (obj.oid,)) - return ','.join(result).encode() - - class RegisterTest(RegisterUdp.TestCase): def runTest(self): # should send Register request at start pkt = self.serv.recv() self.assertMsgEqual( Lwm2mRegister('/rd?lwm2m=1.0&ep=%s<=86400' % (DEMO_ENDPOINT_NAME,), - content=expected_content()), + content=self.expected_content()), pkt) # should retry when no response is sent @@ -407,7 +423,7 @@ def runTest(self): self.assertMsgEqual( Lwm2mRegister('/rd?lwm2m=1.0&ep=%s<=86400' % (DEMO_ENDPOINT_NAME,), - content=expected_content()), + content=self.expected_content()), pkt) # should ignore this message as Message ID does not match @@ -420,7 +436,7 @@ def runTest(self): self.assertMsgEqual( Lwm2mRegister('/rd?lwm2m=1.0&ep=%s<=86400' % (DEMO_ENDPOINT_NAME,), - content=expected_content()), + content=self.expected_content()), pkt) # should not retry after receiving valid response @@ -428,7 +444,7 @@ def runTest(self): with self.assertRaises(socket.timeout, msg='unexpected message'): print(self.serv.recv(timeout_s=6)) - + class RegisterCheckOngoingRegistrations(RegisterUdp.TestCase): def runTest(self): @@ -437,7 +453,7 @@ def runTest(self): pkt = self.serv.recv() self.assertMsgEqual( Lwm2mRegister('/rd?lwm2m=1.0&ep=%s<=86400' % (DEMO_ENDPOINT_NAME,), - content=expected_content()), pkt) + content=self.expected_content()), pkt) self.assertTrue(self.ongoing_registration_exists()) @@ -452,7 +468,7 @@ def runTest(self): pkt = self.serv.recv() self.assertMsgEqual( Lwm2mRegister('/rd?lwm2m=1.0&ep=%s<=86400' % (DEMO_ENDPOINT_NAME,), - content=expected_content()), + content=self.expected_content()), pkt) # Separate Response: Confirmable; msg_id does not match, but token does @@ -489,7 +505,7 @@ def runTest(self): path += '&b=T' self.assertTcpCsm() pkt = self.serv.recv() - self.assertMsgEqual(Lwm2mRegister(path, content=expected_content()), pkt) + self.assertMsgEqual(Lwm2mRegister(path, content=self.expected_content()), pkt) self.read_path(self.serv, ResPath.Device.Manufacturer) self.serv.send(Lwm2mCreated.matching(pkt)(location='/rd/demo')) @@ -519,7 +535,7 @@ def runTest(self): path += '&b=T' self.assertTcpCsm() pkt = self.serv.recv() - self.assertMsgEqual(Lwm2mRegister(path, content=expected_content()), pkt) + self.assertMsgEqual(Lwm2mRegister(path, content=self.expected_content()), pkt) self.serv.send(Lwm2mCreated.matching(pkt)(location='/some/weird/rd/point')) # Update shall not contain the path and query from Server URI diff --git a/tests/modules/security/persistence.c b/tests/modules/security/persistence.c index e60c8da5..76bd07c1 100644 --- a/tests/modules/security/persistence.c +++ b/tests/modules/security/persistence.c @@ -46,11 +46,11 @@ security_persistence_test_env_create(void) { env->stream = avs_stream_membuf_create(); AVS_UNIT_ASSERT_NOT_NULL(env->stream); ANJAY_MUTEX_LOCK(anjay_unlocked, env->anjay_stored); - env->stored = *_anjay_dm_find_object_by_oid(anjay_unlocked, + env->stored = *_anjay_dm_find_object_by_oid(_anjay_get_dm(anjay_unlocked), ANJAY_DM_OID_SECURITY); ANJAY_MUTEX_UNLOCK(env->anjay_stored); ANJAY_MUTEX_LOCK(anjay_unlocked, env->anjay_restored); - env->restored = *_anjay_dm_find_object_by_oid(anjay_unlocked, + env->restored = *_anjay_dm_find_object_by_oid(_anjay_get_dm(anjay_unlocked), ANJAY_DM_OID_SECURITY); ANJAY_MUTEX_UNLOCK(env->anjay_restored); env->stored_repr = _anjay_sec_get(env->stored); diff --git a/tests/modules/server/persistence.c b/tests/modules/server/persistence.c index ee4cf5c3..140004b5 100644 --- a/tests/modules/server/persistence.c +++ b/tests/modules/server/persistence.c @@ -45,12 +45,12 @@ static server_persistence_test_env_t *server_persistence_test_env_create(void) { env->stream = avs_stream_membuf_create(); AVS_UNIT_ASSERT_NOT_NULL(env->stream); ANJAY_MUTEX_LOCK(anjay_unlocked, env->anjay_stored); - env->stored = - *_anjay_dm_find_object_by_oid(anjay_unlocked, ANJAY_DM_OID_SERVER); + env->stored = *_anjay_dm_find_object_by_oid(_anjay_get_dm(anjay_unlocked), + ANJAY_DM_OID_SERVER); ANJAY_MUTEX_UNLOCK(env->anjay_stored); ANJAY_MUTEX_LOCK(anjay_unlocked, env->anjay_restored); - env->restored = - *_anjay_dm_find_object_by_oid(anjay_unlocked, ANJAY_DM_OID_SERVER); + env->restored = *_anjay_dm_find_object_by_oid(_anjay_get_dm(anjay_unlocked), + ANJAY_DM_OID_SERVER); ANJAY_MUTEX_UNLOCK(env->anjay_restored); env->stored_repr = _anjay_serv_get(env->stored); env->restored_repr = _anjay_serv_get(env->restored); diff --git a/tools/coverage b/tools/coverage index e0ce1e9f..1b6a015c 100755 --- a/tools/coverage +++ b/tools/coverage @@ -10,28 +10,38 @@ set -e . "$(dirname "$0")/utils.sh" -[[ "$PROJECT_ROOT" ]] || PROJECT_ROOT="$(dirname "$(dirname "$(canonicalize "$0")")")" function die() { echo -e "$@" >&2 exit 1 } -which gcovr || die "gcovr not found, exiting" +which gcc || die "gcc not found, exiting" +which lcov || die "lcov not found, exiting" +which genhtml || die "genhtml not found, exiting" + +GCC_VERSION=$(gcc --version 2>&1 | head -n 1 | awk 'END {print $NF}') +GCC_MAJOR_VERSION=${GCC_VERSION%%.*} +LCOV_VERSION=$(lcov --version 2>&1 | head -n 1 | awk 'END {print $NF}') +LCOV_MAJOR_VERSION=${LCOV_VERSION%%.*} + +if [ "$LCOV_MAJOR_VERSION" -gt 1 ]; then + LCOV_ADDITIONAL_OPTS="--rc branch_coverage=1 --ignore-errors mismatch" +else + LCOV_ADDITIONAL_OPTS="--rc lcov_branch_coverage=1" +fi +[[ "$PROJECT_ROOT" ]] || PROJECT_ROOT="$(dirname "$(dirname "$(canonicalize "$0")")")" + +rm -rf "$PROJECT_ROOT/build/coverage" mkdir -p "$PROJECT_ROOT/build/coverage" pushd "$PROJECT_ROOT/build/coverage" - mkdir -p "$PROJECT_ROOT/build/coverage" - "$PROJECT_ROOT/devconfig" -D CMAKE_C_FLAGS="-std=c99 -D_POSIX_C_SOURCE=200809L -g --coverage" -D CMAKE_EXE_LINKER_FLAGS="--coverage" "$@" - make -j$(num_processors) - make check - + "$PROJECT_ROOT/devconfig" --without-analysis --without-memcheck --no-examples -DCMAKE_C_FLAGS="-std=c99 -D_POSIX_C_SOURCE=200809L -g -O0 --coverage" -DCMAKE_EXE_LINKER_FLAGS="--coverage" "$@" + make anjay_check -j$(num_processors) mkdir -p "$PROJECT_ROOT/coverage" - gcovr . --html --html-details -r "$PROJECT_ROOT" \ - -f "$PROJECT_ROOT/src" -f "$PROJECT_ROOT/include_public" \ - -f "$PROJECT_ROOT/deps/avs_coap/src" -f "$PROJECT_ROOT/deps/avs_coap/include_public" \ - -f "$PROJECT_ROOT/deps/avs_commons/src" -f "$PROJECT_ROOT/deps/avs_commons/include_public" \ - -o "$PROJECT_ROOT/coverage/coverage.html" + lcov $LCOV_ADDITIONAL_OPTS -c -d . -o coverage.info --gcov-tool /usr/bin/gcov-$GCC_MAJOR_VERSION + lcov $LCOV_ADDITIONAL_OPTS --remove coverage.info "$PROJECT_ROOT/tests/*" "$PROJECT_ROOT/deps/*" "/usr/*" "$PROJECT_ROOT/include_public/*" -o coverage.info + genhtml coverage.info --branch-coverage --function-coverage --output-directory "$PROJECT_ROOT/coverage" popd cat <