From 83000a94230f3048ab8cbf1a4b5ae984f3a621b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleksander=20W=C3=B3jtowicz?= Date: Wed, 31 Jan 2024 14:40:10 +0100 Subject: [PATCH] Anjay 4.0.0-alpha.1 Initial release of Anjay 4 Alpha. --- .clang-format | 22 + .gitignore | 25 + .gitmodules | 3 + CMakeLists.txt | 170 ++ LICENSE | 47 + README.md | 80 + deps/avs_commons | 1 + examples/anjay_basic/anjay_net_impl.c | 200 ++ examples/anjay_basic/main.c | 167 ++ .../anjay_net_impl.c | 198 ++ .../firmware_update.c | 158 ++ .../firmware_update.h | 25 + .../anjay_basic_with_firmware_update/main.c | 175 ++ .../anjay_basic_with_send/anjay_net_impl.c | 200 ++ examples/anjay_basic_with_send/main.c | 189 ++ include_public/anj/anj_time.h | 21 + include_public/anj/dm.h | 674 +++++ include_public/anj/dm_io.h | 492 ++++ include_public/anj/sdm.h | 295 ++ include_public/anj/sdm_device_object.h | 84 + include_public/anj/sdm_fw_update.h | 298 +++ include_public/anj/sdm_impl.h | 116 + include_public/anj/sdm_io.h | 677 +++++ include_public/anj/sdm_notification.h | 57 + include_public/anj/sdm_send.h | 57 + include_public/anjay_lite/anjay_lite.h | 68 + include_public/anjay_lite/anjay_lite_config.h | 47 + include_public/anjay_lite/anjay_net.h | 95 + include_public/fluf/fluf.h | 357 +++ include_public/fluf/fluf_cbor_decoder_ll.h | 550 ++++ include_public/fluf/fluf_cbor_encoder_ll.h | 69 + include_public/fluf/fluf_config.h | 25 + include_public/fluf/fluf_defs.h | 448 ++++ include_public/fluf/fluf_io.h | 1033 +++++++ include_public/fluf/fluf_io_ctx.h | 169 ++ include_public/fluf/fluf_utils.h | 216 ++ linux_config/anj/anj_config.h | 25 + .../avsystem/commons/avs_commons_config.h | 839 ++++++ src/anj/compat/posix/anj_time.c | 28 + src/anj/dm/dm_execute.c | 53 + src/anj/dm/dm_handlers.c | 123 + src/anj/dm/dm_read.c | 235 ++ src/anj/dm/dm_write.c | 220 ++ src/anj/dm_core.c | 957 +++++++ src/anj/dm_core.h | 33 + src/anj/dm_io_core.c | 417 +++ src/anj/dm_utils/dm_utils.h | 226 ++ src/anj/dm_utils/dm_utils_core.h | 57 + src/anj/sdm/sdm_core.c | 418 +++ src/anj/sdm/sdm_core.h | 104 + src/anj/sdm/sdm_create.c | 112 + src/anj/sdm/sdm_delete.c | 102 + src/anj/sdm/sdm_device_object.c | 235 ++ src/anj/sdm/sdm_discover.c | 381 +++ src/anj/sdm/sdm_execute.c | 63 + src/anj/sdm/sdm_fw_update.c | 518 ++++ src/anj/sdm/sdm_impl.c | 554 ++++ src/anj/sdm/sdm_notification.c | 400 +++ src/anj/sdm/sdm_read.c | 429 +++ src/anj/sdm/sdm_register.c | 100 + src/anj/sdm/sdm_send.c | 150 ++ src/anj/sdm/sdm_write.c | 309 +++ src/anjay_lite/anjay_lite.c | 55 + src/anjay_lite/anjay_lite_objs.h | 31 + src/anjay_lite/anjay_lite_register.c | 170 ++ src/anjay_lite/anjay_lite_register.h | 26 + src/anjay_lite/anjay_lite_security_obj.c | 133 + src/anjay_lite/anjay_lite_server_obj.c | 181 ++ src/anjay_lite/anjay_lite_servers.c | 465 ++++ src/anjay_lite/anjay_lite_servers.h | 59 + src/fluf/fluf_attributes.c | 175 ++ src/fluf/fluf_attributes.h | 34 + src/fluf/fluf_block.c | 146 + src/fluf/fluf_block.h | 29 + src/fluf/fluf_cbor_decoder_ll.c | 1506 +++++++++++ src/fluf/fluf_cbor_encoder.c | 145 + src/fluf/fluf_cbor_encoder.h | 40 + src/fluf/fluf_cbor_encoder_ll.c | 138 + src/fluf/fluf_coap_udp_header.h | 173 ++ src/fluf/fluf_coap_udp_msg.c | 260 ++ src/fluf/fluf_coap_udp_msg.h | 55 + src/fluf/fluf_decode.c | 378 +++ src/fluf/fluf_discover.c | 343 +++ src/fluf/fluf_internal.h | 114 + src/fluf/fluf_io.c | 428 +++ src/fluf/fluf_options.c | 460 ++++ src/fluf/fluf_options.h | 144 + src/fluf/fluf_prepare.c | 214 ++ src/fluf/fluf_senml_cbor_encoder.c | 256 ++ src/fluf/fluf_tlv_decoder.c | 580 ++++ src/fluf/fluf_tlv_decoder.h | 28 + src/fluf/fluf_utils.c | 290 ++ tests/anj/dm/dm_core.c | 144 + tests/anj/dm/dm_core_register_discover.c | 605 +++++ tests/anj/dm/dm_execute.c | 164 ++ tests/anj/dm/dm_get_readable_res_count.c | 174 ++ tests/anj/dm/dm_read.c | 632 +++++ tests/anj/dm/dm_read_root.c | 141 + tests/anj/dm/dm_write.c | 583 ++++ .../dm/static_functions_tests/core_statics.c | 67 + tests/anj/sdm/sdm.c | 255 ++ tests/anj/sdm/sdm_bootstrap_discover.c | 307 +++ tests/anj/sdm/sdm_create.c | 281 ++ tests/anj/sdm/sdm_delete.c | 619 +++++ tests/anj/sdm/sdm_discover.c | 236 ++ tests/anj/sdm/sdm_execute.c | 166 ++ tests/anj/sdm/sdm_read.c | 500 ++++ tests/anj/sdm/sdm_register.c | 165 ++ tests/anj/sdm/sdm_write_replace.c | 744 ++++++ tests/anj/sdm/sdm_write_update.c | 663 +++++ tests/fluf/bigdata.h | 36 + tests/fluf/bootstrap_discover_payload.c | 340 +++ tests/fluf/cbor_decoder_ll.c | 2374 +++++++++++++++++ tests/fluf/cbor_encoder.c | 480 ++++ tests/fluf/coap_udp_msg.c | 339 +++ tests/fluf/discover_payload.c | 392 +++ tests/fluf/lwm2m_decode.c | 679 +++++ tests/fluf/lwm2m_prepare.c | 423 +++ tests/fluf/options.c | 327 +++ tests/fluf/register_payload.c | 373 +++ tests/fluf/senml_cbor_encoder.c | 600 +++++ tests/fluf/tlv_in.c | 883 ++++++ tests/fluf/utils.c | 55 + tools/coverage | 52 + 124 files changed, 35451 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 README.md create mode 160000 deps/avs_commons create mode 100644 examples/anjay_basic/anjay_net_impl.c create mode 100644 examples/anjay_basic/main.c create mode 100644 examples/anjay_basic_with_firmware_update/anjay_net_impl.c create mode 100644 examples/anjay_basic_with_firmware_update/firmware_update.c create mode 100644 examples/anjay_basic_with_firmware_update/firmware_update.h create mode 100644 examples/anjay_basic_with_firmware_update/main.c create mode 100644 examples/anjay_basic_with_send/anjay_net_impl.c create mode 100644 examples/anjay_basic_with_send/main.c create mode 100644 include_public/anj/anj_time.h create mode 100644 include_public/anj/dm.h create mode 100644 include_public/anj/dm_io.h create mode 100644 include_public/anj/sdm.h create mode 100644 include_public/anj/sdm_device_object.h create mode 100644 include_public/anj/sdm_fw_update.h create mode 100644 include_public/anj/sdm_impl.h create mode 100644 include_public/anj/sdm_io.h create mode 100644 include_public/anj/sdm_notification.h create mode 100644 include_public/anj/sdm_send.h create mode 100644 include_public/anjay_lite/anjay_lite.h create mode 100644 include_public/anjay_lite/anjay_lite_config.h create mode 100644 include_public/anjay_lite/anjay_net.h create mode 100644 include_public/fluf/fluf.h create mode 100644 include_public/fluf/fluf_cbor_decoder_ll.h create mode 100644 include_public/fluf/fluf_cbor_encoder_ll.h create mode 100644 include_public/fluf/fluf_config.h create mode 100644 include_public/fluf/fluf_defs.h create mode 100644 include_public/fluf/fluf_io.h create mode 100644 include_public/fluf/fluf_io_ctx.h create mode 100644 include_public/fluf/fluf_utils.h create mode 100644 linux_config/anj/anj_config.h create mode 100644 linux_config/avsystem/commons/avs_commons_config.h create mode 100644 src/anj/compat/posix/anj_time.c create mode 100644 src/anj/dm/dm_execute.c create mode 100644 src/anj/dm/dm_handlers.c create mode 100644 src/anj/dm/dm_read.c create mode 100644 src/anj/dm/dm_write.c create mode 100644 src/anj/dm_core.c create mode 100644 src/anj/dm_core.h create mode 100644 src/anj/dm_io_core.c create mode 100644 src/anj/dm_utils/dm_utils.h create mode 100644 src/anj/dm_utils/dm_utils_core.h create mode 100644 src/anj/sdm/sdm_core.c create mode 100644 src/anj/sdm/sdm_core.h create mode 100644 src/anj/sdm/sdm_create.c create mode 100644 src/anj/sdm/sdm_delete.c create mode 100644 src/anj/sdm/sdm_device_object.c create mode 100644 src/anj/sdm/sdm_discover.c create mode 100644 src/anj/sdm/sdm_execute.c create mode 100644 src/anj/sdm/sdm_fw_update.c create mode 100644 src/anj/sdm/sdm_impl.c create mode 100644 src/anj/sdm/sdm_notification.c create mode 100644 src/anj/sdm/sdm_read.c create mode 100644 src/anj/sdm/sdm_register.c create mode 100644 src/anj/sdm/sdm_send.c create mode 100644 src/anj/sdm/sdm_write.c create mode 100644 src/anjay_lite/anjay_lite.c create mode 100644 src/anjay_lite/anjay_lite_objs.h create mode 100644 src/anjay_lite/anjay_lite_register.c create mode 100644 src/anjay_lite/anjay_lite_register.h create mode 100644 src/anjay_lite/anjay_lite_security_obj.c create mode 100644 src/anjay_lite/anjay_lite_server_obj.c create mode 100644 src/anjay_lite/anjay_lite_servers.c create mode 100644 src/anjay_lite/anjay_lite_servers.h create mode 100644 src/fluf/fluf_attributes.c create mode 100644 src/fluf/fluf_attributes.h create mode 100644 src/fluf/fluf_block.c create mode 100644 src/fluf/fluf_block.h create mode 100644 src/fluf/fluf_cbor_decoder_ll.c create mode 100644 src/fluf/fluf_cbor_encoder.c create mode 100644 src/fluf/fluf_cbor_encoder.h create mode 100644 src/fluf/fluf_cbor_encoder_ll.c create mode 100644 src/fluf/fluf_coap_udp_header.h create mode 100644 src/fluf/fluf_coap_udp_msg.c create mode 100644 src/fluf/fluf_coap_udp_msg.h create mode 100644 src/fluf/fluf_decode.c create mode 100644 src/fluf/fluf_discover.c create mode 100644 src/fluf/fluf_internal.h create mode 100644 src/fluf/fluf_io.c create mode 100644 src/fluf/fluf_options.c create mode 100644 src/fluf/fluf_options.h create mode 100644 src/fluf/fluf_prepare.c create mode 100644 src/fluf/fluf_senml_cbor_encoder.c create mode 100644 src/fluf/fluf_tlv_decoder.c create mode 100644 src/fluf/fluf_tlv_decoder.h create mode 100644 src/fluf/fluf_utils.c create mode 100644 tests/anj/dm/dm_core.c create mode 100644 tests/anj/dm/dm_core_register_discover.c create mode 100644 tests/anj/dm/dm_execute.c create mode 100644 tests/anj/dm/dm_get_readable_res_count.c create mode 100644 tests/anj/dm/dm_read.c create mode 100644 tests/anj/dm/dm_read_root.c create mode 100644 tests/anj/dm/dm_write.c create mode 100644 tests/anj/dm/static_functions_tests/core_statics.c create mode 100644 tests/anj/sdm/sdm.c create mode 100644 tests/anj/sdm/sdm_bootstrap_discover.c create mode 100644 tests/anj/sdm/sdm_create.c create mode 100644 tests/anj/sdm/sdm_delete.c create mode 100644 tests/anj/sdm/sdm_discover.c create mode 100644 tests/anj/sdm/sdm_execute.c create mode 100644 tests/anj/sdm/sdm_read.c create mode 100644 tests/anj/sdm/sdm_register.c create mode 100644 tests/anj/sdm/sdm_write_replace.c create mode 100644 tests/anj/sdm/sdm_write_update.c create mode 100644 tests/fluf/bigdata.h create mode 100644 tests/fluf/bootstrap_discover_payload.c create mode 100644 tests/fluf/cbor_decoder_ll.c create mode 100644 tests/fluf/cbor_encoder.c create mode 100644 tests/fluf/coap_udp_msg.c create mode 100644 tests/fluf/discover_payload.c create mode 100644 tests/fluf/lwm2m_decode.c create mode 100644 tests/fluf/lwm2m_prepare.c create mode 100644 tests/fluf/options.c create mode 100644 tests/fluf/register_payload.c create mode 100644 tests/fluf/senml_cbor_encoder.c create mode 100644 tests/fluf/tlv_in.c create mode 100644 tests/fluf/utils.c create mode 100755 tools/coverage diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..9856b263 --- /dev/null +++ b/.clang-format @@ -0,0 +1,22 @@ +BasedOnStyle: llvm +AccessModifierOffset: -4 +AlignEscapedNewlinesLeft: true +AlignOperands: true +AllowShortFunctionsOnASingleLine: Empty +AlwaysBreakTemplateDeclarations: true +AvoidMisleadingControlStatementContinuationIndent: true +BinPackParameters: false +BinPackArguments: true +BreakBeforeBinaryOperators: NonAssignment +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 8 +ContinuationIndentWidth: 8 +Cpp11BracedListStyle: false +IndentWidth: 4 +PenaltyBreakBeforeFirstCallParameter: 40 +SpaceAfterCStyleCast: true +TypenameMacros: ['AVS_LIST', 'AVS_RBTREE', 'AVS_RBTREE_ELEM', 'AVS_VECTOR', 'AVS_SORTED_SET', 'AVS_SORTED_SET_ELEM'] +BreakDesignatedInitializers: true +IndentPPDirectives: AfterHash +CommentPragmas: "AVSYSTEM_ANJAY_COMMERCIAL.*" +ExperimentalAutoDetectBinPacking: true diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..effcca92 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +docs/diagrams/out + +# NetBeans Project +nbproject + +# VSCode +.vscode/ + +build/ +site/ + +# make +CMakeFiles/ +anjay_basic_example +cmake_install.cmake +CMakeCache.txt +libfluf.a +libavs_commons.a +Makefile + +/anjay_basic_with_dm_example +/dm_tests +/sdm_tests +/example_coap_udp +/fluf_tests diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..102f958f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "deps/avs_commons"] + path = deps/avs_commons + url = https://github.com/AVSystem/avs_commons.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..75295f1f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,170 @@ +cmake_minimum_required(VERSION 3.4.0) + +add_compile_options(-std=c99 -pedantic -Wall -Wextra -Winit-self + -Wmissing-declarations -Wc++-compat -Wsign-conversion + -Wconversion -Wcast-qual -Wvla -Wno-variadic-macros + -Wno-long-long -Wshadow -O0 -g3) +#target_compile_options(fluf PUBLIC -O0 -g3 -Wall) + +#avs_commons +project(avs_commons C) +file(GLOB_RECURSE commons_src + "${CMAKE_CURRENT_SOURCE_DIR}/deps/avs_commons/src/**/*.c" + ) + +add_library(avs_commons ${commons_src}) + +target_include_directories(avs_commons PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/linux_config" + "${CMAKE_CURRENT_SOURCE_DIR}/deps/avs_commons/include_public" + "${CMAKE_CURRENT_SOURCE_DIR}/deps/avs_commons/src" + ) +target_link_libraries(avs_commons m pthread anl) + + +#fluf +project(fluf C) + +file(GLOB_RECURSE fluf_src + "${CMAKE_CURRENT_SOURCE_DIR}/src/fluf/*.c" + ) +add_library(fluf ${fluf_src}) + +target_compile_definitions(fluf PUBLIC + -DFLUF_WITH_LWM2M11 + -DFLUF_WITH_LWM2M12 + -DFLUF_WITH_SENML_CBOR + -DFLUF_WITH_LWM2M_CBOR + -DFLUF_WITH_CBOR + -DFLUF_WITH_CBOR_INDEFINITE_BYTES + -DFLUF_WITH_CBOR_DECIMAL_FRACTIONS + -DFLUF_WITH_CBOR_HALF_FLOAT + -DFLUF_WITH_CBOR_STRING_TIME) + +target_include_directories(fluf PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/include_public" + "${CMAKE_CURRENT_SOURCE_DIR}/deps/avs_commons/include_public" + ) +target_link_libraries(fluf PUBLIC avs_commons) + +# anjay_basic example +project(anjay_basic_example C) + +file(GLOB_RECURSE anjay_lite_src + "${CMAKE_CURRENT_SOURCE_DIR}/src/anjay_lite/*.c" + "${CMAKE_CURRENT_SOURCE_DIR}/src/anj/sdm/*.c" + "${CMAKE_CURRENT_SOURCE_DIR}/src/anj/compat/posix/anj_time.c") + +add_executable(anjay_basic_example + examples/anjay_basic/anjay_net_impl.c + examples/anjay_basic/main.c + ${anjay_lite_src}) + +target_include_directories(anjay_basic_example PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/include_public" + ) + +target_link_libraries(anjay_basic_example PUBLIC fluf) + +# anjay_basic_with_send example +project(anjay_send_example C) + +add_executable(anjay_send_example + examples/anjay_basic_with_send/anjay_net_impl.c + examples/anjay_basic_with_send/main.c + ${anjay_lite_src}) + +target_include_directories(anjay_send_example PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/include_public" + ) + +target_link_libraries(anjay_send_example PUBLIC fluf) + +# anjay_basic_with_firmware_update example +project(anjay_firmware_update_example C) + +add_executable(anjay_firmware_update_example + examples/anjay_basic_with_firmware_update/anjay_net_impl.c + examples/anjay_basic_with_firmware_update/firmware_update.c + examples/anjay_basic_with_firmware_update/main.c + ${anjay_lite_src}) + +target_include_directories(anjay_firmware_update_example PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/examples/anjay_basic_with_firmware_update" + "${CMAKE_CURRENT_SOURCE_DIR}/include_public" + ) + +target_link_libraries(anjay_firmware_update_example PUBLIC fluf) + + +#fluf_tests +project(fluf_tests C) + +file(GLOB fluf_tests_sources + "${CMAKE_CURRENT_SOURCE_DIR}/tests/fluf/*.c") + +add_executable(fluf_tests + ${fluf_tests_sources}) + +target_include_directories(fluf_tests PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/include_public" + "${CMAKE_CURRENT_SOURCE_DIR}/deps/avs_commons/include_public" + ) +target_link_libraries(fluf_tests PUBLIC fluf) + +target_compile_options(fluf_tests PUBLIC -Wno-pedantic -Wno-c++-compat) + +#dm_tests +project(dm_tests C) + +file(GLOB dm_test_sources + "${CMAKE_CURRENT_SOURCE_DIR}/src/anj/*.c" + "${CMAKE_CURRENT_SOURCE_DIR}/src/anj/dm/*.c" + "${CMAKE_CURRENT_SOURCE_DIR}/tests/anj/dm/*.c") + +add_executable(dm_tests + ${dm_test_sources}) + +target_compile_definitions(dm_tests PUBLIC + -DUNIT_TESTING) + +target_include_directories(dm_tests PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/include_public" + "${CMAKE_CURRENT_SOURCE_DIR}/deps/avs_commons/include_public" + "${CMAKE_CURRENT_SOURCE_DIR}/tests/anj/dm") + +target_link_libraries(dm_tests PUBLIC avs_commons) + +target_compile_options(dm_tests PUBLIC -Wno-pedantic -Wno-c++-compat) + +#sdm_tests +project(sdm_tests C) + +file(GLOB sdm_test_sources + "${CMAKE_CURRENT_SOURCE_DIR}/src/anj/sdm/*.c" + "${CMAKE_CURRENT_SOURCE_DIR}/tests/anj/sdm/*.c" + "${CMAKE_CURRENT_SOURCE_DIR}/src/anj/compat/posix/anj_time.c") + +add_executable(sdm_tests + ${sdm_test_sources}) + +target_compile_definitions(sdm_tests PUBLIC -DUNIT_TESTING) + +target_include_directories(sdm_tests PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/include_public" + "${CMAKE_CURRENT_SOURCE_DIR}/deps/avs_commons/include_public") + +target_link_libraries(sdm_tests PUBLIC fluf) + +target_compile_options(sdm_tests PUBLIC -Wno-pedantic -Wno-c++-compat) + +#valgrind targets +find_program(VALGRIND_EXECUTABLE valgrind) +if(VALGRIND_EXECUTABLE) + add_custom_target(fluf_tests_with_valgrind + COMMAND ${VALGRIND_EXECUTABLE} --leak-check=full --track-origins=yes -q --error-exitcode=63 $) + add_custom_target(dm_tests_with_valgrind + COMMAND ${VALGRIND_EXECUTABLE} --leak-check=full --track-origins=yes -q --error-exitcode=63 $) + add_custom_target(sdm_tests_with_valgrind + COMMAND ${VALGRIND_EXECUTABLE} --leak-check=full --track-origins=yes -q --error-exitcode=63 $) +endif() diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..b9d3b5de --- /dev/null +++ b/LICENSE @@ -0,0 +1,47 @@ + AVSystem-5-clause License + +Copyright by AVSystem sp. z o.o. (AVSystem) 17th March 2022. +All rights reserved. +This software is licensed under the following conditions specified below. + + + +All materials referring to features or use of this software must display the +following acknowledgement: + +"This product includes software developed by AVSystem" + +Redistribution and use of the software in source and binary code forms, with or +without modification, are permitted only provided that the following conditions +are met: + +1. Redistribution of source code must retain copyright notice and include the + list of usage conditions and AVSystem's disclaimer. + +2. Redistribution in binary form, except as embedded in a physical device, must + contain the above copyright notice, this list of conditions of use and the + following disclaimer in the documentation and/or other materials provided + with the software. + +3. Neither the name of the AVSystem nor its collaborators (if any) may be used + to offer or promote products derived from this software without prior + written consent of the owner. + +4. Commercial use of this software, with or without modifications, requires + prior notification to AVSystem at sales@avsystem.com of such use. In case + the software is used together with AVSystem's CoioteDM Platform, such + notification is not required. + +5. Commercial use of the software is limited to 100.000 (one hundred thousand) + devices, unless a separate agreement with AVSystem is made. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT OWNER "AS IS" AND ANY DIRECT OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE COPYRIGHT OWNER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND REGARDLESS OF THE SOURCE OF +LIABILITY, WHETHER CONTRACT, DIRECT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..79497dfc --- /dev/null +++ b/README.md @@ -0,0 +1,80 @@ +# Anjay LwM2M library [](http://www.avsystem.com/) + +## ALPHA RELEASE NOTICE + +This is an alpha, work-in-progress release of Anjay 4. All APIs are a subject to +change without any notice. + +### Currently supported features + +- no dynamic memory allocation +- UDP binding +- LwM2M 1.1 & 1.2 +- TLV Content-Format for input operations +- CBOR and SenML CBOR Content-Format for output operations +- Static data model supporting all operations, except Composite operations +- LwM2M Send support +- Minimal implementations of Security (/0), Server (/1) and Device (/3) objects +- Full implementation for Firmware Update (/5) object +- Partial support for LwM2M Notify (only on resource level, with `pmin` and + `pmax` attributes) + +## What is Anjay? + +Anjay is a C library that aims to be the reference implementation of the OMA +Lightweight Machine-to-Machine (LwM2M) device management protocol. It eases +development of fully-featured LwM2M client applications by taking care of +protocol details, allowing the user to focus on device-specific aspects. + +The project has been created and is actively maintained by +[AVSystem](https://www.avsystem.com). + +## Getting started + +> **__NOTE:__** Currently, the only supported OS that can run examples and tests +> is Linux. + +### Building + +```sh +mkdir build +cd build +cmake .. + +make -j all +``` + +### Running examples + +There are 3 examples available: +- `anjay_basic_example`, which is a minimal application that instantiates a + simulated temperature sensor +- `anjay_send_example`, which additionally periodicaly reports temperature + readings using LwM2M Send method +- `anjay_firmware_update_example`, which uses LwM2M Firmware Update object to + download and run the downloaded binary + +```sh +cd build +./anjay_basic_example endpoint_name +./anjay_send_example endpoint_name +./anjay_firmware_update_example endpoint_name +``` + +### Running tests + +```sh +cd build +./dm_tests +./fluf_tests +./sdm_tests +``` + +Additionally, these tests can be run with Valgrind: + +```sh +cd build +make fluf_tests_with_valgrind +make dm_tests_with_valgrind +make sdm_tests_with_valgrind +``` diff --git a/deps/avs_commons b/deps/avs_commons new file mode 160000 index 00000000..ebcc5a7a --- /dev/null +++ b/deps/avs_commons @@ -0,0 +1 @@ +Subproject commit ebcc5a7a49fde3566d83c2453c15bd273b3c2d00 diff --git a/examples/anjay_basic/anjay_net_impl.c b/examples/anjay_basic/anjay_net_impl.c new file mode 100644 index 00000000..90aaf719 --- /dev/null +++ b/examples/anjay_basic/anjay_net_impl.c @@ -0,0 +1,200 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +typedef struct { + char hostname_storage[100]; + char port_storage[6]; + struct addrinfo hints; + struct gaicb query; + atomic_bool gai_finished; + int fd; + size_t send_res_await_counter; + ssize_t last_send_res; +} conn_ctx_t; + +static int af_family_from_ip_ver(anjay_net_ip_ver_t ip_ver) { + return ip_ver == ANJAY_NET_IP_VER_V4 ? AF_INET : AF_INET6; +} + +static void handle_gai_result(conn_ctx_t *conn_ctx) { + if (!conn_ctx->query.ar_result) { + return; + } + int fd = socket(conn_ctx->hints.ai_family, + conn_ctx->hints.ai_socktype | SOCK_NONBLOCK, 0); + if (fd < 0) { + return; + } + if (connect(fd, conn_ctx->query.ar_result->ai_addr, + conn_ctx->query.ar_result->ai_addrlen) + < 0) { + return; + } + conn_ctx->fd = fd; +} + +static void gai_cb(union sigval sigval) { + conn_ctx_t *conn_ctx = (conn_ctx_t *) sigval.sival_ptr; + handle_gai_result(conn_ctx); + freeaddrinfo(conn_ctx->query.ar_result); + atomic_store(&conn_ctx->gai_finished, true); +} + +static int copy_str(char *target, size_t target_size, const char *source) { + int result = snprintf(target, target_size, "%s", source); + return (result < 0 || (size_t) result >= target_size) ? -1 : 0; +} + +anjay_net_op_res_t anjay_net_op_handler(anjay_net_op_ctx_t *op_ctx) { + switch (op_ctx->op) { + case ANJAY_NET_OP_OPEN_UDP: { + conn_ctx_t *conn_ctx = (conn_ctx_t *) calloc(1, sizeof(*conn_ctx)); + if (!conn_ctx) { + return ANJAY_NET_OP_RES_ERR; + } + + atomic_init(&conn_ctx->gai_finished, false); + conn_ctx->fd = -1; + if (copy_str(conn_ctx->hostname_storage, + sizeof(conn_ctx->hostname_storage), + op_ctx->args.open_udp.hostname)) { + return ANJAY_NET_OP_RES_ERR; + } + snprintf(conn_ctx->port_storage, sizeof(conn_ctx->port_storage), + "%" PRIu16, op_ctx->args.open_udp.port); + conn_ctx->hints.ai_family = + af_family_from_ip_ver(op_ctx->args.open_udp.version); + conn_ctx->hints.ai_socktype = SOCK_DGRAM; + conn_ctx->query.ar_request = &conn_ctx->hints; + conn_ctx->query.ar_name = conn_ctx->hostname_storage; + conn_ctx->query.ar_service = conn_ctx->port_storage; + + struct sigevent callback = { + .sigev_notify = SIGEV_THREAD, + .sigev_value.sival_ptr = conn_ctx, + .sigev_notify_function = gai_cb + }; + + struct gaicb *query_ptr = &conn_ctx->query; + if (getaddrinfo_a(GAI_NOWAIT, &query_ptr, 1, &callback)) { + free(conn_ctx); + return ANJAY_NET_OP_RES_ERR; + } + + op_ctx->conn_ref.ref_ptr = conn_ctx; + return ANJAY_NET_OP_RES_OK; + } + case ANJAY_NET_OP_OPEN_UDP_RES: { + conn_ctx_t *conn_ctx = (conn_ctx_t *) op_ctx->conn_ref.ref_ptr; + + if (!atomic_load(&conn_ctx->gai_finished)) { + return ANJAY_NET_OP_RES_AGAIN; + } + if (conn_ctx->fd < 0) { + return ANJAY_NET_OP_RES_ERR; + } + return ANJAY_NET_OP_RES_OK; + } + case ANJAY_NET_OP_TRY_RECV: { + conn_ctx_t *conn_ctx = (conn_ctx_t *) op_ctx->conn_ref.ref_ptr; + + errno = 0; + ssize_t received = + recv(conn_ctx->fd, op_ctx->args.try_recv.out_read_buf, + op_ctx->args.try_recv.length, 0); + + if (received < 0) { + return errno == EWOULDBLOCK ? ANJAY_NET_OP_RES_AGAIN + : ANJAY_NET_OP_RES_ERR; + } + + op_ctx->args.try_recv.out_read_length = (size_t) received; + return ANJAY_NET_OP_RES_OK; + } + case ANJAY_NET_OP_SEND: { + conn_ctx_t *conn_ctx = (conn_ctx_t *) op_ctx->conn_ref.ref_ptr; + + errno = 0; + ssize_t sent = send(conn_ctx->fd, op_ctx->args.send.buf, + op_ctx->args.send.length, 0); + + if (sent == -1) { + // HACK: in case of errno == EWOULDBLOCK I'd probably have to + // properly copy the packet and schedule it to be sent in next + // attempts/calls with ANJAY_NET_OP_SEND_RES + // + // I imagine that implementation with a modem like BG96 would + // copy the buffer and await for the result of send operation + // which would be polled by ANJAY_NET_OP_SEND_RES. Current API + // seems to be the one which matches both possible interfaces + // the best. + return ANJAY_NET_OP_RES_ERR; + } + + // Just return it in next iteration + conn_ctx->last_send_res = sent; + // Modem implementation could require repetetive asking for + // send result, so simulate it with a counter + conn_ctx->send_res_await_counter = 0; + + return ANJAY_NET_OP_RES_OK; + } + case ANJAY_NET_OP_SEND_RES: { + conn_ctx_t *conn_ctx = (conn_ctx_t *) op_ctx->conn_ref.ref_ptr; + + if (conn_ctx->send_res_await_counter++ < 2) { + return ANJAY_NET_OP_RES_AGAIN; + } + + if (conn_ctx->last_send_res < 0) { + return ANJAY_NET_OP_RES_ERR; + } + + op_ctx->args.send_res.out_write_length = + (size_t) conn_ctx->last_send_res; + return ANJAY_NET_OP_RES_OK; + } + case ANJAY_NET_OP_CLOSE: { + // close in Linux seems to be immediate? so let's just do it in + // function which asks for result xD + return ANJAY_NET_OP_RES_OK; + } + case ANJAY_NET_OP_CLOSE_RES: { + conn_ctx_t *conn_ctx = (conn_ctx_t *) op_ctx->conn_ref.ref_ptr; + if (conn_ctx->fd != -1) { + return close(conn_ctx->fd) ? ANJAY_NET_OP_RES_ERR + : ANJAY_NET_OP_RES_OK; + } + return ANJAY_NET_OP_RES_OK; + } + case ANJAY_NET_OP_CLEANUP: { + conn_ctx_t *conn_ctx = (conn_ctx_t *) op_ctx->conn_ref.ref_ptr; + free(conn_ctx); + return ANJAY_NET_OP_RES_OK; + } + default: { return ANJAY_NET_OP_RES_ERR; } + } +} diff --git a/examples/anjay_basic/main.c b/examples/anjay_basic/main.c new file mode 100644 index 00000000..2b641a28 --- /dev/null +++ b/examples/anjay_basic/main.c @@ -0,0 +1,167 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#define _DEFAULT_SOURCE + +#include + +#include +#include +#include + +#include +#include + +#include +#include + +static int sensor_read_callback(sdm_obj_t *obj, + sdm_obj_inst_t *obj_inst, + sdm_res_t *res, + sdm_res_inst_t *res_inst, + fluf_res_value_t *out_value); + +static const sdm_res_spec_t sensor_val_res_spec = { + .rid = 5700, + .operation = SDM_RES_R, + .type = FLUF_DATA_TYPE_DOUBLE +}; +static const sdm_res_spec_t sensor_unit_spec = { + .rid = 5701, + .operation = SDM_RES_R, + .type = FLUF_DATA_TYPE_STRING +}; +static const sdm_res_spec_t sensor_application_type_spec = { + .rid = 5750, + .operation = SDM_RES_RW, + .type = FLUF_DATA_TYPE_STRING +}; +static const sdm_res_handlers_t res_handlers = { + .res_read = sensor_read_callback +}; +static sdm_res_t sensor_val_res = { + .res_handlers = &res_handlers, + .res_spec = &sensor_val_res_spec +}; +static char units[] = "C"; +static char application_type[20]; +static sdm_res_t resources[3] = { + { + .res_spec = &sensor_val_res_spec, + .res_handlers = &res_handlers + }, + { + .res_spec = &sensor_unit_spec, + .value.res_value.value.bytes_or_string.data = units, + .value.res_value.value.bytes_or_string.chunk_length = sizeof(units) - 1 + }, + { + .res_spec = &sensor_application_type_spec, + .value.res_value.value.bytes_or_string.data = application_type, + .value.res_value.resource_buffer_size = sizeof(application_type) + } +}; +static sdm_obj_inst_t temperature_obj_inst_1 = { + .iid = 0, + .res_count = 3, + .resources = resources +}; +static sdm_obj_inst_t temperature_obj_inst_2 = { + .iid = 1, + .res_count = 1, + .resources = &sensor_val_res +}; + +static sdm_obj_inst_t *temperature_obj_insts[2] = { &temperature_obj_inst_1, + &temperature_obj_inst_2 }; + +static sdm_obj_t temperature_obj = { + .version = "1.1", + .oid = 3303, + .insts = temperature_obj_insts, + .inst_count = 2, + .max_inst_count = 2 +}; + +static int sensor_read_callback(sdm_obj_t *obj, + sdm_obj_inst_t *obj_inst, + sdm_res_t *res, + sdm_res_inst_t *res_inst, + fluf_res_value_t *out_value) { + (void) (obj); + (void) (res); + (void) (res_inst); + + static double sensor_value_1 = 0.0; + static double sensor_value_2 = 2.0; + + if (obj_inst == &temperature_obj_inst_1) { + out_value->double_value = sensor_value_1; + sensor_value_1 += 1.23; + } else if (obj_inst == &temperature_obj_inst_2) { + out_value->double_value = sensor_value_2; + sensor_value_2 *= 2.0; + } else { + return SDM_ERR_BAD_REQUEST; + } + + return 0; +} + +int main(int argc, char *argv[]) { + if (argc != 2) { + printf("No endpoint name given\n"); + return -1; + } + + anjay_lite_t anjay_lite = { + .endpoint_name = argv[1], + .server_conf = { + .binding = FLUF_BINDING_UDP, + .ssid = 1, + .security_mode = ANJAY_SECURITY_NOSEC, + .hostname = "eu.iot.avsystem.cloud", + .port = 5683, + .lifetime = 20, + } + }; + + sdm_device_object_init_t device_obj_conf = { + .firmware_version = "1.0", + .manufacturer = "", + .reboot_handler = NULL, + .serial_number = "12345", + .supported_binding_modes = "U" + }; + + sdm_initialize(&anjay_lite.dm, + anjay_lite.objs_array, + ANJAY_LITE_ALLOWED_OBJECT_NUMBER); + if (sdm_add_obj(&anjay_lite.dm, &temperature_obj)) { + printf("sdm_add_obj error\n"); + return -1; + } + + if (sdm_device_object_install(&anjay_lite.dm, &device_obj_conf)) { + printf("sdm_device_object_install error\n"); + return -1; + } + + if (anjay_lite_init(&anjay_lite)) { + printf("anjay_lite_init error\n"); + return -1; + } + + while (1) { + anjay_lite_process(&anjay_lite); + usleep(50 * 1000); + } + + return 0; +} diff --git a/examples/anjay_basic_with_firmware_update/anjay_net_impl.c b/examples/anjay_basic_with_firmware_update/anjay_net_impl.c new file mode 100644 index 00000000..574d0436 --- /dev/null +++ b/examples/anjay_basic_with_firmware_update/anjay_net_impl.c @@ -0,0 +1,198 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +typedef struct { + char hostname_storage[100]; + char port_storage[6]; + struct addrinfo hints; + struct gaicb query; + atomic_bool gai_finished; + int fd; + size_t send_res_await_counter; + ssize_t last_send_res; +} conn_ctx_t; + +static int af_family_from_ip_ver(anjay_net_ip_ver_t ip_ver) { + return ip_ver == ANJAY_NET_IP_VER_V4 ? AF_INET : AF_INET6; +} + +static void handle_gai_result(conn_ctx_t *conn_ctx) { + if (!conn_ctx->query.ar_result) { + return; + } + int fd = socket(conn_ctx->hints.ai_family, + conn_ctx->hints.ai_socktype | SOCK_NONBLOCK, 0); + if (fd < 0) { + return; + } + if (connect(fd, conn_ctx->query.ar_result->ai_addr, + conn_ctx->query.ar_result->ai_addrlen) + < 0) { + return; + } + conn_ctx->fd = fd; +} + +static void gai_cb(union sigval sigval) { + conn_ctx_t *conn_ctx = (conn_ctx_t *) sigval.sival_ptr; + handle_gai_result(conn_ctx); + freeaddrinfo(conn_ctx->query.ar_result); + atomic_store(&conn_ctx->gai_finished, true); +} + +static int copy_str(char *target, size_t target_size, const char *source) { + int result = snprintf(target, target_size, "%s", source); + return (result < 0 || (size_t) result >= target_size) ? -1 : 0; +} + +anjay_net_op_res_t anjay_net_op_handler(anjay_net_op_ctx_t *op_ctx) { + switch (op_ctx->op) { + case ANJAY_NET_OP_OPEN_UDP: { + conn_ctx_t *conn_ctx = (conn_ctx_t *) calloc(1, sizeof(*conn_ctx)); + if (!conn_ctx) { + return ANJAY_NET_OP_RES_ERR; + } + + atomic_init(&conn_ctx->gai_finished, false); + conn_ctx->fd = -1; + if (copy_str(conn_ctx->hostname_storage, + sizeof(conn_ctx->hostname_storage), + op_ctx->args.open_udp.hostname)) { + return ANJAY_NET_OP_RES_ERR; + } + snprintf(conn_ctx->port_storage, sizeof(conn_ctx->port_storage), + "%" PRIu16, op_ctx->args.open_udp.port); + conn_ctx->hints.ai_family = + af_family_from_ip_ver(op_ctx->args.open_udp.version); + conn_ctx->hints.ai_socktype = SOCK_DGRAM; + conn_ctx->query.ar_request = &conn_ctx->hints; + conn_ctx->query.ar_name = conn_ctx->hostname_storage; + conn_ctx->query.ar_service = conn_ctx->port_storage; + + struct sigevent callback = { + .sigev_notify = SIGEV_THREAD, + .sigev_value.sival_ptr = conn_ctx, + .sigev_notify_function = gai_cb + }; + + struct gaicb *query_ptr = &conn_ctx->query; + if (getaddrinfo_a(GAI_NOWAIT, &query_ptr, 1, &callback)) { + free(conn_ctx); + return ANJAY_NET_OP_RES_ERR; + } + + op_ctx->conn_ref.ref_ptr = conn_ctx; + return ANJAY_NET_OP_RES_OK; + } + case ANJAY_NET_OP_OPEN_UDP_RES: { + conn_ctx_t *conn_ctx = (conn_ctx_t *) op_ctx->conn_ref.ref_ptr; + + if (!atomic_load(&conn_ctx->gai_finished)) { + return ANJAY_NET_OP_RES_AGAIN; + } + if (conn_ctx->fd < 0) { + return ANJAY_NET_OP_RES_ERR; + } + return ANJAY_NET_OP_RES_OK; + } + case ANJAY_NET_OP_TRY_RECV: { + conn_ctx_t *conn_ctx = (conn_ctx_t *) op_ctx->conn_ref.ref_ptr; + + errno = 0; + ssize_t received = + recv(conn_ctx->fd, op_ctx->args.try_recv.out_read_buf, + op_ctx->args.try_recv.length, 0); + + if (received < 0) { + return errno == EWOULDBLOCK ? ANJAY_NET_OP_RES_AGAIN + : ANJAY_NET_OP_RES_ERR; + } + + op_ctx->args.try_recv.out_read_length = (size_t) received; + return ANJAY_NET_OP_RES_OK; + } + case ANJAY_NET_OP_SEND: { + conn_ctx_t *conn_ctx = (conn_ctx_t *) op_ctx->conn_ref.ref_ptr; + + errno = 0; + ssize_t sent = send(conn_ctx->fd, op_ctx->args.send.buf, + op_ctx->args.send.length, 0); + + if (sent == -1) { + // HACK: in case of errno == EWOULDBLOCK I'd probably have to + // properly copy the packet and schedule it to be sent in next + // attempts/calls with ANJAY_NET_OP_SEND_RES + // + // I imagine that implementation with a modem like BG96 would + // copy the buffer and await for the result of send operation + // which would be polled by ANJAY_NET_OP_SEND_RES. Current API + // seems to be the one which matches both possible interfaces + // the best. + return ANJAY_NET_OP_RES_ERR; + } + + // Just return it in next iteration + conn_ctx->last_send_res = sent; + // Modem implementation could require repetetive asking for + // send result, so simulate it with a counter + conn_ctx->send_res_await_counter = 0; + + return ANJAY_NET_OP_RES_OK; + } + case ANJAY_NET_OP_SEND_RES: { + conn_ctx_t *conn_ctx = (conn_ctx_t *) op_ctx->conn_ref.ref_ptr; + + if (conn_ctx->send_res_await_counter++ < 2) { + return ANJAY_NET_OP_RES_AGAIN; + } + + if (conn_ctx->last_send_res < 0) { + return ANJAY_NET_OP_RES_ERR; + } + + op_ctx->args.send_res.out_write_length = + (size_t) conn_ctx->last_send_res; + return ANJAY_NET_OP_RES_OK; + } + case ANJAY_NET_OP_CLOSE: { + // close in Linux seems to be immediate? so let's just do it in + // function which asks for result xD + return ANJAY_NET_OP_RES_OK; + } + case ANJAY_NET_OP_CLOSE_RES: { + conn_ctx_t *conn_ctx = (conn_ctx_t *) op_ctx->conn_ref.ref_ptr; + if (conn_ctx->fd != -1) { + return close(conn_ctx->fd) ? ANJAY_NET_OP_RES_ERR + : ANJAY_NET_OP_RES_OK; + } + return ANJAY_NET_OP_RES_OK; + } + case ANJAY_NET_OP_CLEANUP: { + conn_ctx_t *conn_ctx = (conn_ctx_t *) op_ctx->conn_ref.ref_ptr; + free(conn_ctx); + return ANJAY_NET_OP_RES_OK; + } + default: { return ANJAY_NET_OP_RES_ERR; } + } +} diff --git a/examples/anjay_basic_with_firmware_update/firmware_update.c b/examples/anjay_basic_with_firmware_update/firmware_update.c new file mode 100644 index 00000000..ba36d737 --- /dev/null +++ b/examples/anjay_basic_with_firmware_update/firmware_update.c @@ -0,0 +1,158 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#define _DEFAULT_SOURCE + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "firmware_update.h" + +static const char *FW_IMAGE_DOWNLOAD_NAME = "/tmp/firmware_image.bin"; +static const char *FW_UPDATED_MARKER = "/tmp/fw-updated-marker"; + +typedef struct { + const char *endpoint_name; + const char *firmware_version; + FILE *firmware_file; + size_t offset; + bool waiting_for_reboot; +} firmware_update_t; + +static firmware_update_t firmware_update; + +static sdm_fw_update_result_t fu_write_start(void *user_ptr) { + firmware_update_t *fu = (firmware_update_t *) user_ptr; + + assert(fu->firmware_file == NULL); + fu->firmware_file = fopen(FW_IMAGE_DOWNLOAD_NAME, "wb"); + if (!fu->firmware_file) { + printf("Could not open %s\n", FW_IMAGE_DOWNLOAD_NAME); + return SDM_FW_UPDATE_RESULT_FAILED; + } + printf("Firmware download begins\n"); + return SDM_FW_UPDATE_RESULT_SUCCESS; +} + +static sdm_fw_update_result_t +fu_write(void *user_ptr, void *data, size_t data_size) { + firmware_update_t *fu = (firmware_update_t *) user_ptr; + assert(fu->firmware_file != NULL); + + printf("Writing %lu bytes with %lu offset\n", data_size, fu->offset); + fu->offset += data_size; + if (fwrite(data, data_size, 1, fu->firmware_file) != 1) { + printf("Writing to firmware image failed\n"); + return SDM_FW_UPDATE_RESULT_FAILED; + } + return SDM_FW_UPDATE_RESULT_SUCCESS; +} + +static sdm_fw_update_result_t fu_write_finish(void *user_ptr) { + firmware_update_t *fu = (firmware_update_t *) user_ptr; + assert(fu->firmware_file != NULL); + + if (fclose(fu->firmware_file)) { + printf("Closing firmware image failed\n"); + fu->firmware_file = NULL; + return SDM_FW_UPDATE_RESULT_FAILED; + } + fu->offset = 0; + fu->firmware_file = NULL; + printf("Firmware download ends\n"); + return SDM_FW_UPDATE_RESULT_SUCCESS; +} + +static sdm_fw_update_result_t fu_update_start(void *user_ptr) { + (void) user_ptr; + printf("fu_update_start\n"); + firmware_update.waiting_for_reboot = true; + return SDM_FW_UPDATE_RESULT_SUCCESS; +} + +static void fu_reset(void *user_ptr) { + firmware_update_t *fu = (firmware_update_t *) user_ptr; + printf("fu_reset\n"); + if (fu->firmware_file) { + (void) fclose(fu->firmware_file); + fu->firmware_file = NULL; + } + unlink(FW_IMAGE_DOWNLOAD_NAME); +} + +static const char *fu_get_version(void *user_ptr) { + firmware_update_t *fu = (firmware_update_t *) user_ptr; + printf("fu_get_version\n"); + return fu->firmware_version; +} + +static sdm_fw_update_handlers_t fu_handlers = { + .package_write_start_handler = fu_write_start, + .package_write_handler = fu_write, + .package_write_finish_handler = fu_write_finish, + .update_start_handler = fu_update_start, + .reset_handler = fu_reset, + .get_version = fu_get_version +}; + +void fw_update_check(void) { + if (firmware_update.waiting_for_reboot) { + printf("perform reset\n"); + firmware_update.waiting_for_reboot = false; + if (chmod(FW_IMAGE_DOWNLOAD_NAME, 0700) == -1) { + printf("Could not make firmware executable: %s\n", strerror(errno)); + return; + } + FILE *marker = fopen(FW_UPDATED_MARKER, "w"); + if (!marker) { + printf("Marker file could not be created\n"); + return; + } + fclose(marker); + + (void) execl(FW_IMAGE_DOWNLOAD_NAME, + FW_IMAGE_DOWNLOAD_NAME, + firmware_update.endpoint_name, + NULL); + printf("execl() failed: %s\n", strerror(errno)); + unlink(FW_UPDATED_MARKER); + } +} + +int fw_update_object_install(sdm_data_model_t *dm, + const char *firmware_version, + const char *endpoint_name) { + + firmware_update.firmware_version = firmware_version; + firmware_update.endpoint_name = endpoint_name; + firmware_update.waiting_for_reboot = false; + + if (sdm_fw_update_object_install(dm, + &fu_handlers, + &firmware_update, + (uint8_t) SDM_FW_UPDATE_PROTOCOL_COAP)) { + return -1; + } + + if (access(FW_UPDATED_MARKER, F_OK) != -1) { + printf("firmware update succeded\n"); + unlink(FW_UPDATED_MARKER); + return sdm_fw_update_object_set_update_result( + SDM_FW_UPDATE_RESULT_SUCCESS); + } + + return 0; +} diff --git a/examples/anjay_basic_with_firmware_update/firmware_update.h b/examples/anjay_basic_with_firmware_update/firmware_update.h new file mode 100644 index 00000000..6560e29f --- /dev/null +++ b/examples/anjay_basic_with_firmware_update/firmware_update.h @@ -0,0 +1,25 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#ifndef FIRMWARE_UPDATE_H +#define FIRMWARE_UPDATE_H + +#include +#include +#include + +#include + +int fw_update_object_install(sdm_data_model_t *dm, + const char *firmware_version, + const char *endpoint_name); + +void fw_update_check(void); + +#endif // FIRMWARE_UPDATE_H diff --git a/examples/anjay_basic_with_firmware_update/main.c b/examples/anjay_basic_with_firmware_update/main.c new file mode 100644 index 00000000..f077afcf --- /dev/null +++ b/examples/anjay_basic_with_firmware_update/main.c @@ -0,0 +1,175 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#define _DEFAULT_SOURCE + +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#include "firmware_update.h" + +static int sensor_read_callback(sdm_obj_t *obj, + sdm_obj_inst_t *obj_inst, + sdm_res_t *res, + sdm_res_inst_t *res_inst, + fluf_res_value_t *out_value); + +static const sdm_res_spec_t sensor_val_res_spec = { + .rid = 5700, + .operation = SDM_RES_R, + .type = FLUF_DATA_TYPE_DOUBLE +}; +static const sdm_res_spec_t sensor_unit_spec = { + .rid = 5701, + .operation = SDM_RES_R, + .type = FLUF_DATA_TYPE_STRING +}; +static const sdm_res_spec_t sensor_application_type_spec = { + .rid = 5750, + .operation = SDM_RES_RW, + .type = FLUF_DATA_TYPE_STRING +}; +static const sdm_res_handlers_t res_handlers = { + .res_read = sensor_read_callback +}; +static sdm_res_t sensor_val_res = { + .res_handlers = &res_handlers, + .res_spec = &sensor_val_res_spec +}; +static char units[] = "C"; +static char application_type[20]; +static sdm_res_t resources[3] = { + { + .res_spec = &sensor_val_res_spec, + .res_handlers = &res_handlers + }, + { + .res_spec = &sensor_unit_spec, + .value.res_value.value.bytes_or_string.data = units, + .value.res_value.value.bytes_or_string.chunk_length = sizeof(units) - 1 + }, + { + .res_spec = &sensor_application_type_spec, + .value.res_value.value.bytes_or_string.data = application_type, + .value.res_value.resource_buffer_size = sizeof(application_type) + } +}; +static sdm_obj_inst_t temperature_obj_inst_1 = { + .iid = 0, + .res_count = 3, + .resources = resources +}; +static sdm_obj_inst_t temperature_obj_inst_2 = { + .iid = 1, + .res_count = 1, + .resources = &sensor_val_res +}; + +static sdm_obj_inst_t *temperature_obj_insts[2] = { &temperature_obj_inst_1, + &temperature_obj_inst_2 }; + +static sdm_obj_t temperature_obj = { + .version = "1.1", + .oid = 3303, + .insts = temperature_obj_insts, + .inst_count = 2, + .max_inst_count = 2 +}; + +static int sensor_read_callback(sdm_obj_t *obj, + sdm_obj_inst_t *obj_inst, + sdm_res_t *res, + sdm_res_inst_t *res_inst, + fluf_res_value_t *out_value) { + (void) (obj); + (void) (res); + (void) (res_inst); + + static double sensor_value_1 = 0.0; + static double sensor_value_2 = 2.0; + + if (obj_inst == &temperature_obj_inst_1) { + out_value->double_value = sensor_value_1; + sensor_value_1 += 1.23; + } else if (obj_inst == &temperature_obj_inst_2) { + out_value->double_value = sensor_value_2; + sensor_value_2 *= 2.0; + } else { + return SDM_ERR_BAD_REQUEST; + } + + return 0; +} + +int main(int argc, char *argv[]) { + if (argc != 2) { + printf("No endpoint name given\n"); + return -1; + } + + anjay_lite_t anjay_lite = { + .endpoint_name = argv[1], + .server_conf = { + .binding = FLUF_BINDING_UDP, + .ssid = 1, + .security_mode = ANJAY_SECURITY_NOSEC, + .hostname = "eu.iot.avsystem.cloud", + .port = 5683, + .lifetime = 20, + } + }; + + sdm_device_object_init_t device_obj_conf = { + .firmware_version = "0.1", + .manufacturer = "", + .reboot_handler = NULL, + .serial_number = "12345", + .supported_binding_modes = "U" + }; + + sdm_initialize(&anjay_lite.dm, + anjay_lite.objs_array, + ANJAY_LITE_ALLOWED_OBJECT_NUMBER); + if (sdm_add_obj(&anjay_lite.dm, &temperature_obj)) { + printf("sdm_add_obj error\n"); + return -1; + } + if (fw_update_object_install( + &anjay_lite.dm, device_obj_conf.firmware_version, argv[1])) { + printf("firmware update object installation error\n"); + return -1; + } + + if (sdm_device_object_install(&anjay_lite.dm, &device_obj_conf)) { + printf("sdm_device_object_install error\n"); + return -1; + } + + if (anjay_lite_init(&anjay_lite)) { + printf("anjay_lite_init error\n"); + return -1; + } + + while (1) { + anjay_lite_process(&anjay_lite); + fw_update_check(); + usleep(50 * 1000); + } + + return 0; +} diff --git a/examples/anjay_basic_with_send/anjay_net_impl.c b/examples/anjay_basic_with_send/anjay_net_impl.c new file mode 100644 index 00000000..90aaf719 --- /dev/null +++ b/examples/anjay_basic_with_send/anjay_net_impl.c @@ -0,0 +1,200 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +typedef struct { + char hostname_storage[100]; + char port_storage[6]; + struct addrinfo hints; + struct gaicb query; + atomic_bool gai_finished; + int fd; + size_t send_res_await_counter; + ssize_t last_send_res; +} conn_ctx_t; + +static int af_family_from_ip_ver(anjay_net_ip_ver_t ip_ver) { + return ip_ver == ANJAY_NET_IP_VER_V4 ? AF_INET : AF_INET6; +} + +static void handle_gai_result(conn_ctx_t *conn_ctx) { + if (!conn_ctx->query.ar_result) { + return; + } + int fd = socket(conn_ctx->hints.ai_family, + conn_ctx->hints.ai_socktype | SOCK_NONBLOCK, 0); + if (fd < 0) { + return; + } + if (connect(fd, conn_ctx->query.ar_result->ai_addr, + conn_ctx->query.ar_result->ai_addrlen) + < 0) { + return; + } + conn_ctx->fd = fd; +} + +static void gai_cb(union sigval sigval) { + conn_ctx_t *conn_ctx = (conn_ctx_t *) sigval.sival_ptr; + handle_gai_result(conn_ctx); + freeaddrinfo(conn_ctx->query.ar_result); + atomic_store(&conn_ctx->gai_finished, true); +} + +static int copy_str(char *target, size_t target_size, const char *source) { + int result = snprintf(target, target_size, "%s", source); + return (result < 0 || (size_t) result >= target_size) ? -1 : 0; +} + +anjay_net_op_res_t anjay_net_op_handler(anjay_net_op_ctx_t *op_ctx) { + switch (op_ctx->op) { + case ANJAY_NET_OP_OPEN_UDP: { + conn_ctx_t *conn_ctx = (conn_ctx_t *) calloc(1, sizeof(*conn_ctx)); + if (!conn_ctx) { + return ANJAY_NET_OP_RES_ERR; + } + + atomic_init(&conn_ctx->gai_finished, false); + conn_ctx->fd = -1; + if (copy_str(conn_ctx->hostname_storage, + sizeof(conn_ctx->hostname_storage), + op_ctx->args.open_udp.hostname)) { + return ANJAY_NET_OP_RES_ERR; + } + snprintf(conn_ctx->port_storage, sizeof(conn_ctx->port_storage), + "%" PRIu16, op_ctx->args.open_udp.port); + conn_ctx->hints.ai_family = + af_family_from_ip_ver(op_ctx->args.open_udp.version); + conn_ctx->hints.ai_socktype = SOCK_DGRAM; + conn_ctx->query.ar_request = &conn_ctx->hints; + conn_ctx->query.ar_name = conn_ctx->hostname_storage; + conn_ctx->query.ar_service = conn_ctx->port_storage; + + struct sigevent callback = { + .sigev_notify = SIGEV_THREAD, + .sigev_value.sival_ptr = conn_ctx, + .sigev_notify_function = gai_cb + }; + + struct gaicb *query_ptr = &conn_ctx->query; + if (getaddrinfo_a(GAI_NOWAIT, &query_ptr, 1, &callback)) { + free(conn_ctx); + return ANJAY_NET_OP_RES_ERR; + } + + op_ctx->conn_ref.ref_ptr = conn_ctx; + return ANJAY_NET_OP_RES_OK; + } + case ANJAY_NET_OP_OPEN_UDP_RES: { + conn_ctx_t *conn_ctx = (conn_ctx_t *) op_ctx->conn_ref.ref_ptr; + + if (!atomic_load(&conn_ctx->gai_finished)) { + return ANJAY_NET_OP_RES_AGAIN; + } + if (conn_ctx->fd < 0) { + return ANJAY_NET_OP_RES_ERR; + } + return ANJAY_NET_OP_RES_OK; + } + case ANJAY_NET_OP_TRY_RECV: { + conn_ctx_t *conn_ctx = (conn_ctx_t *) op_ctx->conn_ref.ref_ptr; + + errno = 0; + ssize_t received = + recv(conn_ctx->fd, op_ctx->args.try_recv.out_read_buf, + op_ctx->args.try_recv.length, 0); + + if (received < 0) { + return errno == EWOULDBLOCK ? ANJAY_NET_OP_RES_AGAIN + : ANJAY_NET_OP_RES_ERR; + } + + op_ctx->args.try_recv.out_read_length = (size_t) received; + return ANJAY_NET_OP_RES_OK; + } + case ANJAY_NET_OP_SEND: { + conn_ctx_t *conn_ctx = (conn_ctx_t *) op_ctx->conn_ref.ref_ptr; + + errno = 0; + ssize_t sent = send(conn_ctx->fd, op_ctx->args.send.buf, + op_ctx->args.send.length, 0); + + if (sent == -1) { + // HACK: in case of errno == EWOULDBLOCK I'd probably have to + // properly copy the packet and schedule it to be sent in next + // attempts/calls with ANJAY_NET_OP_SEND_RES + // + // I imagine that implementation with a modem like BG96 would + // copy the buffer and await for the result of send operation + // which would be polled by ANJAY_NET_OP_SEND_RES. Current API + // seems to be the one which matches both possible interfaces + // the best. + return ANJAY_NET_OP_RES_ERR; + } + + // Just return it in next iteration + conn_ctx->last_send_res = sent; + // Modem implementation could require repetetive asking for + // send result, so simulate it with a counter + conn_ctx->send_res_await_counter = 0; + + return ANJAY_NET_OP_RES_OK; + } + case ANJAY_NET_OP_SEND_RES: { + conn_ctx_t *conn_ctx = (conn_ctx_t *) op_ctx->conn_ref.ref_ptr; + + if (conn_ctx->send_res_await_counter++ < 2) { + return ANJAY_NET_OP_RES_AGAIN; + } + + if (conn_ctx->last_send_res < 0) { + return ANJAY_NET_OP_RES_ERR; + } + + op_ctx->args.send_res.out_write_length = + (size_t) conn_ctx->last_send_res; + return ANJAY_NET_OP_RES_OK; + } + case ANJAY_NET_OP_CLOSE: { + // close in Linux seems to be immediate? so let's just do it in + // function which asks for result xD + return ANJAY_NET_OP_RES_OK; + } + case ANJAY_NET_OP_CLOSE_RES: { + conn_ctx_t *conn_ctx = (conn_ctx_t *) op_ctx->conn_ref.ref_ptr; + if (conn_ctx->fd != -1) { + return close(conn_ctx->fd) ? ANJAY_NET_OP_RES_ERR + : ANJAY_NET_OP_RES_OK; + } + return ANJAY_NET_OP_RES_OK; + } + case ANJAY_NET_OP_CLEANUP: { + conn_ctx_t *conn_ctx = (conn_ctx_t *) op_ctx->conn_ref.ref_ptr; + free(conn_ctx); + return ANJAY_NET_OP_RES_OK; + } + default: { return ANJAY_NET_OP_RES_ERR; } + } +} diff --git a/examples/anjay_basic_with_send/main.c b/examples/anjay_basic_with_send/main.c new file mode 100644 index 00000000..bae4bfe6 --- /dev/null +++ b/examples/anjay_basic_with_send/main.c @@ -0,0 +1,189 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#define _DEFAULT_SOURCE + +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include + +static int sensor_read_callback(sdm_obj_t *obj, + sdm_obj_inst_t *obj_inst, + sdm_res_t *res, + sdm_res_inst_t *res_inst, + fluf_res_value_t *out_value); + +static const sdm_res_spec_t sensor_val_res_spec = { + .rid = 5700, + .operation = SDM_RES_R, + .type = FLUF_DATA_TYPE_DOUBLE +}; +static const sdm_res_spec_t sensor_unit_spec = { + .rid = 5701, + .operation = SDM_RES_R, + .type = FLUF_DATA_TYPE_STRING +}; +static const sdm_res_spec_t sensor_application_type_spec = { + .rid = 5750, + .operation = SDM_RES_RW, + .type = FLUF_DATA_TYPE_STRING +}; +static const sdm_res_handlers_t res_handlers = { + .res_read = sensor_read_callback +}; +static sdm_res_t sensor_val_res = { + .res_handlers = &res_handlers, + .res_spec = &sensor_val_res_spec +}; +static char units[] = "C"; +static char application_type[20]; +static sdm_res_t resources[3] = { + { + .res_spec = &sensor_val_res_spec, + .res_handlers = &res_handlers + }, + { + .res_spec = &sensor_unit_spec, + .value.res_value.value.bytes_or_string.data = units, + .value.res_value.value.bytes_or_string.chunk_length = sizeof(units) - 1 + }, + { + .res_spec = &sensor_application_type_spec, + .value.res_value.value.bytes_or_string.data = application_type, + .value.res_value.resource_buffer_size = sizeof(application_type) + } +}; +static sdm_obj_inst_t temperature_obj_inst_1 = { + .iid = 0, + .res_count = 3, + .resources = resources +}; +static sdm_obj_inst_t temperature_obj_inst_2 = { + .iid = 1, + .res_count = 1, + .resources = &sensor_val_res +}; + +static sdm_obj_inst_t *temperature_obj_insts[2] = { &temperature_obj_inst_1, + &temperature_obj_inst_2 }; + +static sdm_obj_t temperature_obj = { + .version = "1.1", + .oid = 3303, + .insts = temperature_obj_insts, + .inst_count = 2, + .max_inst_count = 2 +}; + +static int sensor_read_callback(sdm_obj_t *obj, + sdm_obj_inst_t *obj_inst, + sdm_res_t *res, + sdm_res_inst_t *res_inst, + fluf_res_value_t *out_value) { + (void) (obj); + (void) (res); + (void) (res_inst); + + static double sensor_value_1 = 0.0; + static double sensor_value_2 = 2.0; + + if (obj_inst == &temperature_obj_inst_1) { + out_value->double_value = sensor_value_1; + sensor_value_1 += 1.23; + } else if (obj_inst == &temperature_obj_inst_2) { + out_value->double_value = sensor_value_2; + sensor_value_2 *= 2.0; + } else { + return SDM_ERR_BAD_REQUEST; + } + + return 0; +} + +int main(int argc, char *argv[]) { + if (argc != 2) { + printf("No endpoint name given\n"); + return -1; + } + + anjay_lite_t anjay_lite = { + .endpoint_name = argv[1], + .server_conf = { + .binding = FLUF_BINDING_UDP, + .ssid = 1, + .security_mode = ANJAY_SECURITY_NOSEC, + .hostname = "eu.iot.avsystem.cloud", + .port = 5683, + .lifetime = 20, + } + }; + + sdm_device_object_init_t device_obj_conf = { + .firmware_version = "1.0", + .manufacturer = "", + .reboot_handler = NULL, + .serial_number = "12345", + .supported_binding_modes = "U" + }; + + sdm_initialize(&anjay_lite.dm, + anjay_lite.objs_array, + ANJAY_LITE_ALLOWED_OBJECT_NUMBER); + if (sdm_add_obj(&anjay_lite.dm, &temperature_obj)) { + printf("sdm_add_obj error\n"); + return -1; + } + + if (sdm_device_object_install(&anjay_lite.dm, &device_obj_conf)) { + printf("sdm_device_object_install error\n"); + return -1; + } + + if (anjay_lite_init(&anjay_lite)) { + printf("anjay_lite_init error\n"); + return -1; + } + + uint64_t last_send = anj_time_now(); + while (1) { + anjay_lite_process(&anjay_lite); + + /* Generate Send message every 10 sec. */ + if (anj_time_now() > last_send + 10 * 1000) { + fluf_uri_path_t path[] = { FLUF_MAKE_RESOURCE_PATH(3303, 0, 5700) }; + uint8_t payload[50]; + size_t payload_size = sizeof(payload); + + if (!sdm_send_create_msg_from_dm(&anjay_lite.dm, + FLUF_COAP_FORMAT_SENML_CBOR, + payload, + &payload_size, + path, + AVS_ARRAY_SIZE(path))) { + anjay_lite_send(payload, payload_size); + last_send = anj_time_now(); + } + } + usleep(50 * 1000); + } + + return 0; +} diff --git a/include_public/anj/anj_time.h b/include_public/anj/anj_time.h new file mode 100644 index 00000000..10b8fe0b --- /dev/null +++ b/include_public/anj/anj_time.h @@ -0,0 +1,21 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#ifndef ANJ_TIME_H +#define ANJ_TIME_H + +#include + +/** + * @return The number of milliseconds that have elapsed since the system was + * started + */ +uint64_t anj_time_now(void); + +#endif // ANJ_TIME_H diff --git a/include_public/anj/dm.h b/include_public/anj/dm.h new file mode 100644 index 00000000..523b9337 --- /dev/null +++ b/include_public/anj/dm.h @@ -0,0 +1,674 @@ +/* + * Copyright 2023-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_DM_DM_H +#define ANJAY_DM_DM_H + +#include +#include +#include + +#include + +typedef struct dm_object_def_struct dm_object_def_t; + +/** + * Installed object struct. It is used only to allocate memory + * before @ref dm_initialize call. + * + * WARNING: Arrays allocated with this type must remain valid + * throughout the entire usage of the data model. This type is + * not intended for direct user use. + * + * NOTE: refer to description of @ref dm_initialize for more + * information and usage example. + */ +typedef struct { + /** + * Object definition pointer. + */ + const dm_object_def_t *const *def; +} dm_installed_object_t; + +/** data model object which stores registered LwM2M objects. */ +typedef struct dm { + dm_installed_object_t *objects; + size_t objects_count; + size_t objects_count_max; +} dm_t; + +/** + * A handler that enumerates all Object Instances for the Object. + * + * @param dm data model to operate on. + * @param obj_ptr Object definition pointer, as passed to + * @ref dm_register_object . + * @param ctx Context through which the Instance IDs shall be returned, see + * @ref dm_emit . + * + * Instance listing handlers MUST always return Instance IDs in a strictly + * ascending, sorted order. Failure to do so will result in an error being sent + * to the LwM2M server or passed down to internal routines that called this + * handler. + * + * @returns This handler should return: + * - 0 on success, + * - an negative value or one of FLUF_COAP_CODE_ constants in case of error. + */ +typedef int dm_list_instances_t(dm_t *dm, + const dm_object_def_t *const *obj_ptr, + dm_list_ctx_t *ctx); + +/** + * Convenience function to use as the list_instances handler in Single Instance + * objects. + * + * Implements a valid iteration that returns a single Instance ID: 0. + */ +int dm_list_instances_SINGLE(dm_t *dm, + const dm_object_def_t *const *obj_ptr, + dm_list_ctx_t *ctx); + +/** + * A handler that enumerates SUPPORTED Resources for an Object Instance, called + * only if the Object Instance is PRESENT (has recently been returned via + * @ref dm_list_instances_t). + * + * CAUTION: The library MAY call other data model handlers for the same Object + * from within the @ref dm_emit_res call. Please make sure that your code + * is able to handle this - e.g. avoid calling @ref dm_emit_res with + * a non-recursive object-scope mutex locked. + * + * @param dm data model to operate on. + * @param obj_ptr Object definition pointer, as passed to + * @ref dm_register_object . + * @param iid Object Instance ID. + * @param ctx Context through which the Resource IDs shall be returned, see + * @ref dm_emit_res . + * + * Resource listing handlers MUST always return Resource IDs in a strictly + * ascending, sorted order. Failure to do so will result in an error being sent + * to the LwM2M server or passed down to internal routines that called this + * handler. + * + * @returns This handler should return: + * - 0 on success, + * - an negative value or one of FLUF_COAP_CODE_ constants in case of error. + */ +typedef int dm_list_resources_t(dm_t *dm, + const dm_object_def_t *const *obj_ptr, + fluf_iid_t iid, + dm_resource_list_ctx_t *ctx); + +/** + * A handler that reads the Resource or Resource Instance value, called only if + * the Resource is PRESENT and is one of the @ref DM_RES_R, + * @ref DM_RES_RW, @ref DM_RES_RM, @ref DM_RES_RWM or @ref DM_RES_BS_RW kinds + * (as returned by @ref dm_list_resources_t). + * + * @param dm data model to operate on. + * @param obj_ptr Object definition pointer, as passed to + * @ref dm_register_object . + * @param iid Object Instance ID. + * @param rid Resource ID. + * @param riid Resource Instance ID, or @ref FLUF_ID_INVALID in case of a + * Single Resource. + * @param ctx Output context to write the resource value to using the + * dm_ret_* function family. + * + * NOTE: One of the dm_ret_* functions MUST be called + * in this handler before returning successfully. Failure to do so will result + * in 5.00 Internal Server Error being sent to the server. + * + * NOTE: This handler will only be called with @p riid set to a valid value if + * the Resource Instance is PRESENT (has recently been returned via + * @ref dm_list_resource_instances_t). + * + * @returns This handler should return: + * - 0 on success, + * - an negative value or one of FLUF_COAP_CODE_ constants in case of error. + */ +typedef int dm_resource_read_t(dm_t *dm, + const dm_object_def_t *const *obj_ptr, + fluf_iid_t iid, + fluf_rid_t rid, + fluf_riid_t riid, + dm_output_ctx_t *ctx); + +/** + * A handler that writes the Resource value, called only if the Resource is + * SUPPORTED and not of the @ref DM_RES_E kind (as returned by + * @ref dm_list_resources_t). + * + * @param dm data model object to operate on. + * @param obj_ptr Object definition pointer, as passed to + * @ref dm_register_object . + * @param iid Object Instance ID. + * @param rid Resource ID. + * @param riid Resource Instance ID, or @ref FLUF_ID_INVALID in case of a + * Single Resource. + * @param ctx Input context to read the resource value from using the + * dm_get_* function family. + * + * NOTE: This handler will only be called with @p riid set to a valid value if + * the Resource has been verified to be a Multiple Resource (as returned by + * @ref dm_list_resources_t). + * + * @returns This handler should return: + * - 0 on success, + * - an negative value or one of FLUF_COAP_CODE_ constants in case of error. + */ +typedef int dm_resource_write_t(dm_t *dm, + const dm_object_def_t *const *obj_ptr, + fluf_iid_t iid, + fluf_rid_t rid, + fluf_riid_t riid, + dm_input_ctx_t *ctx); + +/** + * A handler that performs the Execute action on given Resource, called only if + * the Resource is PRESENT and of the @ref DM_RES_E kind (as returned by + * @ref dm_list_resources_t). + * + * @param dm data model object to operate on. + * @param obj_ptr Object definition pointer, as passed to + * @ref dm_register_object . + * @param iid Object Instance ID. + * @param rid Resource ID. + * @param ctx Execute context to read the execution arguments from, using + * the dm_execute_get_* function family. + * + * @returns This handler should return: + * - 0 on success, + * - an negative value or one of FLUF_COAP_CODE_ constants in case of error. + */ +typedef int dm_resource_execute_t(dm_t *dm, + const dm_object_def_t *const *obj_ptr, + fluf_iid_t iid, + fluf_rid_t rid, + dm_execute_ctx_t *ctx); + +/** + * A handler that enumerates all Resource Instances of a Multiple Resource, + * called only if the Resource is PRESENT and is of either @ref DM_RES_RM, + * @ref DM_RES_WM or @ref DM_RES_RWM kind (as returned by + * @ref dm_list_resources_t). + * + * The library will not attempt to call @ref dm_resource_write_t inside the + * @ref dm_emit calls performed from this handler, so the implementation + * is free to use iteration state that would be invalidated by such calls. + * + * CAUTION: Aside from the note above, the library MAY call other data model + * handlers for the same Object from within the @ref dm_emit call. Please + * make sure that your code is able to handle this - e.g. avoid calling + * @ref dm_emit with a non-recursive object-scope mutex locked. + * + * @param dm data model object to operate on. + * @param obj_ptr Object definition pointer, as passed to + * @ref register_object . + * @param iid Object Instance ID. + * @param rid Resource ID. + * @param ctx Context through which the Resource Instance IDs shall be + * returned, see @ref dm_emit . + * + * Resource instance listing handlers MUST always return Resource Instance IDs + * in a strictly ascending, sorted order. Failure to do so will result in an + * error being sent to the LwM2M server or passed down to internal routines that + * called this handler. + * + * @returns This handler should return: + * - 0 on success, + * - an negative value or one of FLUF_COAP_CODE_ constants in case of error. + */ +typedef int dm_list_resource_instances_t(dm_t *dm, + const dm_object_def_t *const *obj_ptr, + fluf_iid_t iid, + fluf_rid_t rid, + dm_list_ctx_t *ctx); + +/** A struct containing pointers to Object handlers. */ +typedef struct { + /** + * Enumerate available Object Instances, @ref dm_list_instances_t + * + * Required for every LwM2M operation. + * + * **Must not be NULL.** @ref dm_list_instances_SINGLE can be used + * here. + */ + dm_list_instances_t *list_instances; + + /** + * Enumerate PRESENT Resources in a given Object Instance, + * @ref dm_list_resources_t + * + * Required for every LwM2M operation. + * + * **Must not be NULL.** + */ + dm_list_resources_t *list_resources; + + /** + * Get Resource value, @ref dm_resource_read_t + * + * Required for *LwM2M Read* operation. + * + * Can be NULL if the object does not contain readable resources. + */ + dm_resource_read_t *resource_read; + + /** + * Set Resource value, @ref dm_resource_write_t + * + * Required for *LwM2M Write* operation. + * + * Can be NULL if the object does not contain writable resources. + */ + dm_resource_write_t *resource_write; + + /** + * Perform Execute action on a Resource, @ref dm_resource_execute_t + * + * Required for *LwM2M Execute* operation. + * + * Can be NULL if the object does not contain executable resources. + */ + dm_resource_execute_t *resource_execute; + + /** + * Enumerate available Resource Instances, + * @ref dm_list_resource_instances_t + * + * Required for *LwM2M Read*, *LwM2M Write* and *LwM2M Discover* operations + * performed on multiple-instance resources.. + * + * Can be NULL if the object does not contain multiple resources. + */ + dm_list_resource_instances_t *list_resource_instances; +} dm_handlers_t; + +/** A struct defining a LwM2M Object. */ +struct dm_object_def_struct { + /** Object ID; MUST not be FLUF_ID_INVALID (65535) */ + fluf_oid_t oid; + + /** + * Object version: a string with static lifetime, containing two digits + * separated by a dot (for example: "1.1"). + * If left NULL, client will not include the "ver=" attribute in Register + * and Discover messages. This implies: + * 1. Version 1.0 for Non-Core Objects. + * 2. The version corresponding to the version in the LwM2M Enabler for Core + * Objects. + */ + const char *version; + + /** Handler callbacks for this object. */ + dm_handlers_t handlers; +}; + +/** + * A callback that is called for each resource that is read within a call to + * @ref dm_read . The purpose of this callback is to retrieve values that are + * read from the data model, provided as values of type @ref fluf_io_out_entry_t + * which are supposed to be e.g. copied by the user, and later serialized and + * sent over the network. + * + * arg argument is the context argument that was provided with the call + * to @ref dm_read . + * + * @param arg Opaque pointer to user data. + * @param out_entry Pointer to retrieved data model entry. + * + * @returns 0 on success, a negative value in case of error. + */ +typedef int dm_output_ctx_cb_t(void *arg, fluf_io_out_entry_t *out_entry); + +/** A struct defining a context for data model read operation. */ +struct dm_output_ctx_struct { + /** + * Pointer to callback which provides user with data model entry, + * @ref dm_output_ctx_cb_t. + * + * Required for every @ref dm_read call. + * + * **Must not be NULL.** + */ + dm_output_ctx_cb_t *callback; + + /** + * Pointer to user data which will be passed as first + * argument to callback function. + * + * It is optional, it can be NULL. + */ + void *arg; +}; + +/** + * A callback that is called for each resource that is written within a call to + * @ref dm_write . The purpose of this callback is to provide values that are + * written to the data model, provided as values of type @ref + * fluf_io_out_entry_t which are going to be copied and stored by data model. + * + * This callback is called directly by dm_get_* functions (while handling + * dm_write) to get the @param in_entry value. + * + * WARNING: If @ref fluf_data_type_t in @ref fluf_io_out_entry_t is + * one of @ref FLUF_DATA_TYPE_BYTES, @ref FLUF_DATA_TYPE_STRING, + * @ref FLUF_DATA_TYPE_EXTERNAL_BYTES or @ref FLUF_DATA_TYPE_EXTERNAL_STRING + * it's user responsibility to ensure that any context required by it (data + * behind the pointer or get_external_data with user_args) is + * valid, until the last reference to it. + * + * @param arg Opaque pointer to user data. + * @param out_entry Pointer to place where data model entry should be written. + * + * @returns 0 on success, other value in case of error. + */ +typedef int dm_input_ctx_cb_t(void *arg, + fluf_data_type_t expected_type, + fluf_io_out_entry_t *in_entry); + +/** A struct defining a context for data model write operation. */ +struct dm_input_ctx_struct { + /** + * Pointer to callback which provides user with pointer to place where, + * data model entry should be written, @ref dm_input_ctx_cb_t. + * + * Required for every @ref dm_write call. + * + * **Must not be NULL.** + */ + dm_input_ctx_cb_t *callback; + + /** + * Pointer to user data which will be passed as first + * argument to callback function. + * + * It is optional, it can be NULL. + */ + void *arg; +}; + +/** + * A callback that is called within a call to @ref dm_register_prepare . It is + * called for each present object and object instance. The purpose of this + * callback is to retrieve data sent in LwM2M Register message. + * + * @param arg Opaque pointer to user data. + * @param uri Pointer to URI path of an object or an object instance. + * + * @returns 0 on success, a negative value in case of error. + */ +typedef int dm_register_ctx_cb_t(void *arg, fluf_uri_path_t *uri); + +/** A struct defining a context for data model register operation. */ +struct dm_register_ctx_struct { + /** + * Pointer to callback which is called for every present object and object + * instance, @ref dm_register_ctx_cb_t. + * + * Required for every @ref dm_register_prepare call. + * + * **Must not be NULL.** + */ + dm_register_ctx_cb_t *callback; + + /** + * Pointer to user data which will be passed as first + * argument to callback function. + * + * It is optional, it can be NULL. + */ + void *arg; +}; + +/** + * A callback that is called within a call to @ref dm_discover_resp_prepare . It + * is called for every data model element that should be discovered. The purpose + * of this callback is to retrieve data sent in LwM2M Discover message. + * + * @param arg Opaque pointer to user data. + * @param uri Pointer to URI path of an object or an object instance. + * + * @returns 0 on success, a negative value in case of error. + */ +typedef int dm_discover_ctx_cb_t(void *arg, fluf_uri_path_t *uri); + +/** A struct defining a context for data model discover operation. */ +struct dm_discover_ctx_struct { + /** + * Pointer to callback which is called for every data model element + * that should be discovered, @ref dm_discover_ctx_cb_t. + * + * Required for every @ref dm_discover_resp_prepare call. + * + * **Must not be NULL.** + */ + dm_discover_ctx_cb_t *callback; + + /** + * Pointer to user data which will be passed as first + * argument to callback function. + * + * It is optional, it can be NULL. + */ + void *arg; +}; + +/** + * Initializes the data model module. This function is used to initialize + * @ref dm_t object before any other operation on data model. + * + * Upon successful initialization, there are no registered LwM2M Objects. + * At this point, the data model is ready for object registration and other + * operations. + * + * + * #define MAX_OBJECTS_COUNT 2 + * static dm_t dm; + * static dm_installed_object_t installed_objects[MAX_OBJECTS_COUNT]; + * dm_initialize(&dm, installed_objects, MAX_OBJECTS_COUNT); + * + * + * @param dm data model pointer to operate on. + * @param objects pointer to an array used as a storage for installed objects + * @param max_count maximum number of objects that can be installed. + * @return 0 on success, other value in case of error. + */ +int dm_initialize(dm_t *dm, dm_installed_object_t *objects, size_t max_count); + +/** + * Registers the Object in the data model. + * + * NOTE: def_ptr MUST stay valid up to and including the corresponding + * @ref dm_delete or @ref dm_unregister_object call. + * + * Call to this function with any argument as NULL will result in undefined + * behavior. + * + * @param dm data model pointer to operate on. + * @param def_ptr Pointer to the Object definition struct. The exact value + * passed to this function will be forwarded to all data model + * handler calls. + * @return 0 on success, other value in case of error. + */ +int dm_register_object(dm_t *dm, const dm_object_def_t *const *def_ptr); + +/** + * Unregisters an Object in the data model. + * + * def_ptr MUST be a pointer previously passed to + * @ref dm_register_object for the same dm object. + * + * After a successful unregister, any resources used by the actual object may be + * safely freed up. + * + * NOTE: This function MUST NOT be called from within any data model handler + * callback function (i.e. any of the @ref dm_handlers_t members). Doing + * so is undefined behavior. + * + * Call to this function with any argument as NULL will result in undefined + * behavior. + * + * @param dm data model object to operate on. + * @param def_ptr Pointer to the Object definition struct. + * + * @returns 0 on success, a negative value if def_ptr does not correspond + * to any known registered object. + */ +int dm_unregister_object(dm_t *dm, const dm_object_def_t *const *def_ptr); + +/** + * Read data from data model. During the call to this function, the callback + * @ref dm_output_ctx_cb_t provided by @ref dm_output_ctx_t structure will + * be called for each resource instance that should be retrieved. + * + * Provided URI path can be: root path, object path, object instance path, + * resource path or resource instance path. E.g. when provided URI path is + * a root path, the callback will be called for every resource + * instance in every object instance in every object. + * + * In case when provided URI path is a path to unknown object, unknown resource + * or resource is not present or resource is not readable, the callback will not + * be called and dm_read function will return an error. + * + * Call to this function with any argument as NULL will result in undefined + * behavior. This function must be called with pointer to callback + * function, placed in @ref dm_output_ctx_t struct. This callback can't be NULL. + * + * @param dm data model to operate on. + * @param uri URI path to read. + * @param out_ctx Output context with pointer to callback function and optional + * pointer to user data. + * + * @return + * - 0 on success + * - one of FLUF_COAP_CODE_ constants in case of error. + */ +int dm_read(dm_t *dm, const fluf_uri_path_t *uri, dm_output_ctx_t *out_ctx); + +/** + * Write data to data model. During the call to this function, the callback + * @ref dm_input_ctx_cb_t provided by @ref dm_input_ctx_t structure will + * be called for each resource instance that should be written. + * + * Provided URI path can be: object instance path, resource path or + * resource instance path. In case when provided URI path is a path to unknown + * object, unknown resource or resource is not present or resource is not + * writable, the callback will not be called and dm_write function will return + * an error. + * + * Call to this function with any argument as NULL will result in undefined + * behavior. This function must be called with pointer to callback + * function, placed in @ref dm_input_ctx_t struct. This callback can't be NULL. + * + * @param dm data model to operate on. + * @param uri URI path to write. + * @param in_ctx Input context with pointer to callback function and optional + * pointer to user data. + * + * @return + * - 0 on success + * - one of FLUF_COAP_CODE_ constants in case of error. + */ +int dm_write(dm_t *dm, const fluf_uri_path_t *uri, dm_input_ctx_t *in_ctx); + +/** + * Performs execute operation on an data model's resource pointed by provided + * URI path. + * + * NOTE: Provided URI has to point to resource path. Otherwise this function + * will return FLUF_COAP_CODE_METHOD_NOT_ALLOWED. + * + * Call to this function with any argument as NULL will result in undefined + * behavior. + * + * @param dm data model to operate on. + * @param uri URI path to execute. + * + * @return + * - 0 on success + * - one of FLUF_COAP_CODE_ constants in case of error. + */ +int dm_execute(dm_t *dm, const fluf_uri_path_t *uri); + +/** + * Returns number of resources in a data model provided URI path. + * Counts only readable resources. + * + * Call to this function with any argument as NULL will result in undefined + * behavior. + * + * @param dm data model to operate on. + * @param uri URI path. + * @param out_count pointer writes counted out value. + + * @return + * - 0 on success + * - one of FLUF_COAP_CODE_ constants in case of error. + */ +int dm_get_readable_res_count(dm_t *dm, + fluf_uri_path_t *uri, + size_t *out_count); + +/** + * Prepares data for LwM2M Register message. + * + * During the call to this function, the @ref dm_register_ctx_cb_t callback + * provided by @ref dm_register_ctx_t structure will be called for every + * registered object and present object instance. + * + * Call to this function with any argument as NULL will result in undefined + * behavior. This function must be called with a pointer to callback + * function, placed in @ref dm_register_ctx_t struct. This callback can't be + * NULL. + * + * @param dm data model to operate on. + * @param ctx context with a pointer to callback function and optional pointer + * to user data. + * + * @return + * - 0 on success + * - one of FLUF_COAP_CODE_ constants in case of error. + */ +int dm_register_prepare(dm_t *dm, dm_register_ctx_t *ctx); + +/** + * Prepares data for response to LwM2M Discover message. + * + * During the call to this function, the @ref dm_discover_ctx_cb_t callback + * provided by @ref dm_discover_ctx_t structure will be called for every data + * model element that should be discovered. + * + * Provided URI path can be: object path, object instance path, resource path, + * otherwise behavior is undefined. + * + * Pointer to depth parameter can be NULL, in this case default depth will be + * used. Otherwise, possible values behind the pointer are 0..3. + * + * Call to this function with any argument (other than depth) as NULL will + * result in undefined behavior. This function must be called with a + * pointer to callback function, placed in @ref dm_register_ctx_t struct. This + * callback can't be NULL. + * + * @param dm data model to operate on. + * @param uri URI path to discover. + * @param depth pointer to depth value. + * @param ctx context with a pointer to callback function and optional pointer + * to user data. + * @return + */ +int dm_discover_resp_prepare(dm_t *dm, + fluf_uri_path_t *uri, + const uint8_t *depth, + dm_discover_ctx_t *ctx); + +#endif /*ANJAY_DM_DM_H*/ diff --git a/include_public/anj/dm_io.h b/include_public/anj/dm_io.h new file mode 100644 index 00000000..80fec25f --- /dev/null +++ b/include_public/anj/dm_io.h @@ -0,0 +1,492 @@ +/* + * Copyright 2023-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_DM_DM_IO_H +#define ANJAY_DM_DM_IO_H + +#include +#include +#include + +#include +#include + +typedef struct dm_list_ctx_struct dm_list_ctx_t; +typedef struct dm_resource_list_ctx_struct dm_resource_list_ctx_t; +typedef struct dm_output_ctx_struct dm_output_ctx_t; +typedef struct dm_input_ctx_struct dm_input_ctx_t; +typedef struct dm_execute_ctx_struct dm_execute_ctx_t; +typedef struct dm_register_ctx_struct dm_register_ctx_t; +typedef struct dm_discover_ctx_struct dm_discover_ctx_t; + +/** + * Returns a blob of data from the data model handler. + * + * Note: this should be used only for small, self-contained chunks of data. + * See @ref dm_ret_external_bytes documentation for a recommended method of + * returning large data blobs. + * + * WARNING: The data behind the pointer is not being copied. The pointer is + * passed verbatim to @ref dm_output_ctx_cb_t callback implemented by the + * user and it must stay valid until that time. User is free to either copy + * the data immediately and discard/free the pointer, or take ownership of it, + * which extends the expected lifetime of the pointer further. + * + * @param ctx Context to operate on. + * @param data Data buffer. + * @param data_len Number of bytes available in the @p data buffer. + * + * @returns 0 on success, a negative value in case of error. + */ +int dm_ret_bytes(dm_output_ctx_t *ctx, void *data, size_t data_len); + +/** + * Returns a null-terminated string from the data model handler. + * + * Note: this should be used only for small, self-contained strings. + * See @ref dm_ret_external_string documentation for a recommended method of + * returning large strings. + * + * WARNING: The data behind the pointer is not being copied. The pointer is + * passed verbatim to @ref dm_output_ctx_cb_t callback implemented by the + * user and it must stay valid until that time. User is free to either copy + * the data immediately and discard/free the pointer, or take ownership of it, + * which extends the expected lifetime of the pointer further. + * + * @param ctx Output context to operate on. + * @param value Null-terminated string to return. + * + * @returns 0 on success, a negative value in case of error. + */ +int dm_ret_string(dm_output_ctx_t *ctx, char *value); + +/** + * Returns a blob of data from the data model handler. + * + * NOTE: The difference between this function and @ref dm_ret_bytes is that + * this function allows the data model handler to return large data blobs which + * is realized in multiple chunks AND by user callback that allows the data to + * be not self-contained (i.e. it may be stored in a file, or in a database, + * external memory, etc.). + * + * This method uses a @ref fluf_get_external_data_t callback for multiple + * calls to get the data. The callback is called until the whole data is + * returned. The callback is called with the same @p user_args pointer as passed + * to this function. + * + * WARNING: get_external_data callback is passed to @ref + * dm_output_ctx_cb_t callback and it's user's responsibility to ensure that any + * context required by get_external_data (including user_args + * pointers) remains valid until the last call to it. + * + * @param ctx Output context to operate on. + * @param get_external_data Callback to get the data. + * @param user_args User arguments passed to the callback. + * @param length Length of the data to be returned. + * + * @return 0 in case of success, negative value in case of error. + */ +int dm_ret_external_bytes(dm_output_ctx_t *ctx, + fluf_get_external_data_t *get_external_data, + void *user_args, + size_t length); + +/** + * Returns a null-terminated string from the data model handler. + * + * NOTE: The difference between this function and @ref dm_ret_string is that + * this function allows the data model handler to return large strings which + * is realized in multiple chunks AND by user callback thst allows the data to + * be not self-contained (i.e. it may be stored in a file, or in a database, + * external memory, etc.). + * + * This method uses a @ref fluf_get_external_data_t callback for multiple + * calls to get the data. The callback is called until the whole data is + * returned. The callback is called with the same @p user_args pointer as passed + * to this function. + * + * WARNING: get_external_data callback is passed to @ref + * dm_output_ctx_cb_t callback and it's user's responsibility to ensure that any + * context required by get_external_data (including user_args + * pointers) remains valid until the last call to it. + * + * @param ctx Output context to operate on. + * @param get_external_data Callback to get the data. + * @param user_args User arguments passed to the callback. + * @param length Length of the data to be returned. + * + * @return 0 in case of success, negative value in case of error. + */ +int dm_ret_external_string(dm_output_ctx_t *ctx, + fluf_get_external_data_t *get_external_data, + void *user_args, + size_t length); + +/** + * Returns a 64-bit signed integer from the data model handler. + * + * @param ctx Output context to operate on. + * @param value The value to return. + * + * @returns 0 on success, a negative value in case of error. + */ +int dm_ret_i64(dm_output_ctx_t *ctx, int64_t value); + +/** + * Returns a 64-bit floating-point value from the data model handler. + * + * @param ctx Output context to operate on. + * @param value The value to return. + * + * @returns 0 on success, a negative value in case of error. + */ +int dm_ret_double(dm_output_ctx_t *ctx, double value); + +/** + * Returns a boolean value from the data model handler. + * + * @param ctx Output context to operate on. + * @param value The value to return. + * + * @returns 0 on success, a negative value in case of error. + */ +int dm_ret_bool(dm_output_ctx_t *ctx, bool value); + +/** + * Returns a object link (Object ID/Instance ID pair) from the + * data model handler. + * + * @param ctx Output context to operate on. + * @param oid Object ID part of the link. + * @param iid Object Instance ID part of the link. + * + * @returns 0 on success, a negative value in case of error. + */ +int dm_ret_objlnk(dm_output_ctx_t *ctx, fluf_oid_t oid, fluf_iid_t iid); + +/** + * Returns a 64-bit unsigned integer from the data model handler. + * + * @param ctx Output context to operate on. + * @param value The value to return. + * + * @returns 0 on success, a negative value in case of error. + */ +int dm_ret_u64(dm_output_ctx_t *ctx, uint64_t value); + +/** + * Returns a time value from the data model handler. It is 64-bit signed integer + * Unix Time, representing the number of seconds since Jan 1st, 1970 in the UTC + * time zone. + * + * @param ctx Output context to operate on. + * @param value The value to return. + * + * @returns 0 on success, a negative value in case of error. + */ +int dm_ret_time(dm_output_ctx_t *ctx, uint64_t time); + +/** + * Reads a chunk of data blob from the @ref dm_write operation. + * + * Consecutive calls to this function will return successive chunks of + * the data blob. Reaching end of the data is signaled by setting the + * @p out_message_finished flag. + * + * A call to this function will always attempt to read as much data as possible. + * + * Example: writing a large data blob to file. + * + * @code + * FILE *file; + * // initialize file + * + * bool finished; + * size_t bytes_read; + * char buf[1024]; + * + * do { + * if (dm_get_bytes(ctx, &bytes_read, &finished, buf, sizeof(buf)) + * || fwrite(buf, 1, bytes_read, file) < bytes_read) { + * // handle error + * } + * } while (!finished); + * + * @endcode + * + * @param ctx Input context to operate on. + * @param[out] out_bytes_read Number of bytes read. + * @param[out] out_message_finished Set to true if there is no more data + * to read. + * @param[out] out_buf Buffer to read data into. + * @param buf_size Number of bytes available in @p out_buf . + * + * @returns 0 on success, a negative value in case of error. + */ +int dm_get_bytes(dm_input_ctx_t *ctx, + size_t *out_bytes_read, + bool *out_message_finished, + void *out_buf, + size_t buf_size); + +#define DM_BUFFER_TOO_SHORT 1 +/** + * Reads a null-terminated string from @ref dm_write operation. On success or + * even when @ref DM_BUFFER_TOO_SHORT is returned, the content inside @p out_buf + * is always null-terminated. On failure, the contents of @p out_buf are + * undefined. + * + * When the input buffer is not big enough to contain whole message content + + * terminating nullbyte, DM_BUFFER_TOO_SHORT is returned, after which further + * calls can be made, to retrieve more data. + * + * @param ctx Input context to operate on. + * @param[out] out_buf Buffer to read data into. + * @param buf_size Number of bytes available in @p out_buf . + * Must be at least 1. + * + * @returns 0 on success, a negative value in case of error, + * @ref DM_BUFFER_TOO_SHORT if the buffer is not big enough to + * contain whole message content + terminating nullbyte. + */ +int dm_get_string(dm_input_ctx_t *ctx, char *out_buf, size_t buf_size); + +/** + * Reads a chunk of data blob from the @ref dm_write operation. The data is + * returned by calling the @p fluf_get_external_data_t callback. + * + * The difference between this function and @ref dm_get_bytes is that this + * function allows to read from @ref dm_write operation large data blobs which + * is realized in multiple chunks AND could be stored outside of data model + * (i.e. in a file, or in a database, external memory, etc.). + * + * During the call to this function, @param out_get_external_data will be set to + * the callback function, which then should be called by the user to get the + * data. The callback is called until the whole data is returned. + * + * NOTE: Call to this function will set @param out_user_args which HAS TO be + * passed to the callback function. + * + * @param ctx Context to operate on. + * @param out_get_external_data Pointer to which set the internal callback. + * @param out_user_args Pointer to arguments to be passed to the + * callback. + * @param out_length Pointer to which set the length of the data to + * be returned. + * + * @return 0 in case of success, negative value in case of error. + */ +int dm_get_external_bytes(dm_input_ctx_t *ctx, + fluf_get_external_data_t **out_get_external_data, + void **out_user_args, + size_t *out_length); + +/** + * Reads a null-terminated string from @ref dm_write operation. The data is + * returned by calling the @p fluf_get_external_data_t callback. + * + * The difference between this function and @ref dm_get_string is that this + * function allows to read from @ref dm_write operation large strings which + * is realized in multiple chunks AND could be stored outside of data model + * (i.e. in a file, or in a database, external memory, etc.). + * + * During the call to this function, @param out_get_external_data will be set to + * the callback function, which then should be called by the user to get the + * data. The callback is called until the whole data is returned. + * + * NOTE: Call to this function will set @param out_user_args which HAS TO be + * passed to the callback function. + * + * @param ctx Context to operate on. + * @param out_get_external_data Pointer to which set the internal callback. + * @param out_user_args Pointer to arguments to be passed to the + * callback. + * @param out_length Pointer to which set the length of the data to + * be returned. + * + * @return 0 in case of success, negative value in case of error. + */ +int dm_get_external_string(dm_input_ctx_t *ctx, + fluf_get_external_data_t **out_get_external_data, + void **out_user_args, + size_t *out_length); + +/** + * Reads an integer as a 64-bit signed value from the @ref dm_write operation. + * + * @param ctx Input context to operate on. + * @param[out] out Returned value. If the call is not successful, it is + * guaranteed to be left untouched. + * + * @returns 0 on success, a negative value in case of error. + */ +int dm_get_i64(dm_input_ctx_t *ctx, int64_t *out); + +/** + * Reads an unsigned integer as a 32-bit unsigned value from the @ref + * dm_write operation. + * + * @param ctx Input context to operate on. + * @param[out] out Returned value. If the call is not successful, it is + * guaranteed to be left untouched. + * + * @returns 0 on success, a negative value in case of error. + */ +int dm_get_u32(dm_input_ctx_t *ctx, uint32_t *out); + +/** + * Reads a floating-point value as a double from the @ref dm_write operation. + * + * @param ctx Input context to operate on. + * @param[out] out Returned value. If the call is not successful, it is + * guaranteed to be left untouched. + * + * @returns 0 on success, a negative value in case of error. + */ +int dm_get_double(dm_input_ctx_t *ctx, double *out); + +/** + * Reads a boolean value from the @ref dm_write operation. + * + * @param ctx Input context to operate on. + * @param[out] out Returned value. If the call is not successful, it is + * guaranteed to be left untouched. + * + * @returns 0 on success, a negative value in case of error. + */ +int dm_get_bool(dm_input_ctx_t *ctx, bool *out); + +/** + * Reads an object link (Object ID/Object Instance ID pair) from the @ref + * dm_write operation. + * + * @param ctx Input context to operate on. + * @param[out] out_oid Object ID part of the returned value. + * @param[out] out_iid Object Instance ID part of the returned value. + * + * @returns 0 on success, a negative value in case of error. + * + * In case of error, out_oid and out_iid are guaranteed to be left + * untouched. + */ +int dm_get_objlnk(dm_input_ctx_t *ctx, + fluf_oid_t *out_oid, + fluf_iid_t *out_iid); + +/** + * Reads an unsigned integer as a 64-bit unsigned value from the @ref dm_write + * operation. + * + * @param ctx Input context to operate on. + * @param[out] out Returned value. If the call is not successful, it is + * guaranteed to be left untouched. + * + * @returns 0 on success, a negative value in case of error. + */ +int dm_get_u64(dm_input_ctx_t *ctx, uint64_t *out); + +/** + * Reads a time value from the @ref dm_write operation. It is 64-bit signed + * integer Unix Time, representing the number of seconds since Jan 1st, 1970 in + * the UTC time zone. + * + * @param ctx Input context to operate on. + * @param[out] out Returned value. If the call is not successful, it is + * guaranteed to be left untouched. + * + * @returns 0 on success, a negative value in case of error. + */ +int dm_get_time(dm_input_ctx_t *ctx, int64_t *time); + +/** + * Used to return entries from @ref dm_list_instances_t or + * @ref dm_list_resource_instances_t . + * + * @param ctx Context passed to the iteration handler. + * @param id ID of the returned Object Instance or Resource Instance. MUST NOT + * be FLUF_ID_INVALID / FLUF_ID_INVALID (65535). + * + * This function returns no value. Any errors that may occur are handled + * internally by the library after the calling handler returns. + */ +void dm_emit(dm_list_ctx_t *ctx, uint16_t id); + +/** + * Kind of a Resource. + */ +typedef enum { + /** + * Read-only Single-Instance Resource. + */ + DM_RES_R, + + /** + * Write-only Single-Instance Resource. + */ + DM_RES_W, + + /** + * Read/Write Single-Instance Resource. + */ + DM_RES_RW, + + /** + * Read-only Multiple Instance Resource. + */ + DM_RES_RM, + + /** + * Write-only Multiple Instance Resource. + */ + DM_RES_WM, + + /** + * Read/Write Multiple Instance Resource. + */ + DM_RES_RWM, + + /** + * Executable Resource. + */ + DM_RES_E +} dm_resource_kind_t; + +/** + * Resource presentness flag. + */ +typedef enum { + /** + * Resource that is absent. + */ + DM_RES_ABSENT = 0, + + /** + * Resource that is present. + */ + DM_RES_PRESENT = 1 +} dm_resource_presence_t; + +/** + * Used to return Resource entries from @ref dm_list_resources_t . + * + * @param ctx Context passed to the iteration handler. + * @param rid ID of the returned Resource. MUST NOT be + * FLUF_ID_INVALID (65535). + * @param kind Kind of the returned Resource. + * @param presence Flag that indicates whether the Resource is PRESENT. + * + * This function returns no value. Any errors that may occur are handled + * internally by the library after the calling handler returns. + */ +void dm_emit_res(dm_resource_list_ctx_t *ctx, + fluf_rid_t rid, + dm_resource_kind_t kind, + dm_resource_presence_t presence); + +#endif // ANJAY_DM_DM_IO_H diff --git a/include_public/anj/sdm.h b/include_public/anj/sdm.h new file mode 100644 index 00000000..367069c8 --- /dev/null +++ b/include_public/anj/sdm.h @@ -0,0 +1,295 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#ifndef SDM_H +#define SDM_H + +#include +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * There is no more data to read from data model. + * This value can be returned by: + * - @ref sdm_get_read_entry + * - @ref sdm_get_composite_read_entry + * - @ref sdm_get_register_record + * - @ref sdm_get_discover_record + * - @ref sdm_get_bootstrap_discover_record + */ +#define SDM_LAST_RECORD 1 + +/** + * A group of error codes resulting from incorrect API usage or memory + * issues. If this occurs, the @ref FLUF_COAP_CODE_INTERNAL_SERVER_ERROR should + * be returned in response. + */ + +/** Invalid input arguments. */ +#define SDM_ERR_INPUT_ARG (-1) +/** Not enough space in buffer or array. */ +#define SDM_ERR_MEMORY (-2) +/** Invalid call. */ +#define SDM_ERR_LOGIC (-3) +/** Currently unsupported. */ +#define SDM_ERR_CURRENTLY_UNSUPPORTED (-4) + +/** + * Must be called at the beginning of each operation on the data model. It is to + * be called only once, even if the message is divided into several blocks. + * Data model operations are: + * - FLUF_OP_REGISTER, + * - FLUF_OP_UPDATE, + * - FLUF_OP_DM_READ, + * - FLUF_OP_DM_READ_COMP, + * - FLUF_OP_DM_DISCOVER, + * - FLUF_OP_DM_WRITE_REPLACE, + * - FLUF_OP_DM_WRITE_PARTIAL_UPDATE, + * - FLUF_OP_DM_WRITE_COMP, + * - FLUF_OP_DM_EXECUTE, + * - FLUF_OP_DM_CREATE, + * - FLUF_OP_DM_DELETE. + * + * @param dm Data model to operate on. + * @param operation Data model operation type. + * @param is_bootstrap_request Indicate source of request. + * @param path Path from the request, if not specified should + * be NULL. + * + * @returns + * - 0 on success, + * - a negative value in case of error. + */ +int sdm_operation_begin(sdm_data_model_t *dm, + fluf_op_t operation, + bool is_bootstrap_request, + const fluf_uri_path_t *path); + +/** + * Called at the end of each operation on the data model. If during + * operation any function returns error value this function must be called + * immediately. + * + * @param dm Data model to operate on. + * + * @returns + * - 0 on success, + * - a negative value in case of error. + */ +int sdm_operation_end(sdm_data_model_t *dm); + +/** + * Processes READ and BOOTSTRAP-READ operation. Should be repeatedly called + * until it returns the @ref SDM_LAST_RECORD. Returns all @ref SDM_RES_R, @ref + * SDM_RES_RW (and @ref SDM_RES_BS_RW for Bootstrap call) Resources/ Resource + * Instances from path given in @ref sdm_operation_begin. + * + * @param dm Data model to operate on. + * @param[out] out_record Resource or Resource Instance record, with defined + * type, value and path. + * + * @returns + * - 0 on success, + * - @ref SDM_LAST_RECORD when last record was read, + * - a negative value in case of error. + */ +int sdm_get_read_entry(sdm_data_model_t *dm, fluf_io_out_entry_t *out_record); + +/** + * Returns information about the number of Resources and Resource Instances that + * can be read for the READ operation currently in progress. + * + * IMPORTANT: Call this function only after a successful @ref + * sdm_operation_begin call for @ref FLUF_OP_DM_READ or @ref + * FLUF_OP_DM_READ_COMP operation. + * + * @param dm Data model to operate on. + * @param[out] out_res_count Return number of the readable Resources. + * + * @returns + * - 0 on success, + * - a negative value in case of error. + */ +int sdm_get_readable_res_count(sdm_data_model_t *dm, size_t *out_res_count); + +/** + * Processes READ-COMPOSITE operation. For each record from the request should + * be called until it returns the @ref SDM_LAST_RECORD. Returns all @ref + * SDM_RES_R and @ref SDM_RES_RW Resources/ Resource Instances from given @p + * path. + * + * @param dm Data model to operate on. + * @param path Target Object, Object Instance, Resource, + * or Resource Instance path. + * @param[out] out_record Resource or Resource Instance record, with defined + * type, value and path. + * + * @returns + * - 0 on success, + * - @ref SDM_LAST_RECORD when last record was read, + * - a negative value in case of error. + */ +int sdm_get_composite_read_entry(sdm_data_model_t *dm, + const fluf_uri_path_t *path, + fluf_io_out_entry_t *out_record); + +/** + * Returns information about the number of Resources and Resource Instances that + * can be read from @p path. Use it in order to process READ-COMPOSITE + * operation. + * + * IMPORTANT: Call this function only after a successful @ref + * sdm_operation_begin call for @ref FLUF_OP_DM_READ_COMP operation. + * + * @param dm Data model to operate on. + * @param path Target uri path. + * @param[out] out_res_count Return number of the readable Resources. + * + * @returns + * - 0 on success, + * - a negative value in case of error. + */ +int sdm_get_composite_readable_res_count(sdm_data_model_t *dm, + const fluf_uri_path_t *path, + size_t *out_res_count); + +/** + * Adds another record during any kind of WRITE and CREATE operation. Depending + * on the value of @ref sdm_op_t we specified when calling @ref + * sdm_operation_begin this function can be used to handle the following + * operations: + * - FLUF_OP_DM_WRITE_REPLACE, + * - FLUF_OP_DM_WRITE_PARTIAL_UPDATE, + * - FLUF_OP_DM_WRITE_COMP, + * - FLUF_OP_DM_CREATE. + * + * @param dm Data model to operate on. + * @param record Resource or Resource Instance record, with defined type, value + * and path. + * + * @returns + * - 0 on success, + * - a negative value in case of error. + */ +int sdm_write_entry(sdm_data_model_t *dm, fluf_io_out_entry_t *record); + +/** + * Returns information Resource value type, might be useful when payload format + * does not contain information about the type of data. + * + * @param dm Data model to operate on. + * @param path Resource or Resource Instance path. + * @param[out] out_type Resource or Resource Instance value type. + * + * @returns + * - 0 on success, + * - a negative value in case of error. + */ +int sdm_get_resource_type(sdm_data_model_t *dm, + const fluf_uri_path_t *path, + fluf_data_type_t *out_type); + +/** + * Processes REGISTER operation. Should be repeatedly called until it returns + * the @ref SDM_LAST_RECORD. Provides information about Objects and Object + * Instances of the data model. + * + * @param dm Data model to operate on. + * @param[out] out_path Object or Object Instance path. + * @param[out] out_version Object version, provided for Object @p out_path, set + * to NULL if not present. + * + * @returns + * - 0 on success, + * - @ref SDM_LAST_RECORD when last record was read, + * - a negative value in case of error. + */ +int sdm_get_register_record(sdm_data_model_t *dm, + fluf_uri_path_t *out_path, + const char **out_version); + +/** + * Processes DISCOVER operation. Should be repeatedly called until it returns + * the @ref SDM_LAST_RECORD. Provides all elements of the data model + * included in path specified in @ref sdm_operation_begin. + * + * @param dm Data model to operate on. + * @param[out] out_path Object, Object Instance, Resource or Resource + * Instance path. + * @param[out] out_version Object version, provided for Object @p out_path, set + * to NULL if not present. + * @param[out] out_dim Relevant only if @p out_path is Resource, contains + * the number of the Resource Instances. For + * Single-Instance Resources set to NULL. + * + * @returns + * - 0 on success, + * - @ref SDM_LAST_RECORD when last record was read, + * - a negative value in case of error. + */ +int sdm_get_discover_record(sdm_data_model_t *dm, + fluf_uri_path_t *out_path, + const char **out_version, + const uint16_t **out_dim); + +/** + * Processes BOOTSTRAP-DISCOVER operation. Should be repeatedly called until it + * returns the @ref SDM_LAST_RECORD. Provides all elements of the data model + * included in path specified in @ref sdm_operation_begin. + * + * @param dm Data model to operate on. + * @param[out] out_path Object, Object Instance, Resource or Resource + * Instance path. + * @param[out] out_version Object version, provided for Object @p out_path, set + * to NULL if not present. + * @param[out] out_ssid Short server ID of Object Instance, relevant for + * Security, OSCORE and Servers Object Instances. Set to + * NULL if not present. + * @param[out] out_uri Server URI relevant for Security Object Instances. + * Set to NULL if not present. + * + * @returns + * - 0 on success, + * - @ref SDM_LAST_RECORD when last record was read, + * - a negative value in case of error. + */ +int sdm_get_bootstrap_discover_record(sdm_data_model_t *dm, + fluf_uri_path_t *out_path, + const char **out_version, + const uint16_t **ssid, + const char **uri); +/** + * Processes EXECUTE operation, on the Resource pointed to by path specified in + * @ref sdm_operation_begin. If there is a payload in the request then pass it + * through @p execute_arg. + * + * @param dm Data model to operate on. + * @param execute_arg Payload provided in EXECUTE request, set to NULL if + * not present in COAP request. + * @param execute_arg_len Execute payload length. + * + * @returns + * - 0 on success, + * - a negative value in case of error. + */ +int sdm_execute(sdm_data_model_t *dm, + const char *execute_arg, + size_t execute_arg_len); + +#ifdef __cplusplus +} +#endif + +#endif // SDM_H diff --git a/include_public/anj/sdm_device_object.h b/include_public/anj/sdm_device_object.h new file mode 100644 index 00000000..00ef1fea --- /dev/null +++ b/include_public/anj/sdm_device_object.h @@ -0,0 +1,84 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#ifndef CORE_OBJECTS_SDM_DEVICE +#define CORE_OBJECTS_SDM_DEVICE + +#include + +/** + * /3/0/11 resource error codes. + */ +typedef enum { + SDM_DEVICE_OBJ_ERR_CODE_NO_ERROR = 0, + SDM_DEVICE_OBJ_ERR_CODE_LOW_BATTERY_POWER = 1, + SDM_DEVICE_OBJ_ERR_CODE_EXT_POWER_SUPPLY_OFF = 2, + SDM_DEVICE_OBJ_ERR_CODE_GPS_MODULE_FAILURE = 3, + SDM_DEVICE_OBJ_ERR_CODE_LOW_RECV_SIGNAL_STRENGTH = 4, + SDM_DEVICE_OBJ_ERR_CODE_OUT_OF_MEMORY = 5, + SDM_DEVICE_OBJ_ERR_CODE_SMS_FAILURE = 6, + SDM_DEVICE_OBJ_ERR_CODE_IP_CONN_FAILURE = 7, + SDM_DEVICE_OBJ_ERR_CODE_PERIPHERAL_MALFUNCTION = 8, +} sdm_device_obj_err_code_t; + +/** + * Device object initialization structure. Should be filled before passing to + * @ref sdm_device_object_install . + */ +typedef struct { + // /3/0/0 resource value, optional + char *manufacturer; + // /3/0/1 resource value, optional + char *model_number; + // /3/0/2 resource value, optional + char *serial_number; + // /3/0/3 resource value, optional + char *firmware_version; + // /3/0/4 resource callback, mandatory + // if not set, execute on /3/0/4 (reboot) resource will fail + sdm_res_execute_t *reboot_handler; + // 3/0/16 resource value, mandatory + // possible values: U (UDP), M (MQTT), H (HTTP), T (TCP), S (SMS), + // N (Non-IP) + char *supported_binding_modes; +} sdm_device_object_init_t; + +/** + * Installs device object (/3) in SDM. + * + * Example usage: + * @code + * static int reboot_cb(sdm_obj_t *obj, sdm_obj_inst_t *obj_inst, + * sdm_res_t *res, const char *execute_arg, + * size_t execute_arg_len) { + * // perform reboot + * } + * ... + * sdm_device_object_init_t dev_obj_init = { + * .manufacturer = "manufacturer", + * .model_number = "model_number", + * .serial_number = "serial_number", + * .firmware_version = "firmware_version", + * .reboot_handler = reboot_cb + * }; + * sdm_device_object_install(&dm, &dev_obj_init); + * @endcode + * + * @param dm Pointer to a SDM object. Must be non-NULL. + * + * @param obj_init Pointer to a device object's initialization structure. Must + * be non-NULL. + * + * @returns non-zero value on error (i.e. object is already installed), + * 0 otherwise. + */ +int sdm_device_object_install(sdm_data_model_t *dm, + sdm_device_object_init_t *obj_init); + +#endif // CORE_OBJECTS_SDM_DEVICE diff --git a/include_public/anj/sdm_fw_update.h b/include_public/anj/sdm_fw_update.h new file mode 100644 index 00000000..5230f2b1 --- /dev/null +++ b/include_public/anj/sdm_fw_update.h @@ -0,0 +1,298 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#ifndef SDM_FW_UPDATE_H +#define SDM_FW_UPDATE_H + +#include +#include + +#include + +enum sdm_fw_update_protocols { + SDM_FW_UPDATE_PROTOCOL_COAP = (1 << 0), + SDM_FW_UPDATE_PROTOCOL_COAPS = (1 << 1), + SDM_FW_UPDATE_PROTOCOL_HTTP = (1 << 2), + SDM_FW_UPDATE_PROTOCOL_HTTPS = (1 << 3), + SDM_FW_UPDATE_PROTOCOL_COAP_TCP = (1 << 4), + SDM_FW_UPDATE_PROTOCOL_COAP_TLS = (1 << 5) +}; + +/** + * Numeric values of the Firmware Update Result resource. See LwM2M + * specification for details. + * + * Note: they SHOULD only be used with @ref + * sdm_fw_update_object_set_update_result . + */ +typedef enum { + SDM_FW_UPDATE_RESULT_INITIAL = 0, + SDM_FW_UPDATE_RESULT_SUCCESS = 1, + SDM_FW_UPDATE_RESULT_NOT_ENOUGH_SPACE = 2, + SDM_FW_UPDATE_RESULT_OUT_OF_MEMORY = 3, + SDM_FW_UPDATE_RESULT_CONNECTION_LOST = 4, + SDM_FW_UPDATE_RESULT_INTEGRITY_FAILURE = 5, + SDM_FW_UPDATE_RESULT_UNSUPPORTED_PACKAGE_TYPE = 6, + SDM_FW_UPDATE_RESULT_INVALID_URI = 7, + SDM_FW_UPDATE_RESULT_FAILED = 8, + SDM_FW_UPDATE_RESULT_UNSUPPORTED_PROTOCOL = 9, +} sdm_fw_update_result_t; + +/** + * Initates the Push-mode download of FW package. The library calls this + * function when an LwM2M Server performs a Write on Package resource and is + * immediately followed with series of + * @ref sdm_fw_update_package_write_t callback calls that pass the actual binary + * data of the downloaded image if it returns 0. + * + * @param user_ptr Opaque pointer to user data, as passed to + * @ref sdm_fw_update_object_install + * + * @returns The callback shall return 0 or @ref SDM_FW_UPDATE_RESULT_SUCCESS if + * successful or an appropriate reason for the write failure: + * @ref SDM_FW_UPDATE_RESULT_NOT_ENOUGH_SPACE + * @ref SDM_FW_UPDATE_RESULT_OUT_OF_MEMORY + * @ref SDM_FW_UPDATE_RESULT_CONNECTION_LOST + */ +typedef sdm_fw_update_result_t +sdm_fw_update_package_write_start_t(void *user_ptr); + +/** + * Passes the binary data written by an LwM2M Server to the Package resource + * in chunks as they come in a block transfer. If it returns a non-zero value, + * it is set as Result resource and subsequent chunks comming from the server + * are rejected. + * + * @param user_ptr Opaque pointer to user data, as passed to + * @ref sdm_fw_update_object_install + * + * @returns The callback shall return 0 or @ref SDM_FW_UPDATE_RESULT_SUCCESS if + * successful or an appropriate reason for the write failure: + * @ref SDM_FW_UPDATE_RESULT_NOT_ENOUGH_SPACE + * @ref SDM_FW_UPDATE_RESULT_OUT_OF_MEMORY + * @ref SDM_FW_UPDATE_RESULT_CONNECTION_LOST + */ +typedef sdm_fw_update_result_t +sdm_fw_update_package_write_t(void *user_ptr, void *data, size_t data_size); + +/** + * Finitializes the operation of writing FW package chunks. + * The library informs the application that the last call of @ref + * sdm_fw_update_package_write_t was the final one. If this function returns 0, + * FOTA State Machine goes to Downloaded state and waits for LwM2M Server to + * execute Update resource. + * + * @param user_ptr Opaque pointer to user data, as passed to + * @ref sdm_fw_update_object_install + * + * @returns The callback shall return 0 or @ref SDM_FW_UPDATE_RESULT_SUCCESS if + * successful or an appropriate reason for the write failure: + * @ref SDM_FW_UPDATE_RESULT_NOT_ENOUGH_SPACE + * @ref SDM_FW_UPDATE_RESULT_OUT_OF_MEMORY + * @ref SDM_FW_UPDATE_RESULT_CONNECTION_LOST + * @ref SDM_FW_UPDATE_RESULT_INTEGRITY_FAILURE + */ +typedef sdm_fw_update_result_t +sdm_fw_update_package_write_finish_t(void *user_ptr); + +/** + * Informs the application that an LwM2M Server initiated FOTA in Pull mode by + * writing Package URI Resource. If this function return 0, library goes into + * Downloading state and waits for + * @ref sdm_fw_update_object_set_download_result call. + * + * Download abort with a '\0' write to Package URI is handled internally and + * other callback, + * @ref sdm_fw_update_cancel_download_t is called then. + * + * @param user_ptr Opaque pointer to user data, as passed to + * @ref sdm_fw_update_object_install + * @param uri Null-terminated string containing the URI of FW package + * + * @returns The callback shall return 0 or @ref SDM_FW_UPDATE_RESULT_SUCCESS if + * successful or an appropriate reason for the write failure: + * @ref SDM_FW_UPDATE_RESULT_UNSUPPORTED_PACKAGE_TYPE + * @ref SDM_FW_UPDATE_RESULT_INVALID_URI + * @ref SDM_FW_UPDATE_RESULT_UNSUPPORTED_PROTOCOL + */ +typedef sdm_fw_update_result_t sdm_fw_update_uri_write_t(void *user_ptr, + const char *uri); + +/** + * Informs the application that an LwM2M Server aborted FOTA with an empty write + * to Package resoource or er empty string write to Package URI resource. + * + * @param user_ptr Opaque pointer to user data, as passed to + * @ref sdm_fw_update_object_install + * @param uri Null-terminated string containing the URI of FW package + */ +typedef void sdm_fw_update_cancel_download_t(void *user_ptr, const char *uri); + +/** + * Performs the actual upgrade with previously downloaded package. + * + * Will be called at request of the server, after a package has been downloaded. + * + * Most users will want to implement firmware update in a way that involves a + * reboot. In such case, it is expected that this callback will do either one of + * the following: + * + * - perform firmware upgrade, terminate outermost event loop and return, + * call reboot + * - perform the firmware upgrade internally and then reboot, it means that + * the return will never happen (although the library won't be able to send + * the acknowledgement to execution of Update resource) + * + * Regardless of the method, the Update result should be set with a call to + * @ref sdm_fw_update_object_set_update_result + * + * @param user_ptr Opaque pointer to user data, as passed to + * @ref sdm_fw_update_object_install + * + * @returns 0 for success, non-zero for an internal failure (Result resource + * will be set to SDM_FW_UPDATE_RESULT_FAILED). + */ +typedef sdm_fw_update_result_t sdm_fw_update_update_start_t(void *user_ptr); + +/** + * Returns the name of downloaded firmware package. + * + * The name will be exposed in the data model as the PkgName Resource. If this + * callback returns NULL or is not implemented at all (with the + * corresponding field set to NULL), PkgName Resource will contain an + * empty string + * + * It only makes sense for this handler to return non-NULL values if + * there is a valid package already downloaded. The library will not call this + * handler in any state other than Downloaded. + * + * The library will not attempt to deallocate the returned pointer. User code + * must assure that the pointer will remain valid. + * + * @param user_ptr Opaque pointer to user data, as passed to + * @ref sdm_fw_update_object_install + * + * @returns The callback shall return a pointer to a null-terminated string + * containing the package name, or NULL if it is not currently + * available. + */ +typedef const char *sdm_fw_update_get_name_t(void *user_ptr); + +/** + * Returns the version of downloaded firmware package. + * + * The version will be exposed in the data model as the PkgVersion Resource. If + * this callback returns NULL or is not implemented at all (with the + * corresponding field set to NULL), PkgVersion Resource will contain an + * empty string + * + * It only makes sense for this handler to return non-NULL values if + * there is a valid package already downloaded. The library will not call this + * handler in any state other than Downloaded. + * + * The library will not attempt to deallocate the returned pointer. User code + * must assure that the pointer will remain valid. + * + * @param user_ptr Opaque pointer to user data, as passed to + * @ref sdm_fw_update_object_install + * + * @returns The callback shall return a pointer to a null-terminated string + * containing the package version, or NULL if it is not + * currently available. + */ +typedef const char *sdm_fw_update_get_version_t(void *user_ptr); + +/** + * Resets the firmware update state and performs any applicable cleanup of + * temporary storage if necessary. + * + * Will be called at request of the server, or after a failed download. Note + * that it may be called without previously calling + * @ref anjay_fw_update_stream_finish_t, so it shall also close the currently + * open download stream, if any. + * + * @param user_ptr Opaque pointer to user data, as passed to + * @ref sdm_fw_update_object_install + */ +typedef void sdm_fw_update_reset_t(void *user_ptr); + +typedef struct { + sdm_fw_update_package_write_start_t *package_write_start_handler; + sdm_fw_update_package_write_t *package_write_handler; + sdm_fw_update_package_write_finish_t *package_write_finish_handler; + sdm_fw_update_uri_write_t *uri_write_handler; + sdm_fw_update_cancel_download_t *cancel_download_handler; + sdm_fw_update_update_start_t *update_start_handler; + sdm_fw_update_get_name_t *get_name; + sdm_fw_update_get_version_t *get_version; + sdm_fw_update_reset_t *reset_handler; +} sdm_fw_update_handlers_t; + +/** + * Installs the Firmware Update object in an SDM. + * + * @param dm Data Model context where FW Update Object shall be + * installed + * + * @param handlers Pointer to a set of handler functions that handle + * the platform-specific part of firmware update + * process. Note: Contents of the structure are NOT + * copied, so it needs to remain valid for the + * lifetime of the object. + * + * @param user_ptr Opaque user pointer that will be passed as the + * first argument to handler functions. + * + * @param supported_protocols bitmap set according to + * @ref sdm_fw_update_protocols + * + * @return 0 in case of success, negative value in case of error + */ +int sdm_fw_update_object_install(sdm_data_model_t *dm, + sdm_fw_update_handlers_t *handlers, + void *user_ptr, + uint8_t supported_protocols); + +/** + * Sets the result of FW update triggered with /5/0/2 execution. + * + * If FW upgrade is performed with reboot, this function should be called right + * after installing FW Update object. + * + * @param result Result of the FW update. To comply with LwM2M specification + * should be one of the following: + * @ref SDM_FW_UPDATE_RESULT_SUCCESS + * @ref SDM_FW_UPDATE_RESULT_INTEGRITY_FAILURE + * @ref SDM_FW_UPDATE_RESULT_FAILED + * + * @return 0 in case of success, negative value in case of calling the function + * in other state than Updating. + */ +int sdm_fw_update_object_set_update_result(sdm_fw_update_result_t result); + +/** + * Sets the result of FW download in FOTA with PULL method. + * + * @param result Result of the downloading. To comply with LwM2M specification + * should be one of the following: + * @ref SDM_FW_UPDATE_RESULT_SUCCESS + * @ref SDM_FW_UPDATE_RESULT_NOT_ENOUGH_SPACE + * @ref SDM_FW_UPDATE_RESULT_OUT_OF_MEMORY + * @ref SDM_FW_UPDATE_RESULT_CONNECTION_LOST + * @ref SDM_FW_UPDATE_RESULT_INTEGRITY_FAILURE + * @ref SDM_FW_UPDATE_RESULT_UNSUPPORTED_PACKAGE_TYPE + * @ref SDM_FW_UPDATE_RESULT_INVALID_URI + * @ref SDM_FW_UPDATE_RESULT_UNSUPPORTED_PROTOCOL + * + * @return 0 in case of success, negative value in case of calling the function + * in other state than UPDATE_STATE_DOWNLOADING. + */ +int sdm_fw_update_object_set_download_result(sdm_fw_update_result_t result); + +#endif // SDM_FW_UPDATE_H diff --git a/include_public/anj/sdm_impl.h b/include_public/anj/sdm_impl.h new file mode 100644 index 00000000..dc29ea1b --- /dev/null +++ b/include_public/anj/sdm_impl.h @@ -0,0 +1,116 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#ifndef SDM_IMPL_H +#define SDM_IMPL_H + +#include +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * The buffer is full, the function must be called again to retrieve the rest + * of the message. */ +#define SDM_IMPL_BLOCK_TRANSFER_NEEDED 1 + +/** + * Used for block transfers. All records have been read. Call @ref sdm_process + * again with new message to continue parsing. If no more data is available, + * this shall be treated as an error.*/ +#define SDM_IMPL_WANT_NEXT_MSG 2 + +/** + * SDM implementation context, do not modify its fields. + */ +typedef struct { + union { + fluf_io_out_ctx_t out_ctx; + fluf_io_in_ctx_t in_ctx; + fluf_io_register_ctx_t register_ctx; + fluf_io_discover_ctx_t discover_ctx; + fluf_io_bootstrap_discover_ctx_t bootstrap_discover_ctx; + } fluf_io; + bool in_progress; + bool data_to_copy; + fluf_op_t op; + uint32_t block_number; +} sdm_process_ctx_t; + +/** + * Model implementation of SDM API handling. Call it after @ref + * fluf_msg_decode call. Processes all LwM2M requests related to the data model. + * For all operations that read data from data model, the read values encoded in + * the format indicated in @p msg will be written to the @p out_buff. This + * function is designed to handle block operations (RFC7252). If the request + * comes in several packets, call it separately for each @p msg. If the response + * does not fit in the @p out_buff then @ref SDM_IMPL_BLOCK_TRANSFER_NEEDED will + * be returned, in which case send the response as a single block and call the + * function again. After message handling @p in_out_msg is used to prepare the + * answer. If response payload doesn't fit in @p out_buff, block option will be + * added to the @p in_out_msg. + * + * IMPORTANT: In Block-Wise Transfer in CoAP single block size is always + * power of two. If @p out_buff_len does not meet this condition and the entire + * payload does not fit in @p out_buff then an error will be returned. + * + * IMPORTANT: If for some reason you want to handle data model related requests + * by yourself, then use @ref sdm_process as a reference. However, in most + * applications, the use of this function meets all requirements. + * + * @param ctx SDM implementation context. + * @param dm Data model context. + * @param in_out_msg LwM2M Server request or + * @ref LWM2M_OP_REGISTER message, used for + * response preparing. + * @param is_bootstrap_server_call Indicate source of request. + * @param[out] out_buff Buffer for the data read from the data + * model. + * @param out_buff_len Length of payload buffer. Must be + * power of two in order to support + * block operations. + * + * @returns + * - 0 on success, + * - @ref SDM_IMPL_BLOCK_TRANSFER_NEEDED if @p out_buff is full and the function + * must be called again, + * - @ref SDM_IMPL_WANT_NEXT_MSG if the next block message is expected, + * - a negative value in case of error. + */ +int sdm_process(sdm_process_ctx_t *ctx, + sdm_data_model_t *dm, + fluf_data_t *in_out_msg, + bool is_bootstrap_server_call, + char *out_buff, + size_t out_buff_len); + +/** + * Can be used to cancel ongoing operation. There are 2 main usage scenarios: + * - the lack of support for block operations, + * - transaction is cancelled. + * + * @param ctx SDM implementation context. + * @param dm Data model context. + * + * @returns + * - 0 on success, + * - a negative value in case of error. + */ +int sdm_process_stop(sdm_process_ctx_t *ctx, sdm_data_model_t *dm); + +#ifdef __cplusplus +} +#endif + +#endif // SDM_IMPL_H diff --git a/include_public/anj/sdm_io.h b/include_public/anj/sdm_io.h new file mode 100644 index 00000000..7eac7cdc --- /dev/null +++ b/include_public/anj/sdm_io.h @@ -0,0 +1,677 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#ifndef SDM_IO_H +#define SDM_IO_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct sdm_res_inst_struct sdm_res_inst_t; +typedef struct sdm_res_struct sdm_res_t; +typedef struct sdm_obj_inst_struct sdm_obj_inst_t; +typedef struct sdm_obj_struct sdm_obj_t; + +/** Error values that may be returned from data model handlers. @{ */ +/** + * Request sent by the LwM2M Server was malformed or contained an invalid + * value. + */ +#define SDM_ERR_BAD_REQUEST (-(int) FLUF_COAP_CODE_BAD_REQUEST) +/** + * LwM2M Server is not allowed to perform the operation due to lack of + * necessary access rights. + */ +#define SDM_ERR_UNAUTHORIZED (-(int) FLUF_COAP_CODE_UNAUTHORIZED) + +/** Target of the operation (Object/Instance/Resource) does not exist. */ +#define SDM_ERR_NOT_FOUND (-(int) FLUF_COAP_CODE_NOT_FOUND) +/** + * Operation is not allowed in current device state or the attempted operation + * is invalid for this target (Object/Instance/Resource) + */ +#define SDM_ERR_METHOD_NOT_ALLOWED (-(int) FLUF_COAP_CODE_METHOD_NOT_ALLOWED) + +/** Unspecified error, no other error code was suitable. */ +#define SDM_ERR_INTERNAL (-(int) FLUF_COAP_CODE_INTERNAL_SERVER_ERROR) +/** Operation is not implemented by the LwM2M Client. */ +#define SDM_ERR_NOT_IMPLEMENTED (-(int) FLUF_COAP_CODE_NOT_IMPLEMENTED) +/** + * LwM2M Client is busy processing some other request; LwM2M Server may retry + * sending the same request after some delay. + */ +#define SDM_ERR_SERVICE_UNAVAILABLE (-(int) FLUF_COAP_CODE_SERVICE_UNAVAILABLE) +/** @} */ + +/** + * Data model operation result. + */ +typedef enum { + /** Operation success. Object has been changed. */ + SDM_OP_RESULT_SUCCESS_MODIFIED, + /** Read-only operation success. */ + SDM_OP_RESULT_SUCCESS_NOT_MODIFIED, + /** The operation has failed. */ + SDM_OP_RESULT_FAILURE +} sdm_op_result_t; + +/** + * Resource operation types. + */ +typedef enum { + /** + * Read-only Single-Instance Resource. Bootstrap Server might attempt to + * write to it anyway. + */ + SDM_RES_R, + /** + * Read-only Multiple Instance Resource. Bootstrap Server might attempt to + * write to it anyway. + */ + SDM_RES_RM, + /** Write-only Single-Instance Resource. */ + SDM_RES_W, + /** Write-only Multiple Instance Resource. */ + SDM_RES_WM, + /** Read/Write Single-Instance Resource. */ + SDM_RES_RW, + /** Read/Write Multiple Instance Resource. */ + SDM_RES_RWM, + /** Executable Resource. */ + SDM_RES_E, + /** Resource that can be read/written only by Bootstrap server. */ + SDM_RES_BS_RW +} sdm_res_operation_t; + +/** + * Basic information about Resource. Can be used by the same Resource in + * different Instances of an Object. + */ +typedef struct { + /** Resource ID number. */ + fluf_rid_t rid; + /** Resource data type as defined in Appendix C of the LwM2M spec. */ + fluf_data_type_t type; + /** Operation allowed on the Resource. */ + sdm_res_operation_t operation; +} sdm_res_spec_t; + +/** + * A handler that reads the Resource or Resource Instance value, called only if + * the Resource or Resource Instance is one of the @ref SDM_RES_R, @ref + * SDM_RES_RW or @ref SDM_RES_BS_RW, @ref SDM_RES_RM, @ref SDM_RES_RWM, + * + * @param obj Object definition pointer. + * @param obj_inst Object Instance pointer. + * @param res Resource pointer. + * @param res_inst Resource Instance pointer NULL in case + * of a single Resource. + * @param[out] out_value Returned Resource value. + * + * + * @returns This handler should return: + * - 0 on success, + * - a negative value in case of error. If it returns one of SDM_ERR_ + * constants, the response message will have an appropriate CoAP response + * code. Otherwise, the device will respond with an unspecified (but valid) + * error code. + */ +typedef int sdm_res_read_t(sdm_obj_t *obj, + sdm_obj_inst_t *obj_inst, + sdm_res_t *res, + sdm_res_inst_t *res_inst, + fluf_res_value_t *out_value); + +/** + * A handler that writes the Resource or Resource Instance value, called only if + * the Resource or Resource Instance is PRESENT and is one of the @ref + * SDM_RES_W, @ref SDM_RES_RW, @ref SDM_RES_WM, @ref SDM_RES_RWM, @ref + * SDM_RES_BS_RW. + * + * For values of type @ref FLUF_DATA_TYPE_BYTES and @ref FLUF_DATA_TYPE_STRING, + * in case of the block operation, handler can be called several times, with + * consecutive chunks of value - @ref offset value in @ref bytes_or_string will + * be changing. + * + * IMPORTANT: For value of type @ref FLUF_DATA_TYPE_STRING always use + * chunk_length to determine the length of the string, never use the + * strlen() function - pointer to string data points directly to CoAP + * message payload. + * + * @param obj Object definition pointer. + * @param obj_inst Object Instance pointer. + * @param res Resource pointer. + * @param res_inst Resource Instance pointer NULL in case + * of a single Resource. + * @param value Resource value. + * + * + * @returns This handler should return: + * - 0 on success, + * - a negative value in case of error. If it returns one of SDM_ERR_ + * constants, the response message will have an appropriate CoAP response + * code. Otherwise, the device will respond with an unspecified (but valid) + * error code. + */ +typedef int sdm_res_write_t(sdm_obj_t *obj, + sdm_obj_inst_t *obj_inst, + sdm_res_t *res, + sdm_res_inst_t *res_inst, + const fluf_res_value_t *value); + +/** + * A handler that performs the Execute action on given Resource, called only if + * the Resource is PRESENT and is @ref SDM_RES_E kind. + * + * @param obj Object definition pointer. + * @param obj_inst Object Instance pointer. + * @param res Resource pointer. + * @param execute_arg Payload provided in EXECUTE request, NULL if not + * present. + * @param execute_arg_len Execute payload length. + * + * @returns This handler should return: + * - 0 on success, + * - a negative value in case of error. If it returns one of SDM_ERR_ + * constants, the response message will have an appropriate CoAP response + * code. Otherwise, the device will respond with an unspecified (but valid) + * error code. + */ +typedef int sdm_res_execute_t(sdm_obj_t *obj, + sdm_obj_inst_t *obj_inst, + sdm_res_t *res, + const char *execute_arg, + size_t execute_arg_len); + +/** + * A handler called in order to create a new Instance of the Resource. + * + * @param obj Object definition pointer. + * @param obj_inst Object Instance pointer. + * @param res Resource pointer. + * @param [out] out_res_inst Points to the Instances array field in @ref + * sdm_res_t. Must be filled in this call. After + * successfull call the array will be reorganised if + * needed to keep the ascending order of the + * Instances. + * @param riid New Resource Instance ID. + * + * @returns This handler should return: + * - 0 on success, + * - a negative value in case of error. If it returns one of SDM_ERR_ + * constants, the response message will have an appropriate CoAP response + * code. Otherwise, the device will respond with an unspecified (but valid) + * error code. + */ +typedef int sdm_res_inst_create_t(sdm_obj_t *obj, + sdm_obj_inst_t *obj_inst, + sdm_res_t *res, + sdm_res_inst_t **out_res_inst, + fluf_riid_t riid); + +/** + * A handler called in order to delete an Instance of the Resource. After this + * call @p res_inst will be removed from the instances array in the @p res. + * + * @param obj Object definition pointer. + * @param obj_inst Object Instance pointer. + * @param res Resource pointer. + * @param res_inst Resource Instance pointer. + * + * @returns This handler should return: + * - 0 on success, + * - a negative value in case of error. If it returns one of SDM_ERR_ + * constants, the response message will have an appropriate CoAP response + * code. Otherwise, the device will respond with an unspecified (but valid) + * error code. + */ +typedef int sdm_res_inst_delete_t(sdm_obj_t *obj, + sdm_obj_inst_t *obj_inst, + sdm_res_t *res, + sdm_res_inst_t *res_inst); + +/** + * A handler that creates an Object Instance. + * + * @param obj Object definition pointer. + * @param [out] out_obj_inst Points to the Instances array field in @ref + * sdm_obj_t. Must be filled in this call. After + * successfull call the array will be reorganised if + * needed to keep the ascending order of the + * Instances. + * @param iid New object Instance ID. + * + * @returns This handler should return: + * - 0 on success, + * - a negative value in case of error. If it returns one of SDM_ERR_ + * constants, the response message will have an appropriate CoAP response + * code. Otherwise, the device will respond with an unspecified (but valid) + * error code. + */ +typedef int sdm_inst_create_t(sdm_obj_t *obj, + sdm_obj_inst_t **out_obj_inst, + fluf_iid_t iid); + +/** + * A handler that deletes an Object Instance. After this call @p obj_inst will + * be removed from the Instances array in the @p obj. + * + * @param obj Object definition pointer. + * @param obj_inst Object Instance pointer. + * + * @returns This handler should return: + * - 0 on success, + * - a negative value in case of error. If it returns one of SDM_ERR_ + * constants, the response message will have an appropriate CoAP response + * code. Otherwise, the device will respond with an unspecified (but valid) + * error code. + */ +typedef int sdm_inst_delete_t(sdm_obj_t *obj, sdm_obj_inst_t *obj_inst); + +/** + * A handler that shall reset Object Instance to its default (after creational) + * state. In this call remove all resource instances of this @p obj_inst that + * are writable. New values will be provided. This handler is used in the LwM2M + * WRITE_REPLACE operation. + * + * @param obj Object definition pointer. + * @param obj_inst Object Instance pointer. + * + * @returns This handler should return: + * - 0 on success, + * - a negative value in case of error. If it returns one of SDM_ERR_ + * constants, the response message will have an appropriate CoAP response + * code. Otherwise, the device will respond with an unspecified (but valid) + * error code. + */ +typedef int sdm_inst_reset_t(sdm_obj_t *obj, sdm_obj_inst_t *obj_inst); + +/** + * It is called when a request from the LwM2M server involves an Object + * associated with this handler. + * + * @param obj Object definition pointer. + * @param operation Data model operation type. + * + * @returns This handler should return: + * - 0 on success, + * - a negative value in case of error. If it returns one of SDM_ERR_ + * constants, the response message will have an appropriate CoAP response + * code. Otherwise, the device will respond with an unspecified (but valid) + * error code. + */ +typedef int sdm_operation_begin_t(sdm_obj_t *obj, fluf_op_t operation); + +/** + * A handler that is called after transaction is finished, but before + * @ref sdm_operation_end_t is called. It is used to check whether the + * operation can be completed successfully. It is called when a request + * from the LwM2M server involves an Object associated with this handler and + * modifies it (CREATE, WRITE and DELETE operations). + * + * @param obj Object definition pointer. + * + * @returns This handler should return: + * - 0 on success, + * - a negative value in case of error. If it returns one of SDM_ERR_ + * constants, the response message will have an appropriate CoAP response + * code. Otherwise, the device will respond with an unspecified (but valid) + * error code. + */ +typedef int sdm_operation_validate_t(sdm_obj_t *obj); + +/** + * It is called after handling a request from the LwM2M server. If @p status is + * @ref SDM_OP_RESULT_FAILURE user is supposed to restore previous @p obj state. + * + * @param obj Object definition pointer. + * @param result Status of the LwM2M operation. + * + * @returns This handler should return: + * - 0 on success, + * - a negative value in case of error. + */ +typedef int sdm_operation_end_t(sdm_obj_t *obj, sdm_op_result_t result); + +/** + * A struct containing pointers to Resource handlers. A single Instance can be + * used by many Resources. + */ +typedef struct { + /** + * Get Resource value, required for LwM2M READ operation. + * If NULL and the Resource supports READ operation, then the value will be + * read directly from @ref fluf_res_value_t + */ + sdm_res_read_t *res_read; + + /** + * Set Resource value, required for LwM2M WRITE operation. + * If NULL and the Resource supports WRITE operation, then the value will be + * written directly to @ref fluf_res_value_t + */ + sdm_res_write_t *res_write; + + /** + * Required for LwM2M EXECUTE operation. Must be defined for a Resource of + * type @ref SDM_RES_E. + */ + sdm_res_execute_t *res_execute; + + /** + * Create a Resource Instance in a multi-instance Resource. + * + * Required for handling LwM2M WRITE operation. If not defined and the + * operation requires the creation of a new Instance then an error will be + * returned to the LwM2M server. + */ + sdm_res_inst_create_t *res_inst_create; + + /** + * Delete a Resource Instance from a multi-instance Resource. + * + * Required for handling LwM2M DELETE operation performed on Resource + * Instances and for LwM2M WRITE operation in replace mode, which can remove + * all Resource Instances. If not defined and the operation requires + * deleting the Instance, an error will be returned to the LwM2M server. + */ + sdm_res_inst_delete_t *res_inst_delete; +} sdm_res_handlers_t; + +/** Resource value struct. */ +typedef struct { + /** Resource value. */ + fluf_res_value_t value; + /** + * For a Resource of type @ref FLUF_DATA_TYPE_BYTES or @ref + * FLUF_DATA_TYPE_STRING describes the size of the buffer pointed to by the + * value.bytes_or_string.data. + * + * Must be set if Resource supports WRITE operation and @ref res_write + * handler is not defined. + */ + size_t resource_buffer_size; +} sdm_res_value_t; + +/** A struct defining a value of Resource Instance. */ +struct sdm_res_inst_struct { + /** Resource Instance value. */ + sdm_res_value_t res_value; + /** Resource Instance ID number. */ + fluf_riid_t riid; +}; + +/** Main Resource struct. */ +struct sdm_res_struct { + /** Resource specification, can't be NULL. */ + const sdm_res_spec_t *res_spec; + /** + * Resource handlers, can be NULL, unless the Resource is of type @ref + * SDM_RES_E, or we want to fully support the LwM2M WRITE operation (adding + * and removing Resource Instances). + */ + const sdm_res_handlers_t *res_handlers; + /** + * For READ and WRITE operations if the corresponding handlers are not + * defined, the value of the Resource/Resource Instance will be entered or + * taken directly from here. + */ + union { + struct { + /** + * Pointer to the array of the pointers to the Resource Instances. + * @ref max_allowed_inst_number defined the size of the array. + * + * During any type of CREATE or WTIRE operations array of Instances + * might be modified, so if @ref sdm_res_inst_create_t or @ref + * sdm_res_inst_delete_t are defined for the Resource @p insts can't + * points to the const array. + * + * When @ref sdm_add_obj is called, the Instances inside the array + * must be ordered in ascending order of Resource Instance ID + * number. + * + * Example: + * @code + * #define RES_INST_MAX_COUNT 3 + * sdm_res_inst_t res_inst_1 = {.riid = 1}; + * sdm_res_inst_t res_inst_2 = {.riid = 2}; + * sdm_res_inst_t *res_insts[RES_INST_MAX_COUNT] = { + * &res_inst_1, + * &res_inst_2}; + * sdm_res_t custom_res = { + * .res_spec = &custom_res_spec, + * .value.res_inst.insts = res_insts, + * .value.res_inst.inst_count = 2, + * .value.res_inst.max_inst_count = + * RES_INST_MAX_COUNT}; + * @endcode + */ + sdm_res_inst_t **insts; + /** Max allowed number of Instances of this Resource. */ + uint16_t max_inst_count; + /** Number of the Resource Instances. */ + uint16_t inst_count; + } res_inst; + /** For single-instance Resource stores the value of the Resource. */ + sdm_res_value_t res_value; + } value; +}; + +/** A struct defining an Object Instance. */ +struct sdm_obj_inst_struct { + /** Object Instance ID number. */ + fluf_iid_t iid; + /** Pointer to the array of the Resources. */ + sdm_res_t *resources; + /** Number of Resources of this Object Instance. */ + uint16_t res_count; +}; + +/** + * A struct containing pointers to Object handlers. + */ +typedef struct { + /** + * Create an Object Instance. Required for handling LwM2M CREATE operation. + */ + sdm_inst_create_t *inst_create; + /** + * Delete an Object Instance. Required for handling LwM2M DELETE operation. + */ + sdm_inst_delete_t *inst_delete; + /** + * Reset an Object Instance. Required for handling LwM2M WRITE operation in + * replace mode - if it's not present then this operation will fail. + */ + sdm_inst_reset_t *inst_reset; + + /** + * If defined then it's called before any kind of LwM2M operation that + * involves this Object. + * + * Until @ref operation_end call, must not change due to factors other than + * the data model handler calls. + */ + sdm_operation_begin_t *operation_begin; + /** + * If defined then it's called after any kind of LwM2M operation + * that modifies the Object. Return value of this handler determines + * if Object's state is valid or not. + */ + sdm_operation_validate_t *operation_validate; + /** + * If defined then it's called on the end of any kind of LwM2M + * operation that involves this Object. Gives information about operation + * result. + */ + sdm_operation_end_t *operation_end; +} sdm_obj_handlers_t; + +/** A struct defining an Object. */ +struct sdm_obj_struct { + /** Object ID number. */ + fluf_oid_t oid; + /** + * Object version: a string with static lifetime, containing two digits + * separated by a dot (for example: "1.1"). If left NULL, LwM2M client will + * not include the "ver=" attribute in Register and Discover messages, which + * implies version 1.0. + */ + const char *version; + /** + * Object handlers, if NULL every CREATE and DELETE operation will fail. + */ + const sdm_obj_handlers_t *obj_handlers; + /** + * Pointer to the array of the pointers to the Object Instances. + * @ref max_allowed_inst_number defined the size of the array. + * When @ref sdm_add_obj is called, the Instances inside the array must be + * ordered in ascending order of Object Instance ID number. + * + * During LwM2M CREATE and DELETE operations array of Instances will be + * modified, so if @ref sdm_inst_create_t or @ref sdm_inst_delete_t are + * defined for the Object @p insts can't points to the const array. + * + * Example: + * @code + * #define INST_MAX_COUNT 3 + * sdm_obj_inst_t inst_1 = {.iid = 1}; + * sdm_obj_inst_t inst_2 = {.iid = 2}; + * sdm_obj_inst_t *insts[INST_MAX_COUNT] = {&inst_1, &inst_2}; + * sdm_obj_t custom_obj = { + * .oid = CUSTOM_OID, + * .insts = insts, + * .inst_count = 2, + * .max_inst_count = INST_MAX_COUNT}; + * @endcode + */ + sdm_obj_inst_t **insts; + /** Max allowed number of Instances of this Object. */ + uint16_t max_inst_count; + /** Number of Instances of this Object. */ + uint16_t inst_count; + /** + * Indicates an ongoing operation, if true the user should not modify any + * field of the Object. + */ + bool in_transaction; +}; + +/** REGISTER operation context, do not modify this structure directly. */ +typedef struct { + uint16_t obj_idx; + uint16_t inst_idx; + fluf_id_type_t level; +} _sdm_reg_ctx_t; + +/** DISCOVER operation context, do not modify this structure directly. */ +typedef struct { + uint16_t ssid; + uint16_t obj_idx; + uint16_t inst_idx; + uint16_t res_idx; + uint16_t res_inst_idx; + fluf_id_type_t level; +} _sdm_disc_ctx_t; + +/** WRITE operation context, do not modify this structure directly. */ +typedef struct { + fluf_uri_path_t path; + bool instance_created; +} _sdm_write_ctx_t; + +/** READ operation context, do not modify this structure directly. */ +typedef struct { + uint16_t inst_idx; + uint16_t res_idx; + uint16_t res_inst_idx; + size_t total_op_count; + fluf_id_type_t level; + fluf_uri_path_t path; +} _sdm_read_ctx_t; + +/** Set of pointers related with operation, do not modify this structure + * directly. */ +typedef struct { + sdm_obj_t *obj; + sdm_obj_inst_t *inst; + sdm_res_t *res; + sdm_res_inst_t *res_inst; +} _sdm_entity_ptrs_t; + +/** + * Data model context, do not modify this structure directly, its fields are + * changed during sdm API calls. Initialize it by calling @ref + * sdm_dm_initialize. Objects can be added using @ref sdm_add_obj and removed + * with @ref sdm_remove_obj. + */ +typedef struct { + sdm_obj_t **objs; + uint16_t objs_count; + uint16_t max_allowed_objs_number; + + union { + _sdm_reg_ctx_t reg_ctx; + _sdm_disc_ctx_t disc_ctx; + _sdm_write_ctx_t write_ctx; + _sdm_read_ctx_t read_ctx; + } op_ctx; + _sdm_entity_ptrs_t entity_ptrs; + int result; + bool boostrap_operation; + bool is_transactional; + size_t op_count; + bool op_in_progress; + fluf_op_t operation; +} sdm_data_model_t; + +/** + * Assigns @p objs_array to @p dm. Every @ref sdm_add_obj call will add Object + * to @p objs_array until @p objs_array_size is reached. @p objs_array must not + * contain any objects. + * + * @param dm Data model. + * @param objs_array Pointer to the array of the pointers to the Objects. + * @param objs_array_size Determines maximum allowed number of Objects. + */ +void sdm_initialize(sdm_data_model_t *dm, + sdm_obj_t **objs_array, + uint16_t objs_array_size); + +/** + * Add Object to the data model and validates it. Remember that resources and + * instances have to be stored in ascending order due to the ID value. + * + * @param dm Data model to which @p obj will be added. + * @param obj Pointer to the Object definition struct. + * + * @returns 0 on success, a negative value in case of error. + */ +int sdm_add_obj(sdm_data_model_t *dm, sdm_obj_t *obj); + +/** + * Removes Object from the data model. + * + * @param dm Data model from which @p obj will be removed. + * @param oid ID number of the Object to be removed. + * + * @returns 0 on success, a negative value in case of error. + */ +int sdm_remove_obj(sdm_data_model_t *dm, fluf_oid_t oid); + +#ifdef __cplusplus +} +#endif + +#endif // SDM_IO_H diff --git a/include_public/anj/sdm_notification.h b/include_public/anj/sdm_notification.h new file mode 100644 index 00000000..705ff67a --- /dev/null +++ b/include_public/anj/sdm_notification.h @@ -0,0 +1,57 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#ifndef SDM_NOTIFICATION_H +#define SDM_NOTIFICATION_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Processes certain incoming operations related to notifications and their + * attributes (Observe Operation, Cancel Observation, Write-Attribute). Supports + * only resource observations and pmin, pmax attributes. + * + * @param in_out_msg + * @param dm + * @param out_buff + * @param out_buff_len + * @return int + */ +int sdm_notification(fluf_data_t *in_out_msg, + sdm_data_model_t *dm, + char *out_buff, + size_t out_buff_len); + +/** + * Check if any notification should be sent. + * Prepares only one notification per call even if more notifications should be + * sent. This function should be called periodically. + * + * @param out_msg + * @param dm + * @param out_buff + * @param out_buff_len + * @param format + * @return int + */ +int sdm_notification_process(fluf_data_t *out_msg, + sdm_data_model_t *dm, + char *out_buff, + size_t out_buff_len, + uint16_t format); + +#ifdef __cplusplus +} +#endif + +#endif // SDM_NOTIFICATION_H diff --git a/include_public/anj/sdm_send.h b/include_public/anj/sdm_send.h new file mode 100644 index 00000000..f0ab8afa --- /dev/null +++ b/include_public/anj/sdm_send.h @@ -0,0 +1,57 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#ifndef SDM_SEND_H +#define SDM_SEND_H + +#include + +#include + +/** + * Creates a Send message based on the current state of the data model and + * list of paths to resources. + * + * @param dm Data model to operate on. + * @param format Format in which the message will created. Currently + * only @p FLUF_COAP_FORMAT_SENML_CBOR is supported. + * @param[out] out_buff Pointer to a buffer to which the message will be + * written. + * @param[out] inout_size Size of the buffer as input, writen bytes as output. + * @param paths Array of paths to resources. + * @param path_cnt Number of paths stored in the @p paths array. + * + * @returns 0 on success, a non zero value in case of error. + */ +int sdm_send_create_msg_from_dm(sdm_data_model_t *dm, + uint16_t format, + uint8_t *out_buff, + size_t *inout_size, + const fluf_uri_path_t *paths, + const size_t path_cnt); + +/** + * Creates a Send message based on a list of records passed to the function. + * + * @param format Format in which the message will created. Currently + * only @p FLUF_COAP_FORMAT_SENML_CBOR is supported. + * @param[out] out_buff Pointer to a buffer to which the message will be + * written. + * @param[out] inout_size Size of the buffer as input, writen bytes as output. + * @param records Array of records of resource values. + * @param path_cnt Number of records stored in the @p records array. + * + * @returns 0 on success, a non zero value in case of error. + */ +int sdm_send_create_msg_from_list_of_records(uint16_t format, + uint8_t *out_buff, + size_t *inout_size, + const fluf_io_out_entry_t *records, + const size_t record_cnt); +#endif // SDM_SEND_H diff --git a/include_public/anjay_lite/anjay_lite.h b/include_public/anjay_lite/anjay_lite.h new file mode 100644 index 00000000..f7dd8f7d --- /dev/null +++ b/include_public/anjay_lite/anjay_lite.h @@ -0,0 +1,68 @@ +/* + * Copyright 2023-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_LITE_H +#define ANJAY_LITE_H + +#include +#include + +#include +#include + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + ANJAY_SECURITY_PSK = 0, //< Pre-Shared Key mode + ANJAY_SECURITY_RPK = 1, //< Raw Public Key mode + ANJAY_SECURITY_CERTIFICATE = 2, //< Certificate mode + ANJAY_SECURITY_NOSEC = 3, //< NoSec mode + ANJAY_SECURITY_EST = 4 //< Certificate mode with EST +} anjay_security_mode_t; + +typedef struct { + uint16_t ssid; + uint32_t lifetime; + fluf_binding_type_t binding; + anjay_security_mode_t security_mode; + char *hostname; + uint16_t port; +} anjay_lite_server_conf_t; + +typedef union { + anjay_net_op_open_udp_args_t udp; + anjay_net_op_open_dtls_args_t dtls; +} anjay_lite_conn_conf_t; + +typedef struct { + sdm_obj_t *objs_array[ANJAY_LITE_ALLOWED_OBJECT_NUMBER]; + sdm_data_model_t dm; + sdm_process_ctx_t dm_impl; + anjay_lite_server_conf_t server_conf; + char *endpoint_name; +} anjay_lite_t; + +int anjay_lite_init(anjay_lite_t *anjay_lite); + +void anjay_lite_process(anjay_lite_t *anjay_lite); + +void anjay_lite_send(uint8_t *payload, size_t size); + +#ifdef __cplusplus +} +#endif + +#endif // ANJAY_LITE_H diff --git a/include_public/anjay_lite/anjay_lite_config.h b/include_public/anjay_lite/anjay_lite_config.h new file mode 100644 index 00000000..145c0851 --- /dev/null +++ b/include_public/anjay_lite/anjay_lite_config.h @@ -0,0 +1,47 @@ +/* + * Copyright 2023-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_LITE_CONFIG_H +#define ANJAY_LITE_CONFIG_H + +/** + * ANJAY_SERVERS + */ +#ifndef ANJAY_LITE_ALLOWED_SERVERS_NUMBER +# define ANJAY_LITE_ALLOWED_SERVERS_NUMBER 1 +#endif // ANJAY_LITE_ALLOWED_SERVERS_NUMBER + +#ifndef ANJAY_LITE_MSG_BUFF_SIZE +# define ANJAY_LITE_MSG_BUFF_SIZE 1500 +#endif // ANJAY_LITE_MSG_BUFF_SIZE + +#ifndef ANJAY_LITE_RECONNECTION_TIMEOUT_MS +# define ANJAY_LITE_RECONNECTION_TIMEOUT_MS 5000 +#endif // ANJAY_LITE_RECONNECTION_TIMEOUT_MS + +#ifndef ANJAY_LITE_RESPONSE_TIMEOUT_MS +# define ANJAY_LITE_RESPONSE_TIMEOUT_MS 20000 +#endif // ANJAY_LITE_RECONNECTION_TIMEOUT_MS + +#ifndef ANJAY_LITE_SERVERS_REGISTER_PATH_STR_LEN +# define ANJAY_LITE_SERVERS_REGISTER_PATH_STR_LEN 30 +#endif // ANJAY_LITE_SERVERS_REGISTER_PATH_STR_LEN + +/** + * ANJAY_DM + */ +#ifndef ANJAY_LITE_ALLOWED_OBJECT_NUMBER +# define ANJAY_LITE_ALLOWED_OBJECT_NUMBER 6 +#endif // ANJAY_LITE_ALLOWED_OBJECT_NUMBER + +#ifndef ANJAY_LITE_PAYLOAD_BUFF_SIZE +# define ANJAY_LITE_PAYLOAD_BUFF_SIZE (512) +#endif // ANJAY_LITE_PAYLOAD_BUFF_SIZE + +#endif // ANJAY_LITE_CONFIG_H diff --git a/include_public/anjay_lite/anjay_net.h b/include_public/anjay_lite/anjay_net.h new file mode 100644 index 00000000..58bd5c2d --- /dev/null +++ b/include_public/anjay_lite/anjay_net.h @@ -0,0 +1,95 @@ +#ifndef ANJAY_LITE_ANJAY_NET_H +#define ANJAY_LITE_ANJAY_NET_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { ANJAY_NET_IP_VER_V4, ANJAY_NET_IP_VER_V6 } anjay_net_ip_ver_t; + +typedef enum { + ANJAY_NET_OP_OPEN_UDP, + ANJAY_NET_OP_OPEN_UDP_RES, + ANJAY_NET_OP_OPEN_DTLS, + ANJAY_NET_OP_OPEN_DTLS_RES, + ANJAY_NET_OP_TRY_RECV, + ANJAY_NET_OP_SEND, + ANJAY_NET_OP_SEND_RES, + ANJAY_NET_OP_CLOSE, + ANJAY_NET_OP_CLOSE_RES, + ANJAY_NET_OP_CLEANUP +} anjay_net_op_t; + +typedef union { + void *ref_ptr; + int ref_int; +} anjay_net_conn_ref_t; + +typedef struct { + const char *hostname; + uint16_t port; + anjay_net_ip_ver_t version; +} anjay_net_op_open_udp_args_t; + +typedef struct { + const char *hostname; + uint16_t port; + anjay_net_ip_ver_t version; + const char *identity; + const char *psk; + bool try_resume; +} anjay_net_op_open_dtls_args_t; + +typedef struct { + bool resumed; +} anjay_net_op_open_dtls_res_args_t; + +typedef struct { + size_t length; + uint8_t *out_read_buf; + size_t out_read_length; +} anjay_net_op_try_recv_args_t; + +typedef struct { + size_t length; + const uint8_t *buf; +} anjay_net_op_send_args_t; + +typedef struct { + size_t out_write_length; +} anjay_net_op_send_res_args_t; + +typedef struct { + anjay_net_op_t op; + anjay_net_conn_ref_t conn_ref; + union { + anjay_net_op_open_udp_args_t open_udp; + anjay_net_op_open_dtls_args_t open_dtls; + anjay_net_op_open_dtls_res_args_t open_dtls_res; + anjay_net_op_try_recv_args_t try_recv; + anjay_net_op_send_args_t send; + anjay_net_op_send_res_args_t send_res; + } args; +} anjay_net_op_ctx_t; + +typedef enum { + ANJAY_NET_OP_RES_OK, + ANJAY_NET_OP_RES_AGAIN, + ANJAY_NET_OP_RES_ERR +} anjay_net_op_res_t; + +// TODO: since Anjay lite for now is using static storage and is not externally +// configurable, provide a function declaration instead of typedef for handler. +// There's no user_ctx in this case, too. + +anjay_net_op_res_t anjay_net_op_handler(anjay_net_op_ctx_t *op_ctx); + +#ifdef __cplusplus +} +#endif + +#endif // ANJAY_LITE_ANJAY_NET_H diff --git a/include_public/fluf/fluf.h b/include_public/fluf/fluf.h new file mode 100644 index 00000000..a8149130 --- /dev/null +++ b/include_public/fluf/fluf.h @@ -0,0 +1,357 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#ifndef FLUF_H +#define FLUF_H + +#include +#include +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef FLUF_WITH_LWM2M12 +# define FLUF_LWM2M_VERSION_STR "1.2" +#else +# define FLUF_LWM2M_VERSION_STR "1.1" +#endif // FLUF_WITH_LWM2M12 + +/** Invalid input arguments. */ +#define FLUF_ERR_INPUT_ARG (-1) +/** Not supported binding type */ +#define FLUF_ERR_BINDING (-2) +/** Options array is not big enough */ +#define FLUF_ERR_OPTIONS_ARRAY (-3) +/** FLUF_ATTR_OPTION_MAX_SIZE is too small */ +#define FLUF_ERR_ATTR_BUFF (-4) +/** Malformed CoAP message. */ +#define FLUF_ERR_MALFORMED_MESSAGE (-5) +/** No space in buffer. */ +#define FLUF_ERR_BUFF (-6) +/** COAP message not supported or not recognized. */ +#define FLUF_ERR_COAP_BAD_MSG (-6) +/** Location paths number oversizes FLUL_MAX_ALLOWED_LOCATION_PATHS_NUMBER*/ +#define FLUF_ERR_LOCATION_PATHS_NUMBER (-7) + +/** + * LWM2M operations. + */ +typedef enum { + // Bootstrap Interface + FLUF_OP_BOOTSTRAP_REQ, + FLUF_OP_BOOTSTRAP_FINISH, + FLUF_OP_BOOTSTRAP_PACK_REQ, + // Registration Interface + FLUF_OP_REGISTER, + FLUF_OP_UPDATE, + FLUF_OP_DEREGISTER, + // DM Interface, + FLUF_OP_DM_READ, + FLUF_OP_DM_READ_COMP, + FLUF_OP_DM_DISCOVER, + FLUF_OP_DM_WRITE_REPLACE, + FLUF_OP_DM_WRITE_PARTIAL_UPDATE, + FLUF_OP_DM_WRITE_ATTR, + FLUF_OP_DM_WRITE_COMP, + FLUF_OP_DM_EXECUTE, + FLUF_OP_DM_CREATE, + FLUF_OP_DM_DELETE, + // Information reporting interface + FLUF_OP_INF_OBSERVE, + FLUF_OP_INF_OBSERVE_COMP, + FLUF_OP_INF_CANCEL_OBSERVE, + FLUF_OP_INF_CANCEL_OBSERVE_COMP, + FLUF_OP_INF_CON_NOTIFY, + FLUF_OP_INF_NON_CON_NOTIFY, + FLUF_OP_INF_SEND, + // client/server ACK Piggybacked/non-con/con response + FLUF_OP_RESPONSE, + // CoAP related messages + FLUF_OP_COAP_RESET, + FLUF_OP_COAP_PING +} fluf_op_t; + +/** + * Defines CoAP transport binding. + */ +typedef enum { + FLUF_BINDING_UDP, + FLUF_BINDING_DTLS_PSK, + FLUF_BINDING_TCP, + FLUF_BINDING_LORAWAN, + FLUF_BINDING_NIDD, + FLUF_BINDING_SMS, +} fluf_binding_type_t; + +/** + * CoAP block option type. + */ +typedef enum { + FLUF_OPTION_BLOCK_NOT_DEFINED, + FLUF_OPTION_BLOCK_1, + FLUF_OPTION_BLOCK_2 +} fluf_block_option_t; + +/** + * CoAP block option struct. + */ +typedef struct { + fluf_block_option_t block_type; + bool more_flag; + uint32_t number; + uint32_t size; +} fluf_block_t; + +/** + * Notification attributes. + */ +typedef struct { + bool has_min_period; + bool has_max_period; + bool has_greater_than; + bool has_less_than; + bool has_step; + bool has_min_eval_period; + bool has_max_eval_period; + + uint32_t min_period; + uint32_t max_period; + double greater_than; + double less_than; + double step; + uint32_t min_eval_period; + uint32_t max_eval_period; + +#ifdef FLUF_WITH_LWM2M12 + bool has_edge; + bool has_con; + bool has_hqmax; + uint32_t edge; + uint32_t con; + uint32_t hqmax; +#endif // FLUF_WITH_LWM2M12 +} fluf_attr_notification_t; + +/** + * DISCOVER operation attribute - depth parameter. + */ +typedef struct { + bool has_depth; + uint32_t depth; +} fluf_attr_discover_t; + +/** + * REGISTER operation attributes. + */ +typedef struct { + bool has_Q; + bool has_endpoint; + bool has_lifetime; + bool has_lwm2m_ver; + bool has_binding; + bool has_sms_number; + + char *endpoint; + uint32_t lifetime; + char *lwm2m_ver; + char *binding; + char *sms_number; +} fluf_attr_register_t; + +/** + * BOOTSTRAP-REQUEST operation attributes. + */ +typedef struct { + bool has_endpoint; + bool has_pct; + + char *endpoint; + uint16_t pct; +} fluf_attr_bootstrap_t; + +/** + * Location-Path from REGISTER operation response. If the number of + * Location-Paths exceeds @ref FLUL_MAX_ALLOWED_LOCATION_PATHS_NUMBER then @ref + * fluf_msg_decode returns a @ref FLUF_ERR_LOCATION_PATHS_NUMBER error. For + * every @ref fluf_msg_prepare calls for UPDATE and DEREGISTER operations, this + * structure must be filled. After @ref fluf_msg_prepare @p location points to + * message buffer, so they have to be copied into user memory. + */ +typedef struct { + // doesn't point to /rd location-path - it's obligatory + const char *location[FLUL_MAX_ALLOWED_LOCATION_PATHS_NUMBER]; + size_t location_len[FLUL_MAX_ALLOWED_LOCATION_PATHS_NUMBER]; + size_t location_count; +} fluf_location_path_t; + +/** Maximum size of ETag option, as defined in RFC7252. */ +#define FLUF_MAX_ETAG_LENGTH 8 + +/** + * CoAP ETag option. + */ +typedef struct { + uint8_t size; + uint8_t bytes[FLUF_MAX_ETAG_LENGTH]; +} fluf_etag_t; + +/** + * Contains all details of CoAP LWM2M message, used with @ref fluf_msg_decode + * and @ref fluf_msg_prepare. During CoAP message preparing all fields related + * with given @p opeartion will be used. @ref fluf_msg_decode extracts all + * possible information, so user doesn't have to use CoAP related function + * directly. + */ +typedef struct { + /** + * LWM2M operation type. Must be defined before @ref fluf_msg_prepare + * call. + */ + fluf_op_t operation; + + /** + * Pointer to CoAP msg payload. Set in @ref fluf_msg_decode, @ref + * fluf_msg_prepare copies payload directly to message buffer. + * + * IMPORTANT: Payload is not encoded or decoded by FLUF functions, use + * FLUF_IO API to achieve this. + */ + void *payload; + + /** Payload length. */ + size_t payload_size; + + /** + * Stores the value of Content Format option. If payload is present it + * describes its format. In @ref fluf_msg_decode set to + * FLUF_COAP_FORMAT_NOT_DEFINED if not present. If message contains payload, + * must be set before @ref fluf_msg_prepare call. + */ + uint16_t content_format; + + /** + * Stores the value of Accept option. It describes response payload + * preferred format. Set to FLUF_COAP_FORMAT_NOT_DEFINED if not present. + */ + uint16_t accept; + + /** + * Observation number. Have to be incremented with every Notify message. + */ + uint64_t observe_number; + + /** + * Stores the value of Uri Path options. Contains information about data + * model path. + */ + fluf_uri_path_t uri; + + /** + * Stores the value of Block option. If block type is defined + * @ref fluf_msg_prepare will add block option to the message. + */ + fluf_block_t block; + + /** Stores the value of ETag option. */ + fluf_etag_t etag; + + /** + * Location path is send in respone to the REGISTER message and then have to + * be used in UPDATE and DEREGISTER requests. + */ + fluf_location_path_t location_path; + + /** + * Attributes are optional and stored in Uri Query options. + */ + union { + fluf_attr_notification_t notification_attr; + fluf_attr_discover_t discover_attr; + fluf_attr_register_t register_attr; + fluf_attr_bootstrap_t bootstrap_attr; + } attr; + + /** + * Coap msg code. Must be set before @ref fluf_msg_prepare call if message + * is any kind of response. + */ + uint8_t msg_code; + + /** Binding type - defines communication channel. **/ + fluf_binding_type_t binding; + + /** + * Contains communication channel dependend informations that allows to + * prepare or identify response. + */ + fluf_coap_msg_t coap; +} fluf_data_t; + +/** + * Based on @p msg decodes CoAP message, compliant with the LwM2M version 1.1 + * or 1.2 (check @ref FLUF_WITH_LWM2M12 config flag). All information from + * message are decoded and stored in the @p data. Each possible option, has its + * own field in @ref fluf_data_t and if present in the message then it is + * decoded. In order to be able to send the response, the data that must be in + * the CoAP header (such as token or message id) are copied to @ref + * fluf_coap_msg_t. + * + * @param msg LWM2M/CoAP message. + * @param msg_size Length of the message. + * @param binding Defined source of the message. + * @param[out] data Empty LwM2M data instance. + * + * NOTES: Check tests/lwm2m_decode.c to see the examples of usage. + * + * @return 0 on success, or an one of the error codes defined at the top of this + * file. + */ +int fluf_msg_decode(uint8_t *msg, + size_t msg_size, + fluf_binding_type_t binding, + fluf_data_t *data); + +/** + * Based on @p data prepares CoAP message, compliant with the LwM2M version 1.1 + * or 1.2 (check @ref FLUF_WITH_LWM2M12 config flag). All information related + * with given @ref fluf_op_t are placed into the message. After function call + * @p out_buff contains a CoAP packet ready to be sent. + * + * @param data LwM2M data instance. + * @param[out] out_buff Buffer for LwM2M message. + * @param out_buff_size Buffer size. + * @param[out] out_msg_size Size of the prepared message. + * + * NOTES: Check tests/lwm2m_prepare.c to see the examples of usage. + * + * @return 0 on success, or an one of the error codes defined at the top of this + * file. + */ +int fluf_msg_prepare(fluf_data_t *data, + uint8_t *out_buff, + size_t out_buff_size, + size_t *out_msg_size); + +/** + * Should be called once to initialize the module. + * + * @param random_seed PRNG seed value, used in CoAP token generation process. + */ +void fluf_init(uint32_t random_seed); + +#ifdef __cplusplus +} +#endif + +#endif // FLUF_H diff --git a/include_public/fluf/fluf_cbor_decoder_ll.h b/include_public/fluf/fluf_cbor_decoder_ll.h new file mode 100644 index 00000000..3c188c2c --- /dev/null +++ b/include_public/fluf/fluf_cbor_decoder_ll.h @@ -0,0 +1,550 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#ifndef FLUF_IO_CBOR_DECODER_LL_H +#define FLUF_IO_CBOR_DECODER_LL_H + +#if defined(FLUF_WITH_SENML_CBOR) || defined(FLUF_WITH_LWM2M_CBOR) \ + || defined(FLUF_WITH_CBOR) + +# include + +# ifdef __cplusplus +extern "C" { +# endif + +# if defined(FLUF_WITH_CBOR_INDEFINITE_BYTES) \ + || defined(FLUF_WITH_CBOR_DECIMAL_FRACTIONS) +# define FLUF_MAX_SUBPARSER_NEST_STACK_SIZE 1 +# else /* defined(FLUF_WITH_CBOR_INDEFINITE_BYTES) || \ + defined(FLUF_WITH_CBOR_DECIMAL_FRACTIONS) */ +# define FLUF_MAX_SUBPARSER_NEST_STACK_SIZE 0 +# endif /* defined(FLUF_WITH_CBOR_INDEFINITE_BYTES) || \ + defined(FLUF_WITH_CBOR_DECIMAL_FRACTIONS) */ + +/** + * Only decimal fractions or indefinite length bytes can cause nesting. + */ +# ifdef FLUF_WITH_CBOR +# define FLUF_MAX_SIMPLE_CBOR_NEST_STACK_SIZE \ + FLUF_MAX_SUBPARSER_NEST_STACK_SIZE +# else // FLUF_WITH_CBOR +# define FLUF_MAX_SIMPLE_CBOR_NEST_STACK_SIZE 0 +# endif // FLUF_WITH_CBOR + +/** + * The LwM2M requires wrapping entries in [ {} ], but decimal fractions or + * indefinite length bytes add another level of nesting in form of an array. + */ +# ifdef FLUF_WITH_SENML_CBOR +# define FLUF_MAX_SENML_CBOR_NEST_STACK_SIZE \ + (2 + FLUF_MAX_SUBPARSER_NEST_STACK_SIZE) +# else // FLUF_WITH_SENML_CBOR +# define FLUF_MAX_SENML_CBOR_NEST_STACK_SIZE 0 +# endif // FLUF_WITH_SENML_CBOR + +/** + * In LwM2M CBOR, there may be {a: {b: {c: {[d]: value}}}} + * Decimal fractions or indefinite length bytes don't add extra level here. + */ +# ifdef FLUF_WITH_LWM2M_CBOR +# define FLUF_MAX_LWM2M_CBOR_NEST_STACK_SIZE 5 +# else // FLUF_WITH_LWM2M_CBOR +# define FLUF_MAX_LWM2M_CBOR_NEST_STACK_SIZE 0 +# endif // FLUF_WITH_LWM2M_CBOR + +# define FLUF_MAX_CBOR_NEST_STACK_SIZE \ + AVS_MAX(FLUF_MAX_SIMPLE_CBOR_NEST_STACK_SIZE, \ + AVS_MAX(FLUF_MAX_SENML_CBOR_NEST_STACK_SIZE, \ + FLUF_MAX_LWM2M_CBOR_NEST_STACK_SIZE)) + +# define FLUF_CBOR_LL_DECODER_ITEMS_INDEFINITE (-1) + +typedef struct fluf_cbor_ll_decoder_struct fluf_cbor_ll_decoder_t; + +typedef enum { + /* decoder is operational */ + FLUF_CBOR_LL_DECODER_STATE_OK, + /* decoder reached end of stream */ + FLUF_CBOR_LL_DECODER_STATE_FINISHED, + /* decoder could not make sense out of some part of the stream */ + FLUF_CBOR_LL_DECODER_STATE_ERROR +} fluf_cbor_ll_decoder_state_t; + +typedef enum { + FLUF_CBOR_LL_VALUE_NULL, + FLUF_CBOR_LL_VALUE_UINT, + FLUF_CBOR_LL_VALUE_NEGATIVE_INT, + FLUF_CBOR_LL_VALUE_BYTE_STRING, + FLUF_CBOR_LL_VALUE_TEXT_STRING, + FLUF_CBOR_LL_VALUE_ARRAY, + FLUF_CBOR_LL_VALUE_MAP, + FLUF_CBOR_LL_VALUE_FLOAT, + FLUF_CBOR_LL_VALUE_DOUBLE, + FLUF_CBOR_LL_VALUE_BOOL, + FLUF_CBOR_LL_VALUE_TIMESTAMP +} fluf_cbor_ll_value_type_t; + +typedef struct { + fluf_cbor_ll_value_type_t type; + union { + uint64_t u64; + int64_t i64; + float f32; + double f64; + } value; +} fluf_cbor_ll_number_t; + +typedef struct { + // If indefinite, this contains available bytes only for the current chunk. + size_t bytes_available; +# ifdef FLUF_WITH_CBOR_INDEFINITE_BYTES + // Used only for indefinite length bytes. + size_t initial_nesting_level; + bool indefinite; +# endif // FLUF_WITH_CBOR_INDEFINITE_BYTES +# ifdef FLUF_WITH_CBOR_STRING_TIME + struct { + size_t bytes_read; + bool initialized; + char buffer[sizeof("9999-12-31T23:59:60.999999999+99:59")]; + } string_time; +# endif // FLUF_WITH_CBOR_STRING_TIME +} fluf_cbor_ll_decoder_bytes_ctx_t; + +typedef enum { + FLUF_CBOR_LL_SUBPARSER_NONE, + FLUF_CBOR_LL_SUBPARSER_BYTES, + FLUF_CBOR_LL_SUBPARSER_EPOCH_BASED_TIME, +# ifdef FLUF_WITH_CBOR_STRING_TIME + FLUF_CBOR_LL_SUBPARSER_STRING_TIME, +# endif // FLUF_WITH_CBOR_STRING_TIME +# ifdef FLUF_WITH_CBOR_DECIMAL_FRACTIONS + FLUF_CBOR_LL_SUBPARSER_DECIMAL_FRACTION, +# endif // FLUF_WITH_CBOR_DECIMAL_FRACTIONS +} fluf_cbor_ll_subparser_type_t; + +typedef struct { + /* Type of the nested structure (FLUF_CBOR_LL_VALUE_BYTE_STRING, + * FLUF_CBOR_LL_VALUE_TEXT_STRING, FLUF_CBOR_LL_VALUE_ARRAY or + * FLUF_CBOR_LL_VALUE_MAP). */ + fluf_cbor_ll_value_type_t type; + union { + /* Number of items of the entry that were parsed */ + size_t total; + /* For indefinite structures, only the even/odd state is tracked */ + bool odd; + } items_parsed; + /* Number of all items to be parsed (in case of definite length), or + * NUM_ITEMS_INDEFINITE. */ + ptrdiff_t all_items; +} fluf_cbor_ll_nested_state_t; + +struct fluf_cbor_ll_decoder_struct { + const uint8_t *input_begin; + const uint8_t *input; + const uint8_t *input_end; + bool input_last; + + uint8_t prebuffer[9]; + uint8_t prebuffer_size; + uint8_t prebuffer_offset; + + fluf_cbor_ll_decoder_state_t state; + bool needs_preprocessing; + bool after_tag; + /** + * This structure contains information about currently processed value. The + * value is "processed" as long as it is not fully consumed, so for example, + * the current_item::value_type is of type "bytes" until it gets read + * entirely by the user. + */ + struct { + /* Type to be decoded or currently being decoded. */ + fluf_cbor_ll_value_type_t value_type; + + /* Initial CBOR header byte of the value currently being decoded. */ + uint8_t initial_byte; + } current_item; + + fluf_cbor_ll_subparser_type_t subparser_type; + union { + fluf_cbor_ll_decoder_bytes_ctx_t bytes_or_string_time; +# ifdef FLUF_WITH_CBOR_DECIMAL_FRACTIONS + struct { + size_t array_level; + bool entered_array; + double exponent; + double mantissa; + } decimal_fraction; +# endif // FLUF_WITH_CBOR_DECIMAL_FRACTIONS + } subparser; + +# if FLUF_MAX_CBOR_NEST_STACK_SIZE > 0 + size_t nest_stack_size; + /** + * A stack of recently entered nested types (e.g. arrays/maps). The type + * lands on a nest_stack, if one of the following functions is called: + * - fluf_cbor_ll_decoder_enter_array(), + * - fluf_cbor_ll_decoder_enter_map(). + * + * The last element (if any) indicates what kind of recursive structure we + * are currently parsing. If too many nest levels are found, the parser + * exits with error. + */ + fluf_cbor_ll_nested_state_t nest_stack[FLUF_MAX_CBOR_NEST_STACK_SIZE]; +# endif // FLUF_MAX_CBOR_NEST_STACK_SIZE > 0 +}; + +/** + * Initializes the low-level CBOR decoder. + * + * @param[out] ctx The context variable to initialize. It will be zeroed out, + * and reset to the initial valid state. + */ +void fluf_cbor_ll_decoder_init(fluf_cbor_ll_decoder_t *ctx); + +/** + * Provides a data buffer to be parsed by @p ctx. + * + * IMPORTANT: Only the pointer to @p buff is stored, so the + * buffer pointed to by that variable has to stay valid until the decoder is + * discarded, or another payload is provided. + * + * NOTE: It is only valid to provide the input buffer either + * immediately after calling @ref fluf_cbor_ll_decoder_init, or after some + * operation has returned @ref FLUF_IO_WANT_NEXT_PAYLOAD. + * + * NOTE: The decoder may read-ahead up to 9 bytes of data + * before actually attempting to decode it. This means that the decoder may + * request further data chunks even to access elements that are fully contained + * in the currently available chunk. Those will be decoded from the read-ahead + * buffer after providing further data. + * + * @param ctx The CBOR decoder context to operate on. + * + * @param buff Pointer to a buffer containing incoming payload. + * + * @param buff_len Size of @p buff in bytes. + * + * @param payload_finished Specifies whether the buffer passed is the last chunk + * of a larger payload (e.g. last block of a CoAP + * blockwise transfer). + * + * If determining that in advance is impractical, it is + * permitted to always pass chunks with this flag set to + * false, and then after next + * @ref FLUF_IO_WANT_NEXT_PAYLOAD, pass a chunk of size + * 0 with this flag set to true. + * + * @returns + * - 0 on success + * - @ref FLUF_IO_ERR_LOGIC if the context is not in a state in which providing + * a new payload is possible + */ +int fluf_cbor_ll_decoder_feed_payload(fluf_cbor_ll_decoder_t *ctx, + const void *buff, + size_t buff_len, + bool payload_finished); + +/** + * Checks if the CBOR decoder is in some error / exceptional state. + * + * @param ctx The CBOR decoder context to operate on. + * + * @returns + * - 0 if the decoder is in a valid state, ready for any of the data consumption + * functions + * - @ref FLUF_IO_EOF if the decoder has reached the end of payload successfully + * - @ref FLUF_IO_WANT_NEXT_PAYLOAD if the decoder is in the middle of parsing + * some value and determining the next steps requires calling + * @ref fluf_cbor_ll_decoder_feed_payload + * - @ref FLUF_IO_ERR_FORMAT if an error occurred earlier during parsing and + * the decoder can no longer be used + */ +int fluf_cbor_ll_decoder_errno(fluf_cbor_ll_decoder_t *ctx); + +/** + * Returns the type of the current value that can be (or currently is) extracted + * from the context. + * + * Before consuming (or preparing to consumption in some cases) the value with + * one of the: + * - @ref fluf_cbor_ll_decoder_null(), + * - @ref fluf_cbor_ll_decoder_bool(), + * - @ref fluf_cbor_ll_decoder_number(), + * - @ref fluf_cbor_ll_decoder_bytes(), + * - @ref fluf_cbor_ll_decoder_enter_array(), + * - @ref fluf_cbor_ll_decoder_enter_map() + * + * the function is guaranteed to return same results each time it is called. + * + * @param[in] ctx The CBOR decoder context to operate on. + * + * @param[out] out_type Ponter to a variable where next type shall be stored. + * + * @returns + * - 0 on success + * - @ref FLUF_IO_WANT_NEXT_PAYLOAD if end of the current payload has been + * reached and calling @ref fluf_cbor_ll_decoder_feed_payload is necessary to + * access the next value + * - @ref FLUF_IO_ERR_FORMAT if an error occurred while parsing the data + * - @ref FLUF_IO_ERR_LOGIC if the end-of-stream has already been reached + */ +int fluf_cbor_ll_decoder_current_value_type( + fluf_cbor_ll_decoder_t *ctx, fluf_cbor_ll_value_type_t *out_type); + +/** + * Consumes a simple null value. + * + * NOTE: May only be called when the next value type is @ref + * FLUF_CBOR_LL_VALUE_NULL, otherwise an error will be reported. + * + * @param[in] ctx The CBOR decoder context to operate on. + * + * @returns + * - 0 on success + * - @ref FLUF_IO_WANT_NEXT_PAYLOAD if end of the current payload has been + * reached and calling @ref fluf_cbor_ll_decoder_feed_payload is necessary to + * access the next value + * - @ref FLUF_IO_ERR_FORMAT if an error occurred while parsing the data + * - @ref FLUF_IO_ERR_LOGIC if the end-of-stream has already been reached + */ +int fluf_cbor_ll_decoder_null(fluf_cbor_ll_decoder_t *ctx); + +/** + * Consumes a simple boolean value. + * + * NOTE: May only be called when the next value type is @ref + * FLUF_CBOR_LL_VALUE_BOOL, otherwise an error will be reported. + * + * @param[in] ctx The CBOR decoder context to operate on. + * + * @param[out] out_value Pointer to a variable where the value shall be stored. + * + * @returns + * - 0 on success + * - @ref FLUF_IO_WANT_NEXT_PAYLOAD if end of the current payload has been + * reached and calling @ref fluf_cbor_ll_decoder_feed_payload is necessary to + * access the next value + * - @ref FLUF_IO_ERR_FORMAT if an error occurred while parsing the data + * - @ref FLUF_IO_ERR_LOGIC if the end-of-stream has already been reached + */ +int fluf_cbor_ll_decoder_bool(fluf_cbor_ll_decoder_t *ctx, bool *out_value); + +/** + * Consumes a scalar value from the context. + * + * NOTE: May only be called when the next value type is either: + * - @ref FLUF_CBOR_LL_VALUE_UINT, + * - @ref FLUF_CBOR_LL_VALUE_NEGATIVE_INT, + * - @ref FLUF_CBOR_LL_VALUE_FLOAT, + * - @ref FLUF_CBOR_LL_VALUE_DOUBLE, + * - @ref FLUF_CBOR_LL_VALUE_TIMESTAMP - in this case, the type identified in + * out_value->type will reflect the actual underlying data type, i.e. + * out_value->type will never be FLUF_CBOR_LL_VALUE_TIMESTAMP + * + * @param[in] ctx The CBOR decoder context to operate on. + * + * @param[out] out_value Pointer to a variable where the value shall be stored. + * + * @returns + * - 0 on success + * - @ref FLUF_IO_WANT_NEXT_PAYLOAD if end of the current payload has been + * reached and calling @ref fluf_cbor_ll_decoder_feed_payload is necessary to + * access the next value + * - @ref FLUF_IO_ERR_FORMAT if an error occurred while parsing the data + * - @ref FLUF_IO_ERR_LOGIC if the end-of-stream has already been reached + */ +int fluf_cbor_ll_decoder_number(fluf_cbor_ll_decoder_t *ctx, + fluf_cbor_ll_number_t *out_value); + +/** + * Prepares for consumption of a byte or text stream element. + * + * NOTE: May only be called when the next value type is either: + * - @ref FLUF_CBOR_LL_VALUE_BYTE_STRING, + * - @ref FLUF_CBOR_LL_VALUE_TEXT_STRING. + * + * After successfully calling this function, you shall call @ref + * fluf_cbor_ll_decoder_bytes_get_some, possibly multiple times until it sets + * the *out_message_finished argument to true, to access the + * actual data. + * + * @param[in] ctx The CBOR decoder context to operate on. + * + * @param[out] out_bytes_ctx Pointer to a variable where the bytes context + * pointer shall be stored. + * + * @param[out] out_total_size Pointer to a variable where the total size of the + * bytes element will be stored. If the element has + * an indefinite size, @ref + * FLUF_CBOR_LL_DECODER_ITEMS_INDEFINITE (-1) will be + * stored - the calling code will need to rely on the + * out_message_finished argument to @ref + * fluf_cbor_ll_decoder_bytes_get_some instead. If + * this argument is NULL, it will be ignored. + * + * @returns + * - 0 on success + * - @ref FLUF_IO_WANT_NEXT_PAYLOAD if end of the current payload has been + * reached and calling @ref fluf_cbor_ll_decoder_feed_payload is necessary to + * access the next value + * - @ref FLUF_IO_ERR_FORMAT if an error occurred while parsing the data + * - @ref FLUF_IO_ERR_LOGIC if the end-of-stream has already been reached + */ +int fluf_cbor_ll_decoder_bytes(fluf_cbor_ll_decoder_t *ctx, + fluf_cbor_ll_decoder_bytes_ctx_t **out_bytes_ctx, + ptrdiff_t *out_total_size); + +/** + * Consumes some amount of bytes from a byte or text stream element. + * + * This function shall be called after a successful call to @ref + * fluf_cbor_ll_decoder_bytes, as many times as necessary until the + * *out_message_finished argument is set to true, to eventually + * access and consume the entire stream. + * + * NOTE: The consumed data is not copied - a pointer to either + * the previously provided input buffer, or the context's internal read-ahead + * buffer, is returned instead. + * + * @param[in] bytes_ctx Bytes context pointer previously returned + * by @ref fluf_cbor_ll_decoder_bytes. + * + * @param[out] out_buf Pointer to a variable that will be set to a + * pointer to some portion of the stream. + * + * @param[out] out_buf_size Pointer to a variable that will be set to + * the number of bytes immediately available at + * *out_buf. Note that this might only + * be a part of the total size of the string + * element. + * + * @param[out] out_message_finished Pointer to a variable that will be set to + * true if the currently returned block + * is the last portion of the string - + * otherwise false. Note that the last + * block may have a length of 0. + * + * @returns + * - 0 on success + * - @ref FLUF_IO_WANT_NEXT_PAYLOAD if end of the current payload has been + * reached and calling @ref fluf_cbor_ll_decoder_feed_payload is necessary to + * access further part of the byte stream + * - @ref FLUF_IO_ERR_FORMAT if an error occurred while parsing the data + * - @ref FLUF_IO_ERR_LOGIC if the end-of-stream has already been reached + */ +int fluf_cbor_ll_decoder_bytes_get_some( + fluf_cbor_ll_decoder_bytes_ctx_t *bytes_ctx, + const void **out_buf, + size_t *out_buf_size, + bool *out_message_finished); + +# if FLUF_MAX_CBOR_NEST_STACK_SIZE > 0 +/** + * Prepares to the consumption of the array. + * + * NOTE: May only be called when the next value type is @ref + * FLUF_CBOR_LL_VALUE_ARRAY. + * + * NOTE: The decoder has a limit of structure nesting levels. Any payload with + * higher nesting degree will be rejected by the decoder by entering the error + * state. + * + * @param[in] ctx The CBOR decoder context to operate on. + * + * @param[out] out_size Pointer to a variable where the total number of elements + * in the array will be stored. If the array has an + * indefinite size, @ref + * FLUF_CBOR_LL_DECODER_ITEMS_INDEFINITE (-1) will be + * stored - the calling code will need to rely on the @ref + * fluf_cbor_ll_decoder_nesting_level function to determine + * the end of the array instead. If this argument is + * NULL, it will be ignored. + * + * @returns + * - 0 on success + * - @ref FLUF_IO_WANT_NEXT_PAYLOAD if end of the current payload has been + * reached and calling @ref fluf_cbor_ll_decoder_feed_payload is necessary to + * access the next value + * - @ref FLUF_IO_ERR_FORMAT if an error occurred while parsing the data + * - @ref FLUF_IO_ERR_LOGIC if the end-of-stream has already been reached + */ +int fluf_cbor_ll_decoder_enter_array(fluf_cbor_ll_decoder_t *ctx, + ptrdiff_t *out_size); + +/** + * Prepares to the consumption of the map. + * + * NOTE: May only be called when the next value type is @ref + * FLUF_CBOR_LL_VALUE_MAP. + * + * NOTE: The decoder has a limit of structure nesting levels. Any payload with + * higher nesting degree will be rejected by the decoder by entering the error + * state. + * + * @param[in] ctx The CBOR decoder context to operate on. + * + * @param[out] out_pair_count Pointer to a variable where the total number of + * element pairs in the array will + * be stored. If the array has an indefinite size, + * @ref FLUF_CBOR_LL_DECODER_ITEMS_INDEFINITE (-1) + * will be stored - the calling code will need to + * rely on the @ref + * fluf_cbor_ll_decoder_nesting_level function to + * determine the end of the map instead. If this + * argument is NULL, it will be ignored. + * + * @returns + * - 0 on success + * - @ref FLUF_IO_WANT_NEXT_PAYLOAD if end of the current payload has been + * reached and calling @ref fluf_cbor_ll_decoder_feed_payload is necessary to + * access the next value + * - @ref FLUF_IO_ERR_FORMAT if an error occurred while parsing the data + * - @ref FLUF_IO_ERR_LOGIC if the end-of-stream has already been reached + */ +int fluf_cbor_ll_decoder_enter_map(fluf_cbor_ll_decoder_t *ctx, + ptrdiff_t *out_pair_count); + +/** + * Gets the number of compound entites that the parser is currently inside. + * + * The number is incremented by 1 after a successfull call to @ref + * fluf_cbor_ll_decoder_enter_array or @ref fluf_cbor_ll_decoder_enter_map, and + * decreased after reading the last element of that array or map. In particular, + * if the array or map has zero elements, its value will not be visibly + * incremented at all. + * + * Note that if a decoding error occurred, the nesting level is assumed to be 0 + * instead of returning an explicit error. + * + * @param[in] ctx The CBOR decoder context to operate on. + * + * @param[out] out_nesting_level Pointer to a variable where the current nesting + * level will be stored. + * + * @returns + * - 0 on success + * - @ref FLUF_IO_WANT_NEXT_PAYLOAD if end of the current payload has been + * reached and calling @ref fluf_cbor_ll_decoder_feed_payload is necessary to + * access the next value + */ +int fluf_cbor_ll_decoder_nesting_level(fluf_cbor_ll_decoder_t *ctx, + size_t *out_nesting_level); +# endif // FLUF_MAX_CBOR_NEST_STACK_SIZE > 0 + +# ifdef __cplusplus +} +# endif + +#endif // defined(FLUF_WITH_SENML_CBOR) || defined(FLUF_WITH_LWM2M_CBOR) || + // defined(FLUF_WITH_CBOR) + +#endif // FLUF_IO_CBOR_DECODER_LL_H diff --git a/include_public/fluf/fluf_cbor_encoder_ll.h b/include_public/fluf/fluf_cbor_encoder_ll.h new file mode 100644 index 00000000..c2505ccb --- /dev/null +++ b/include_public/fluf/fluf_cbor_encoder_ll.h @@ -0,0 +1,69 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#ifndef FLUF_IO_CBOR_ENCODER_LL_H +#define FLUF_IO_CBOR_ENCODER_LL_H + +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * This is a stateless low-level CBOR encoder. + * + * The correctness of cbor message construction is not checked here. + * + * Important notice! + * All functions return the number of bytes written to the buffer. The maximum + * number of bytes that can be written to the buffer during a single function + * call is 9. Therefore, this is the minimum size of the input buffer for which + * an overflow will never occur. To keep the code simple here, the buffer size + * is not checked. Ensuring the security of the operation is on the side of the + * user. + * + * With string and bytes, only the header with size and type is written, the + * data stream must be written outside of this encoder after that. + */ + +#define FLUF_CBOR_LL_SINGLE_CALL_MAX_LEN 9 + +size_t fluf_cbor_ll_encode_uint(void *buff, uint64_t value); + +size_t fluf_cbor_ll_encode_int(void *buff, int64_t value); + +size_t fluf_cbor_ll_encode_bool(void *buff, bool value); + +size_t fluf_cbor_ll_encode_float(void *buff, float value); + +size_t fluf_cbor_ll_encode_double(void *buff, double value); + +size_t fluf_cbor_ll_encode_tag(void *buff, uint64_t value); + +size_t fluf_cbor_ll_string_begin(void *buff, size_t size); + +size_t fluf_cbor_ll_bytes_begin(void *buff, size_t size); + +size_t fluf_cbor_ll_definite_map_begin(void *buff, size_t items_count); + +size_t fluf_cbor_ll_definite_array_begin(void *buff, size_t items_count); + +size_t fluf_cbor_ll_indefinite_map_begin(void *buff); + +size_t fluf_cbor_ll_indefinite_map_end(void *buff); + +#ifdef __cplusplus +} +#endif + +#endif // FLUF_IO_CBOR_ENCODER_LL_H diff --git a/include_public/fluf/fluf_config.h b/include_public/fluf/fluf_config.h new file mode 100644 index 00000000..c0a88423 --- /dev/null +++ b/include_public/fluf/fluf_config.h @@ -0,0 +1,25 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#ifndef FLUF_CONFIG_H +#define FLUF_CONFIG_H + +#ifndef FLUF_MAX_ALLOWED_OPTIONS_NUMBER +# define FLUF_MAX_ALLOWED_OPTIONS_NUMBER 15 +#endif // FLUF_MAX_ALLOWED_OPTIONS_NUMBER + +#ifndef FLUL_MAX_ALLOWED_LOCATION_PATHS_NUMBER +# define FLUL_MAX_ALLOWED_LOCATION_PATHS_NUMBER 1 +#endif // FLUL_MAX_ALLOWED_LOCATION_PATHS_NUMBER + +#ifndef FLUF_ATTR_OPTION_MAX_SIZE +# define FLUF_ATTR_OPTION_MAX_SIZE 100 +#endif // FLUF_ATTR_OPTION_MAX_SIZE + +#endif // FLUF_CONFIG_H diff --git a/include_public/fluf/fluf_defs.h b/include_public/fluf/fluf_defs.h new file mode 100644 index 00000000..cefda663 --- /dev/null +++ b/include_public/fluf/fluf_defs.h @@ -0,0 +1,448 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#ifndef FLUF_DEFS_H +#define FLUF_DEFS_H + +#include +#include +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * CoAP Content-Formats, as defined in "Constrained RESTful Environments (CoRE) + * Parameters": + * https://www.iana.org/assignments/core-parameters/core-parameters.xhtml + * @{ + */ +#define FLUF_COAP_FORMAT_NOT_DEFINED 0xFFFF + +#define FLUF_COAP_FORMAT_PLAINTEXT 0 +#define FLUF_COAP_FORMAT_LINK_FORMAT 40 +#define FLUF_COAP_FORMAT_OPAQUE_STREAM 42 +#define FLUF_COAP_FORMAT_CBOR 60 +#define FLUF_COAP_FORMAT_SENML_JSON 110 +#define FLUF_COAP_FORMAT_SENML_CBOR 112 +#define FLUF_COAP_FORMAT_SENML_ETCH_JSON 320 +#define FLUF_COAP_FORMAT_SENML_ETCH_CBOR 322 +#define FLUF_COAP_FORMAT_OMA_LWM2M_TLV 11542 +#define FLUF_COAP_FORMAT_OMA_LWM2M_JSON 11543 +#define FLUF_COAP_FORMAT_OMA_LWM2M_CBOR 11544 + +#define FLUF_OBJ_ID_SECURITY 0U +#define FLUF_OBJ_ID_SERVER 1U +#define FLUF_OBJ_ID_ACCESS_CONTROL 2U +#define FLUF_OBJ_ID_OSCORE 21U + +/** Object ID */ +typedef uint16_t fluf_oid_t; + +/** Object Instance ID */ +typedef uint16_t fluf_iid_t; + +/** Resource ID */ +typedef uint16_t fluf_rid_t; + +/** Resource Instance ID */ +typedef uint16_t fluf_riid_t; + +/** + * Internal macros used for constructing/parsing CoAP codes. + * @{ + */ +#define _FLUF_COAP_CODE_CLASS_MASK 0xE0 +#define _FLUF_COAP_CODE_CLASS_SHIFT 5 +#define _FLUF_COAP_CODE_DETAIL_MASK 0x1F +#define _FLUF_COAP_CODE_DETAIL_SHIFT 0 + +#define FLUF_COAP_CODE(cls, detail) \ + ((((cls) << _FLUF_COAP_CODE_CLASS_SHIFT) & _FLUF_COAP_CODE_CLASS_MASK) \ + | (((detail) << _FLUF_COAP_CODE_DETAIL_SHIFT) \ + & _FLUF_COAP_CODE_DETAIL_MASK)) + +/** + * @anchor fluf_coap_code_constants + * @name fluf_coap_code_constants + * + * CoAP code constants, as defined in RFC7252/RFC7959. + * + * For detailed description of their semantics, refer to appropriate RFCs. + * @{ + */ +// clang-format off +#define FLUF_COAP_CODE_EMPTY FLUF_COAP_CODE(0, 0) + +#define FLUF_COAP_CODE_GET FLUF_COAP_CODE(0, 1) +#define FLUF_COAP_CODE_POST FLUF_COAP_CODE(0, 2) +#define FLUF_COAP_CODE_PUT FLUF_COAP_CODE(0, 3) +#define FLUF_COAP_CODE_DELETE FLUF_COAP_CODE(0, 4) +/** https://tools.ietf.org/html/rfc8132#section-4 */ +#define FLUF_COAP_CODE_FETCH FLUF_COAP_CODE(0, 5) +#define FLUF_COAP_CODE_PATCH FLUF_COAP_CODE(0, 6) +#define FLUF_COAP_CODE_IPATCH FLUF_COAP_CODE(0, 7) + +#define FLUF_COAP_CODE_CREATED FLUF_COAP_CODE(2, 1) +#define FLUF_COAP_CODE_DELETED FLUF_COAP_CODE(2, 2) +#define FLUF_COAP_CODE_VALID FLUF_COAP_CODE(2, 3) +#define FLUF_COAP_CODE_CHANGED FLUF_COAP_CODE(2, 4) +#define FLUF_COAP_CODE_CONTENT FLUF_COAP_CODE(2, 5) +#define FLUF_COAP_CODE_CONTINUE FLUF_COAP_CODE(2, 31) + +#define FLUF_COAP_CODE_BAD_REQUEST FLUF_COAP_CODE(4, 0) +#define FLUF_COAP_CODE_UNAUTHORIZED FLUF_COAP_CODE(4, 1) +#define FLUF_COAP_CODE_BAD_OPTION FLUF_COAP_CODE(4, 2) +#define FLUF_COAP_CODE_FORBIDDEN FLUF_COAP_CODE(4, 3) +#define FLUF_COAP_CODE_NOT_FOUND FLUF_COAP_CODE(4, 4) +#define FLUF_COAP_CODE_METHOD_NOT_ALLOWED FLUF_COAP_CODE(4, 5) +#define FLUF_COAP_CODE_NOT_ACCEPTABLE FLUF_COAP_CODE(4, 6) +#define FLUF_COAP_CODE_REQUEST_ENTITY_INCOMPLETE FLUF_COAP_CODE(4, 8) +#define FLUF_COAP_CODE_PRECONDITION_FAILED FLUF_COAP_CODE(4, 12) +#define FLUF_COAP_CODE_REQUEST_ENTITY_TOO_LARGE FLUF_COAP_CODE(4, 13) +#define FLUF_COAP_CODE_UNSUPPORTED_CONTENT_FORMAT FLUF_COAP_CODE(4, 15) + +#define FLUF_COAP_CODE_INTERNAL_SERVER_ERROR FLUF_COAP_CODE(5, 0) +#define FLUF_COAP_CODE_NOT_IMPLEMENTED FLUF_COAP_CODE(5, 1) +#define FLUF_COAP_CODE_BAD_GATEWAY FLUF_COAP_CODE(5, 2) +#define FLUF_COAP_CODE_SERVICE_UNAVAILABLE FLUF_COAP_CODE(5, 3) +#define FLUF_COAP_CODE_GATEWAY_TIMEOUT FLUF_COAP_CODE(5, 4) +#define FLUF_COAP_CODE_PROXYING_NOT_SUPPORTED FLUF_COAP_CODE(5, 5) +// clang-format on + +#define FLUF_COAP_MAX_TOKEN_LENGTH 8 + +/** CoAP token object. */ +typedef struct { + uint8_t size; + char bytes[FLUF_COAP_MAX_TOKEN_LENGTH]; +} fluf_coap_token_t; + +/** + * CoAP message type, as defined in RFC 7252. + */ +typedef enum { + FLUF_COAP_UDP_TYPE_CONFIRMABLE, + FLUF_COAP_UDP_TYPE_NON_CONFIRMABLE, + FLUF_COAP_UDP_TYPE_ACKNOWLEDGEMENT, + FLUF_COAP_UDP_TYPE_RESET +} fluf_coap_udp_type_t; + +typedef struct { + fluf_coap_token_t token; + uint16_t message_id; + fluf_coap_udp_type_t type; +} fluf_coap_udp_t; + +typedef union { + fluf_coap_udp_t coap_udp; +} fluf_coap_msg_t; + +typedef enum { + FLUF_ID_OID, + FLUF_ID_IID, + FLUF_ID_RID, + FLUF_ID_RIID, + FLUF_URI_PATH_MAX_LENGTH +} fluf_id_type_t; + +typedef struct { + uint16_t ids[FLUF_URI_PATH_MAX_LENGTH]; + size_t uri_len; +} fluf_uri_path_t; + +/** defines entry type */ +typedef uint16_t fluf_data_type_t; + +/** + * Null data type. It will be returned by the input context in the following + * situations: + * + * - when parsing a Composite-Read request payload + * - when parsing a SenML-ETCH JSON/CBOR payload for a Write-Composite operation + * and an entry without a value, requesting a removal of a specific Resource + * Instance, is encountered + * - when parsing a TLV or LwM2M CBOR payload and an aggregate (e.g. Object + * Instance or a multi-instance Resource) with zero nested elements is + * encountered + * + * @ref fluf_res_value_t is not used for null data. + */ +#define FLUF_DATA_TYPE_NULL ((fluf_data_type_t) 0) + +/** + * "Opaque" data type, as defined in Appendix C of the LwM2M spec. + * + * The bytes_or_string field of @ref fluf_res_value_t is used to pass the + * actual data. + */ +#define FLUF_DATA_TYPE_BYTES ((fluf_data_type_t) (1 << 0)) + +/** + * "String" data type, as defined in Appendix C of the LwM2M spec. + * + * May also be used to represent the "Corelnk" type, as those two are + * indistinguishable on the wire. + * + * The bytes_or_string field of @ref fluf_res_value_t is used to pass the + * actual data. + */ +#define FLUF_DATA_TYPE_STRING ((fluf_data_type_t) (1 << 1)) + +/** + * "Integer" data type, as defined in Appendix C of the LwM2M spec. + * + * The int_value field of @ref fluf_res_value_t is used to pass the + * actual data. + */ +#define FLUF_DATA_TYPE_INT ((fluf_data_type_t) (1 << 2)) + +/** + * "Float" data type, as defined in Appendix C of the LwM2M spec. + * + * The double_value field of @ref fluf_res_value_t is used to pass the + * actual data. + */ +#define FLUF_DATA_TYPE_DOUBLE ((fluf_data_type_t) (1 << 3)) + +/** + * "Boolean" data type, as defined in Appendix C of the LwM2M spec. + * + * The bool_value field of @ref fluf_res_value_t is used to pass the + * actual data. + */ +#define FLUF_DATA_TYPE_BOOL ((fluf_data_type_t) (1 << 4)) + +/** + * "Objlnk" data type, as defined in Appendix C of the LwM2M spec. + * + * The objlnk field of @ref fluf_res_value_t is used to pass the actual + * data. + */ +#define FLUF_DATA_TYPE_OBJLNK ((fluf_data_type_t) (1 << 5)) + +/** + * "Unsigned Integer" data type, as defined in Appendix C of the LwM2M spec. + * + * The uint_value field of @ref fluf_res_value_t is used to pass the + * actual data. + */ +#define FLUF_DATA_TYPE_UINT ((fluf_data_type_t) (1 << 6)) + +/** + * "Time" data type, as defined in Appendix C of the LwM2M spec. + * + * The time_value field of @ref fluf_res_value_t is used to pass the + * actual data. + */ +#define FLUF_DATA_TYPE_TIME ((fluf_data_type_t) (1 << 7)) + +/** + * When a bit mask of data types is applicable, this constant can be used to + * specify all supported data types. + * + * Note that it does NOT include @ref + * FLUF_DATA_TYPE_FLAG_EXTERNAL, and that @ref FLUF_DATA_TYPE_NULL, having a + * a numeric value of 0, does not participate in bit masks. + */ +#define FLUF_DATA_TYPE_ANY \ + ((fluf_data_type_t) (FLUF_DATA_TYPE_BYTES | FLUF_DATA_TYPE_STRING \ + | FLUF_DATA_TYPE_INT | FLUF_DATA_TYPE_DOUBLE \ + | FLUF_DATA_TYPE_BOOL | FLUF_DATA_TYPE_OBJLNK \ + | FLUF_DATA_TYPE_UINT | FLUF_DATA_TYPE_TIME)) + +/** + * A flag that can be ORed with either @ref FLUF_DATA_TYPE_BYTES or @ref + * FLUF_DATA_TYPE_STRING to signify that the data is provided through an + * external callback. Only valid for output contexts. + * + * The external_data field of @ref fluf_res_value_t is then used to pass + * the actual data. + */ +#define FLUF_DATA_TYPE_FLAG_EXTERNAL ((fluf_data_type_t) (1 << 15)) + +/** + * "Opaque" data type, as defined in Appendix C of the LwM2M spec, provided + * through an external callback. Only valid for output contexts. + * + * The external_data field of @ref fluf_res_value_t is used to pass the + * actual data. + */ +#define FLUF_DATA_TYPE_EXTERNAL_BYTES \ + ((fluf_data_type_t) (FLUF_DATA_TYPE_BYTES | FLUF_DATA_TYPE_FLAG_EXTERNAL)) + +/** + * "String" data type, as defined in Appendix C of the LwM2M spec, provided + * through an external callback. Only valid for output contexts. + * + * May also be used to represent the "Corelnk" type, as those two are + * indistinguishable on the wire. + * + * The external_data field of @ref fluf_res_value_t is used to pass the + * actual data. + */ +#define FLUF_DATA_TYPE_EXTERNAL_STRING \ + ((fluf_data_type_t) (FLUF_DATA_TYPE_STRING | FLUF_DATA_TYPE_FLAG_EXTERNAL)) + +/** + * A handler that get binary/string data from external source. + * Will be called if @ref fluf_data_type_t is set to @ref + * FLUF_DATA_TYPE_EXTERNAL_BYTES or @ref FLUF_DATA_TYPE_EXTERNAL_STRING. + * Can be called multiple times. + * + * @param buffer Buffer to copy data. + * @param bytes_to_copy Number of bytes to be copied. + * @param offset From which memory offset to start copying. + * @param user_args User data passed when the function is called. + * + * @return 0 on success, a negative value in case of error. + */ +typedef int fluf_get_external_data_t(void *buffer, + size_t bytes_to_copy, + size_t offset, + void *user_args); + +/** + * Stores a complete or partial value of a data model entry, check "Data Types" + * appendix in LwM2M specification for more information. + */ +typedef union { + /** + * Chunk of information valid for when the underlying data type is + * @ref FLUF_DATA_TYPE_BYTES or @ref FLUF_DATA_TYPE_STRING. + */ + struct { + /** + * Pointer to the data buffer. + */ + void *data; + + /** + * Offset, in bytes, of the entire resource value, that the data + * field points to. + * + * For output contexts (e.g. responding to a Read operation), this + * currently MUST be 0. Please use the external_data variant for + * outputting large resources. + * + * For input contexts (e.g. parsing a Write operation payload), non-zero + * values will be returned when parsing large resources that span + * multiple data packtes. + */ + size_t offset; + + /** + * Length, in bytes, of valid data at the buffer pointed to by + * data. + * + * For output contexts (e.g. responding to a Read operation) and + * resources of type @ref FLUF_DATA_TYPE_STRING, if you leave both + * chunk_length and full_length_hint as 0 and data + * is non-NULL, then data will be assumed to point to a + * null-terminated string and strlen() will be called to + * calculate its length instead. + */ + size_t chunk_length; + + /** + * Full length, in bytes, of the entire resource, if available. If all + * three of offset, chunk_length and + * full_length_hint are zero, this object refers to a zero-length + * resource. In all other cases, a value of 0 signifies that information + * about total length is not available. + * + * For output contexts (e.g. responding to a Read operation), this can + * be set to either 0 or a value equal to chunk_length. Other + * values will be treated as an error. + * + * For input contexts (e.g. parsing a Write operation payload), this + * will be set to 0 when parsing content formats that do not provide + * length information upfront (e.g. Plain Text or SenML JSON), until the + * entire resource is parsed. End of resource will always be marked with + * full_length_hint set to offset + chunk_length. + */ + size_t full_length_hint; + } bytes_or_string; + + /** + * Configuration for resources generated using an external data callback, + * valid for output contexts only, when the underlying data type is + * @ref FLUF_DATA_TYPE_EXTERNAL_BYTES or + * @ref FLUF_DATA_TYPE_EXTERNAL_STRING. + */ + struct { + /** + * Callback that will be called to request a chunk of data. + */ + fluf_get_external_data_t *get_external_data; + + /** + * Opaque pointer that will be passed to get_external_data and + * may be used by the user to pass additional context. + */ + void *user_args; + + /** + * Total length in bytes of the resource data. This is necessary for + * some data types (e.g. CBOR-based ones) that require encoding the + * length before the data. In case of string resources, this shall NOT + * include any null terminator characters. + */ + size_t length; + } external_data; + + /** + * Integer value, valid when the underlying data type is + * @ref FLUF_DATA_TYPE_INT. + */ + int64_t int_value; + + /** + * Unsigned Integer value, valid when the underlying data type is + * @ref FLUF_DATA_TYPE_UINT. + */ + uint64_t uint_value; + + /** + * Double-precision floating-point value, valid when the underlying data + * type is @ref FLUF_DATA_TYPE_DOUBLE. + */ + double double_value; + + /** + * Boolean value, valid when the underlying data type is + * @ref FLUF_DATA_TYPE_BOOL. + */ + bool bool_value; + + /** + * Objlnk value, valid when the underlying data type is + * @ref FLUF_DATA_TYPE_OBJLNK. + */ + struct { + fluf_oid_t oid; + fluf_iid_t iid; + } objlnk; + + /** + * Time value, expressed as a UNIX timestamp, valid when the underlying data + * type is @ref FLUF_DATA_TYPE_TIME. + */ + int64_t time_value; +} fluf_res_value_t; + +#ifdef __cplusplus +} +#endif + +#endif // FLUF_DEFS_H diff --git a/include_public/fluf/fluf_io.h b/include_public/fluf/fluf_io.h new file mode 100644 index 00000000..3b9ade5e --- /dev/null +++ b/include_public/fluf/fluf_io.h @@ -0,0 +1,1033 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#ifndef FLUF_IO_H +#define FLUF_IO_H + +#include +#include +#include + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Invalid input arguments. */ +#define FLUF_IO_ERR_INPUT_ARG (-1) +/** Invalid data type. */ +#define FLUF_IO_ERR_IO_TYPE (-2) +/** Given format is unsupported or does not match the specified input data type. + */ +#define FLUF_IO_ERR_FORMAT (-3) +/** Invalid call. */ +#define FLUF_IO_ERR_LOGIC (-4) +/** Given path is not consistent with the value of depth. */ +#define FLUF_IO_WARNING_DEPTH (-5) + +/** There is no more data to return from an input context. */ +#define FLUF_IO_EOF 1 +/** + * Available payload has been exhausted. Please call + * @ref fluf_io_in_ctx_feed_payload again to continue parsing. If no more data + * is available, this shall be treated as an error. + */ +#define FLUF_IO_WANT_NEXT_PAYLOAD 2 +/** + * The payload format does not contain enough metadata to determine data type of + * the resource. Please call @ref fluf_io_in_ctx_get_entry again with a concrete + * data type specified. + */ +#define FLUF_IO_WANT_TYPE_DISAMBIGUATION 3 +/** + * Please call fluf_io_XXX_ctx_get_payload function again, there is more + * available data to be copied to the output buffer. + */ +#define FLUF_IO_NEED_NEXT_CALL 4 + +typedef struct { + /** defines entry type */ + fluf_data_type_t type; + /** entry value */ + fluf_res_value_t value; + /** resource path */ + fluf_uri_path_t path; + /** + * Entry timestamp, only meaningful for Send and Notify operations. + * + * Is ignored if set to NAN. + * + * This can be the actual Unix time in seconds if it is greater than or + * equal to 2**28s [RFC8428], or a negative value if the time is relative to + * the current time. To convert @ref avs_time_real_t to double you can use + * @ref avs_time_real_to_fscalar function with AVS_TIME_S flag. + */ + double timestamp; +} fluf_io_out_entry_t; + +/** Payload encoding context + * Do not modify this structure directly, its fields are changed during fluf_io + * api calls. + */ +typedef struct { + /** format used */ + uint16_t _format; + /** currently used entry */ + const fluf_io_out_entry_t *_entry; + /** internally stores a coded message for a single entry */ + fluf_io_buff_t _buff; + /** stores the encoder's internal ctx for the duration of the operation */ + union { + fluf_internal_cbor_encoder_t _cbor; + fluf_internal_senml_cbor_encoder_t _senml; + } _encoder; +} fluf_io_out_ctx_t; + +/** + * Must be called to prepare @p ctx to build response message payload. + * It initializes @p ctx and selects the appropriate encoder based on the other + * function arguments, provided that @p format is equal to + * FLUF_COAP_FORMAT_NOT_DEFINED. + * If @p items_count equals 1 and it is a _READ @p operation_type message, one + * of the simple encoders, such as CBOR or Plain Text, will be selected + * (depending on the project configuration). In the case of multiple records or + * SEND operations, one of the complex formatters such as SenML CBOR, SenML-ETCH + * CBOR or LWM2M CBOR will be used. If @p format is different from + * FLUF_COAP_FORMAT_NOT_DEFINED it is checked if it can be used for the given + * arguments. + * @p base_path does not need to be set in the case of a simple formatter. If a + * response to a READ request is being prepared then @p base_path must be set to + * the value indicated in the request. In other cases, its value should be set + * using the @ref FLUF_MAKE_ROOT_PATH() macro. + * + * @param ctx Context to operate on. + * + * @param operation_type Type of operation for which payload is being prepared. + * Only the following operations are supported and will + * result in successful initialization: + * - @ref FLUF_OP_DM_READ + * - @ref FLUF_OP_DM_READ_COMP + * - @ref FLUF_OP_INF_OBSERVE + * - @ref FLUF_OP_INF_OBSERVE_COMP + * - @ref FLUF_OP_INF_CANCEL_OBSERVE + * - @ref FLUF_OP_INF_CANCEL_OBSERVE_COMP + * - @ref FLUF_OP_INF_NON_CON_NOTIFY + * - @ref FLUF_OP_INF_SEND + * + * @param base_path Pointer to path. + * + * @param items_count Number of resources or resource instanes that will be + * in the message . + * + * @param format If value is different from @ref + * FLUF_COAP_FORMAT_NOT_DEFINED then it is taken into + * account . + * + * @return 0 on success, a negative value in case of invalid arguments. + */ +int fluf_io_out_ctx_init(fluf_io_out_ctx_t *ctx, + fluf_op_t operation_type, + const fluf_uri_path_t *base_path, + size_t items_count, + uint16_t format); + +/** + * Call to add new @p entry. + * During this call the @p entry is encoded with given format and internal + * buffer is filled with payload. After calling this function, you need to call + * @ref fluf_io_out_ctx_get_payload to copy the message. Remember that during + * the whole operation @p entry must not change and before next @ref + * fluf_io_out_ctx_new_entry call, the entire previous record must be copied. + * + * @param ctx Context to operate on. + * @param entry Single record. + * + * @return 0 on success, a negative value in case of error. + */ +int fluf_io_out_ctx_new_entry(fluf_io_out_ctx_t *ctx, + const fluf_io_out_entry_t *entry); + +/** + * Call to copy encoded message to payload buffer. + * + * The buffer into which the encoded message is written is given by @p out_buff. + * @p out_copied_bytes returns information about the number of bytes written + * during a single function call, If the function return @ref + * FLUF_IO_NEED_NEXT_CALL it means that the buffer space has run out. If you + * support block operation then at this point you can send the buffer as one + * message block and call this function again with the same @p entry and the + * rest of the record will be written. In case return value equals 0 then + * you can call @ref fluf_io_out_ctx_new_entry with another entry and @ref + * fluf_io_out_ctx_get_payload with pointer to the buffer shifted by the value + * of @p out_copied_bytes, also remember to update the value of @p out_buff_len. + * + * The @p out_buff already contains the LWM2M message payload. Use the @ref + * fluf_io_out_ctx_get_format function to check the format used. + * + * Example use (error checking omitted for brevity): + * @code + * // New out ctx + * fluf_io_out_ctx_t ctx; + * + * // Initializes ctx for READ operation response on single resource + * fluf_io_out_ctx_init(&ctx, FLUF_OP_DM_READ, NULL, 1, FLUF_COAP_FORMAT_CBOR); + * + * // Prepares resource record of int types + * fluf_io_out_entry_t entry; + * entry.type = FLUF_DATA_TYPE_INT; + * entry.value.int_value = 0x01; + * + * // Variables needed to call @ref fluf_io_out_ctx_serialize + * size_t payload_size = 0; + * uint8_t buff[50]; + * + * // Adds entry to the message, after these calls buff can be added to lwm2m + * // message as payload. Return value equals to FLUF_IO_NEED_NEXT_CALL it means + * // that the entry didn't fit in the output buffer and after sending the + * // payload as one part of block transfer fluf_io_out_ctx_serialize must be + * // called again with the same entry. + * fluf_io_out_ctx_new_entry(&ctx, &entry); + * fluf_io_out_ctx_get_payload(&ctx, buff, sizeof(buff), &payload_size); + * @endcode + * + * @param[out] out_buff Payload buffer. + * @param out_buff_len Length of payload buffer. + * @param[out] out_copied_bytes Number of bytes that are written into the + * buffer. + * @param ctx Context to operate on. + * + * @return + * - 0 on success, + * - FLUF_IO_NEED_NEXT_CALL if entry didn't fit in the output buffer and this + * function have to be called again, + * - FLUF_IO_ERR_LOGIC if this function is called but there is no more data in + * internal buffer, + * - error code returned by @ref fluf_get_external_data_t. + */ +int fluf_io_out_ctx_get_payload(fluf_io_out_ctx_t *ctx, + void *out_buff, + size_t out_buff_len, + size_t *out_copied_bytes); + +/** + * Returns the value of the currently used format. + * + * @param ctx Context to operate on. + * + * @return Format used. + */ +uint16_t fluf_io_out_ctx_get_format(fluf_io_out_ctx_t *ctx); + +/** + * Payload decoding context. + * + * Do not modify this structure directly, its fields are changed during fluf_io + * API calls. + */ +typedef struct { + /** format used */ + uint16_t _format; + /** stores the out value for the currently processed entry */ + fluf_res_value_t _out_value; + /** stores the out path for the currently processed entry */ + fluf_uri_path_t _out_path; + /** stores the decoder's internal ctx for the duration of the operation */ + union { + fluf_internal_tlv_decoder_t _tlv; + } _decoder; +} fluf_io_in_ctx_t; + +/** + * Initializes @p ctx so that it can be used to parse incoming payload + * containing data model data. + * + * @param[out] ctx Context variable to initialize. + * + * @param operation_type Type of operation for which payload is being parsed. + * Only the following operations are supported and will + * result in successful initialization: + * - @ref FLUF_OP_DM_READ_COMP + * - @ref FLUF_OP_DM_WRITE_REPLACE + * - @ref FLUF_OP_DM_WRITE_PARTIAL_UPDATE + * - @ref FLUF_OP_DM_WRITE_COMP + * - @ref FLUF_OP_DM_CREATE + * + * @param base_path URI path that has been specified in the operation + * parameters (i.e., CoAP options); may be NULL in + * case of the Composite operations. + * + * @param format CoAP Content-Format number specifying the format of + * incoming data. + * + * @return 0 on success, a negative value in case of invalid arguments. + */ +int fluf_io_in_ctx_init(fluf_io_in_ctx_t *ctx, + fluf_op_t operation_type, + const fluf_uri_path_t *base_path, + uint16_t format); + +/** + * Provides a data buffer to be parsed by @p ctx. + * + * IMPORTANT: Only the pointer to @p buff is stored, so the + * buffer pointed to by that variable has to stay valid until the input context + * is discarded, or another payload is provided. + * + * NOTE: @p buff is passed non-const and depending on the + * content format, it may be modified by the context. For example, base64 + * decoding of binary data, if necessary, will be performed in place by + * overwriting data in the buffer. Please copy the buffer first if you need + * to retain its original contents. + * + * NOTE: It is only valid to provide the input buffer either + * immediately after calling @ref fluf_io_in_ctx_init, or after @ref + * fluf_io_in_ctx_get_entry has returned @ref FLUF_IO_WANT_NEXT_PAYLOAD. + * + * @param ctx Input context to operate on. + * + * @param buff Pointer to a buffer containing incoming payload. + * + * @param buff_len Size of @p buff in bytes. + * + * @param payload_finished Specifies whether the buffer passed is the last chunk + * of a larger payload (e.g. last block of a CoAP + * blockwise transfer). + * + * If determining that in advance is impractical, it is + * permitted to always pass chunks with this flag set to + * false, and then after next + * @ref FLUF_IO_WANT_NEXT_PAYLOAD, pass a chunk of size + * 0 with this flag set to true. + * + * @return 0 on success, a negative value in case of invalid arguments, or if + * the payload could already be determined as unparsable during the + * initial parsing stage. + */ +int fluf_io_in_ctx_feed_payload(fluf_io_in_ctx_t *ctx, + void *buff, + size_t buff_len, + bool payload_finished); + +/** + * Retrieves the next entry parsed by the input context, either in full or in + * part. + * + * Resources of types: Integer, Unsigned Integer, Float, Boolean, Time, Objlnk, + * and entries without a value payload, are always returned after having been + * parsed in full. String and Opaque resources may be parsed in chunks. + * + * If an entry has been parsed in full, then @p inout_type_bitmask will be set + * to a concrete type (at most one bit will be set), @p out_path will be set, + * and if the type is not @ref FLUF_DATA_TYPE_NULL, @p out_value will also be + * populated. + * + * If a String or Opaque resource has been parsed in part, then + * @p inout_type_bitmask will be set to that concrete type, and @p out_value + * will be set to a partial chunk of the parsed value. @p out_path may be + * populated with the first chunk if available, in which case it will also be + * repeated with each subsequent chunk. However, it may also only be populated + * with the last chunk. In some formats (such as SenML CBOR) this may depend on + * the order of encoded elements. + * + * When processing a String or Opaque resource, the last chunk of the resource + * is signalled by out_value->bytes_or_string.full_length_hint being + * equal to out_value->bytes_or_string.offset + + * out_value->bytes_or_string.chunk_length and @p out_path + * being populated. If either of these conditions is not true while this + * function returned success and *inout_type_bitmask is either + * FLUF_DATA_TYPE_BYTES or FLUF_DATA_TYPE_STRING, then the next + * call to this function will return the next chunk of the same entry. Note that + * the final chunk may have a length of zero. + * + * If the last chunk of the payload did not contain enough data to provide the + * value in either its entirety or even in part, then the function returns + * @ref FLUF_IO_WANT_NEXT_PAYLOAD. Values of the output arguments shall be + * treated as undefined in that case. Parsing will be continued after the next + * portion of the input payload (e.g. the next payload from a CoAP blockwise + * transfer) is provided by calling @ref fluf_io_in_ctx_feed_payload - the user + * can then retry the call to this function. + * + * If the type of the resource cannot be reliably determined (e.g. in case of + * the Plain Text format), then @p out_value will not be populated and the + * function will return @ref FLUF_IO_WANT_TYPE_DISAMBIGUATION. The user shall + * then retry the call with the value of *inout_type_bitmask providing + * the single type as which the resource shall be parsed. + * + * In case @p out_value or @p out_path cannot be populated, either or both of + * them will be set to NULL. + * + * Below is an example of code that reads a payload from a file descriptor + * fd and prints out the parsed entries (using auxiliary functions that + * are omitted from this example for brevity). + * + * @code + * fluf_io_in_ctx_t ctx; + * int result = fluf_io_in_ctx_init(&ctx, FLUF_OP_DM_WRITE_COMP, NULL, + * FLUF_COAP_FORMAT_SENML_CBOR); + * if (result) { + * abort(); + * } + * result = FLUF_IO_WANT_NEXT_PAYLOAD; + * char buf[1024]; + * char *bytes_or_string_buf = NULL; + * fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_ANY; + * const fluf_res_value_t *value = NULL; + * const fluf_uri_path_t *path = NULL; + * while (result != FLUF_IO_EOF) { + * if (result == FLUF_IO_WANT_NEXT_PAYLOAD) { + * ssize_t bytes_count = read(fd, buf, sizeof(buf)); + * if (bytes_count < 0 + * || fluf_io_in_ctx_feed_payload(&ctx, buf, + * (size_t) bytes_count, + * bytes_count == 0)) { + * abort(); + * } + * } + * if ((result = fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, + * &path)) + * == FLUF_IO_WANT_TYPE_DISAMBIGUATION) { + * assert(path); + * type_bitmask = determine_type(type_bitmask, path); + * result = fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, + * &path); + * } + * if (result < 0 || result == FLUF_IO_WANT_TYPE_DISAMBIGUATION) { + * abort(); + * } + * if (result) { + * assert(result == FLUF_IO_EOF + * || result == FLUF_IO_WANT_NEXT_PAYLOAD); + * continue; + * } + * assert(value || type_bitmask == FLUF_DATA_TYPE_NULL); + * if (type_bitmask != FLUF_DATA_TYPE_BYTES + * && type_bitmask != FLUF_DATA_TYPE_STRING) { + * assert(path); + * process_resource(path, type_bitmask, value); + * } else { + * if (!(bytes_or_string_buf = (char *) realloc( + * bytes_or_string_buf, + * value->bytes_or_string.full_length_hint + * ? value->bytes_or_string.full_length_hint + * : value->bytes_or_string.offset + * + value->bytes_or_string + * .chunk_length))) { + * abort(); + * } + * memcpy(bytes_or_string_buf + value->bytes_or_string.offset, + * value->bytes_or_string.data, + * value->bytes_or_string.chunk_length); + * if (!path + * || value->bytes_or_string.offset + * + value->bytes_or_string.chunk_length + * != value->bytes_or_string.full_length_hint) { + * // Not the last chunk + * continue; + * } + * process_resource( + * path, type_bitmask, + * &(const fluf_res_value_t) { + * .bytes_or_string = { + * .data = bytes_or_string_buf, + * .offset = 0, + * .chunk_length = + * value->bytes_or_string.full_length_hint, + * .full_length_hint = + * value->bytes_or_string.full_length_hint + * } + * }); + * free(bytes_or_string_buf); + * bytes_or_string_buf = NULL; + * } + * type_bitmask = FLUF_DATA_TYPE_ANY; + * } + * @endcode + * + * @param ctx Context to operate on. + * + * @param[inout] inout_type_bitmask Bidirectional parameter controlling the type + * of the resource. On input, it shall be set + * to a bit mask of all the data types that the + * caller is prepared to handle. On output, + * that bit mask will be narrowed down to the + * data types as which the incoming data may be + * interpreted as. If that is a single data + * type, then @p out_value will be populated as + * well. Otherwise, @ref + * FLUF_IO_WANT_TYPE_DISAMBIGUATION will be + * returned and the user shall retry the call + * with this parameter set to a single type as + * which the resource shall be parsed. + * + * @param[out] out_value Full or partial resource value. + * + * @param[out] out_path Data model path that the value corresponds + * to. This will be a path to a Single-instance + * Resource or a Resource Instance, unless + * *inout_type_bitmask == + * FLUF_DATA_TYPE_NULL, in which case it + * might also signify an empty Object Instance, + * or a query in a Read-Composite request. + * + * @returns + * - Success states (non-negative values): + * - 0 - the entry has been parsed in its entirety (or at least in part in + * case of @ref FLUF_DATA_TYPE_BYTES or @ref FLUF_DATA_TYPE_STRING types), + * and the function can be called again to retrieve the next one (or the + * next chunk of Bytes or String entry) + * - @ref FLUF_IO_EOF - there are no more entries in the payload; this may + * only be returned if @ref fluf_io_in_ctx_feed_payload has been previously + * called with the payload_finished parameter set to true; + * @p out_value and @p out_path will be set to NULL - the EOF + * condition is treated as occurring after the last entry, not as part of it + * - @ref FLUF_IO_WANT_NEXT_PAYLOAD - end of payload has been encountered + * while parsing an entry; the user shall call @ref + * fluf_io_in_ctx_feed_payload and retry calling this function; the output + * arguments will not be populated in case of this return value + * - @ref FLUF_IO_WANT_TYPE_DISAMBIGUATION - resource value has been + * encountered, but the payload format does not contain enough metadata to + * determine its data type; the user shall determine the type and retry the + * call with a concrete type specified through @p inout_type_bitmask; + * @p out_path will be populated in case of this return value + * - Error conditions (negative values): + * - @ref FLUF_IO_ERR_INPUT_ARG - invalid arguments + * - @ref FLUF_IO_ERR_FORMAT - error parsing the input data, including when + * the resource is not compatible with any of the types provided through + * @p inout_type_bitmask + * - @ref FLUF_IO_ERR_LOGIC - the input context is not in a state in which + * calling this function is legal + */ +int fluf_io_in_ctx_get_entry(fluf_io_in_ctx_t *ctx, + fluf_data_type_t *inout_type_bitmask, + const fluf_res_value_t **out_value, + const fluf_uri_path_t **out_path); + +/** + * Retrieves the number of elements in the incoming data. + * + * This data, if available, will be populated inside the context after the first + * successful call to @ref fluf_io_in_ctx_get_entry, and may be retrieved at any + * time afterwards. + * + * @param ctx Input context to operate on. + * @param out_count Variable that, on successful exit, will be filled with the + * total number of entries in the entire payload (not just the + * part already provided to the context). + * + * @returns + * - 0 on success + * - @ref FLUF_IO_ERR_INPUT_ARG in case of invalid arguments + * - @ref FLUF_IO_ERR_FORMAT if the format of the input data does not support + * retrieving this information in advance (e.g., LwM2M TLV, CBOR indefinite + * arrays) + * - @ref FLUF_IO_ERR_LOGIC if the function is called before the first + * successfull call to @ref fluf_io_in_ctx_get_entry + */ +int fluf_io_in_ctx_get_entry_count(fluf_io_in_ctx_t *ctx, size_t *out_count); + +#ifndef FLUF_WITHOUT_REGISTER_CTX + +/** + * Must be called to prepare @p ctx to build message payload of the REGISTER + * operation. + * + * Example use of register_ctx API (error checking omitted for brevity): + * @code + * #define BUFF_SIZE 100 + * // New register payload ctx + * fluf_io_register_ctx_t ctx; + * uint8_t out_buff[BUFF_SIZE]; + * size_t out_copied_bytes = 0; + * size_t offset = 0; + * + * fluf_io_register_ctx_init(&ctx); + * + * fluf_io_register_ctx_new_entry(&ctx, &FLUF_MAKE_OBJECT_PATH(1), "1.1"); + * fluf_io_register_ctx_get_payload(&ctx, &out_buff[offset], BUFF_SIZE - offset, + * &out_copied_bytes); + * offset += out_copied_bytes; + * fluf_io_register_ctx_new_entry(&ctx, &FLUF_MAKE_INSTANCE_PATH(1,0), NULL); + * fluf_io_register_ctx_get_payload(&ctx, &out_buff[offset], BUFF_SIZE - offset, + * &out_copied_bytes); + * offset += out_copied_bytes; + * fluf_io_register_ctx_new_entry(&ctx, &FLUF_MAKE_INSTANCE_PATH(1,1), NULL); + * fluf_io_register_ctx_get_payload(&ctx, &out_buff[offset], BUFF_SIZE - offset, + * &out_copied_bytes); + * offset += out_copied_bytes; + * fluf_io_register_ctx_new_entry(&ctx, &FLUF_MAKE_INSTANCE_PATH(3,0), NULL); + * fluf_io_register_ctx_get_payload(&ctx, &out_buff[offset], BUFF_SIZE - offset, + * &out_copied_bytes); + * offset += out_copied_bytes; + * fluf_io_register_ctx_new_entry(&ctx, &FLUF_MAKE_OBJECT_PATH(5), NULL); + * fluf_io_register_ctx_get_payload(&ctx, &out_buff[offset], BUFF_SIZE - offset, + * &out_copied_bytes); + * offset += out_copied_bytes; + * + * // outgoing message: + * // ;ver=1.1,,,, + * @endcode + * + * @param ctx Context to operate on. + */ +void fluf_io_register_ctx_init(fluf_io_register_ctx_t *ctx); + +/** + * Process another Object or Object Instance record. Remember to keep the order, + * the values of the @p path must be increasing. For Object Instance @p version + * presence is treated as error. + * + * For Core Objects if the version does not match the LwM2M version being used + * (@ref FLUF_WITH_LWM2M12) @p version must be provided. For Non-core Objects + * missing @p version is equivalent to 1.0 and is optional. The @p version + * format is X.X where X is the digit ("The Object Version of an Object is + * composed of 2 digits separated by a dot '.'" [LwM2M Specification]). + * + * As required by the specification, Security Object ID:0 and OSCORE Object + * ID:21 must be skipped. + * + * IMPORTANT: Add Object to the REGISTER payload only if @version is defined or + * this Object doesn't have any Instance. + * + * @param ctx Context to operate on. + * @param path Object or Object Instance path. + * @param version Object version, ignored if NULL. + * + * @return + * - 0 on success, + * - FLUF_IO_ERR_LOGIC if the internal buffer is not empty, + * - FLUF_IO_ERR_INPUT_ARG if: + * - @p path is not Object or Object Instance path, + * - the ascending order of @p path is not respected, + * - @p version format is incorrect, + * - @p version is given for Object Instance @p path, + * - Object ID is equal to @ref FLUF_OBJ_ID_SECURITY or @ref + * FLUF_OBJ_ID_OSCORE. + */ +int fluf_io_register_ctx_new_entry(fluf_io_register_ctx_t *ctx, + const fluf_uri_path_t *path, + const char *version); + +/** + * Call to copy encoded message to payload buffer. + * + * The buffer into which the encoded message is written is given by @p out_buff. + * @p out_copied_bytes returns information about the number of bytes written + * during a single function call, If the function return @ref + * FLUF_IO_NEED_NEXT_CALL it means that the buffer space has run out. If you + * support block operation then at this point you can send the buffer as one + * message block and call this function again and the rest of the record will be + * written. In case return value equals 0 then you can call @ref + * fluf_io_register_ctx_new_entry with another Object/Object Instance and @ref + * fluf_io_register_ctx_get_payload with pointer to the buffer shifted by the + * value of @p out_copied_bytes, also remember to update the value of @p + * out_buff_len. + * + * The @p out_buff already contains the LwM2M message payload. + * + * @param ctx Context to operate on. + * @param[out] out_buff Payload buffer. + * @param out_buff_len Length of payload buffer. + * @param[out] out_copied_bytes Number of bytes that are written into the + * buffer. + * + * @return + * - 0 on success, + * - FLUF_IO_NEED_NEXT_CALL if record didn't fit in the output buffer and this + * function have to be called again, + * - FLUF_IO_ERR_LOGIC if this function is called but there is no more data in + * internal buffer. + */ +int fluf_io_register_ctx_get_payload(fluf_io_register_ctx_t *ctx, + void *out_buff, + size_t out_buff_len, + size_t *out_copied_bytes); + +#endif // FLUF_WITHOUT_REGISTER_CTX + +#ifndef FLUF_WITHOUT_BOOTSTRAP_DISCOVER_CTX + +/** + * Must be called to prepare @p ctx to build message payload of the + * BOOTSTRAP-DISCOVER operation. Information about supported version of LwM2M is + * placed on the beginning of the message. It depends on @ref FLUF_WITH_LWM2M12 + * flag. + * + * Example use of bootstrap_discover_ctx API (error checking omitted for + * brevity): + * @code + * #define BUFF_SIZE 200 + * // New bootstrap-discover payload ctx + * fluf_io_bootstrap_discover_ctx_t ctx; + * uint8_t out_buff[BUFF_SIZE]; + * size_t out_copied_bytes = 0; + * size_t offset = 0; + * uint16_t ssid = 10; + * + * fluf_io_bootstrap_discover_ctx_init(&ctx, &FLUF_MAKE_ROOT_PATH()); + * + * fluf_io_bootstrap_discover_ctx_new_entry(ctx, + * &FLUF_MAKE_INSTANCE_PATH(0,0), NULL, &ssid, + * "coaps://server_1.example.com"); + * fluf_io_bootstrap_discover_ctx_get_payload(&ctx, &out_buff[offset], + * BUFF_SIZE - offset, &out_copied_bytes); + * offset += out_copied_bytes; + * // Security Object iid = 1 contains the credentials for the LwM2M + * // Bootstrap-Server, according to the technical specification we don't + * // provide SSID (prohibited) and URI (optional). + * fluf_io_bootstrap_discover_ctx_new_entry(ctx, + * &FLUF_MAKE_INSTANCE_PATH(0,1), NULL, NULL, NULL); + * fluf_io_bootstrap_discover_ctx_get_payload(&ctx, &out_buff[offset], + * BUFF_SIZE - offset, &out_copied_bytes); + * offset += out_copied_bytes; + * // For Server instance we always provide SSID value. + * fluf_io_bootstrap_discover_ctx_new_entry(ctx, + * &FLUF_MAKE_INSTANCE_PATH(1,0), NULL, &ssid, NULL); + * fluf_io_bootstrap_discover_ctx_get_payload(&ctx, &out_buff[offset], + * BUFF_SIZE - offset, &out_copied_bytes); + * offset += out_copied_bytes; + * fluf_io_bootstrap_discover_ctx_new_entry(ctx, + * &FLUF_MAKE_INSTANCE_PATH(3,0), NULL, NULL, NULL); + * fluf_io_bootstrap_discover_ctx_get_payload(&ctx, &out_buff[offset], + * BUFF_SIZE - offset, &out_copied_bytes); + * offset += out_copied_bytes; + * fluf_io_bootstrap_discover_ctx_new_entry(ctx, &FLUF_MAKE_OBJECT_PATH(4), + * , NULL, NULL, NULL); + * fluf_io_bootstrap_discover_ctx_get_payload(&ctx, &out_buff[offset], + * BUFF_SIZE - offset, &out_copied_bytes); + * offset += out_copied_bytes; + * // For object 55 we defined the version. + * fluf_io_bootstrap_discover_ctx_new_entry(ctx, &FLUF_MAKE_OBJECT_PATH(55), + * "1.9", NULL, NULL); + * fluf_io_bootstrap_discover_ctx_get_payload(&ctx, &out_buff[offset], + * BUFF_SIZE - offset, &out_copied_bytes); + * offset += out_copied_bytes; + * fluf_io_bootstrap_discover_ctx_new_entry(ctx, + * &FLUF_MAKE_INSTANCE_PATH(55,0), NULL, NULL, NULL); + * fluf_io_bootstrap_discover_ctx_get_payload(&ctx, &out_buff[offset], + * BUFF_SIZE - offset, &out_copied_bytes); + * offset += out_copied_bytes; + * + * // outgoing message: + * ;lwm2m=1.2,;ssid=10;uri="coaps://server_1.example.com", + * ,;ssid=10,,,;ver=1.9, + * @endcode + * + * @param ctx Context to operate on. + * @param base_path Object ID from request, its absence means Root + * (empty) path, you might use @ref FLUF_MAKE_ROOT_PATH() + * to create it. + * + * @return + * - 0 on success, + * - FLUF_IO_ERR_INPUT_ARG if @p path is not Object or Root path. + */ +int fluf_io_bootstrap_discover_ctx_init(fluf_io_bootstrap_discover_ctx_t *ctx, + const fluf_uri_path_t *base_path); + +/** + * Adds another Object or Object Instance to the buffer. Remember to keep the + * order, the values of @p path must be increasing. For any Object Instance @p + * version presence is treated as error, also for any Object the presence of @p + * ssid and @p uri is not allowed. + * + * For Core Objects if the version does not match the LwM2M version being used + * (@ref FLUF_WITH_LWM2M12) @p version must be provided. For Non-core Object + * missing @p version is equivalent to 1.0 and is optional. The @p version + * format is X.X where X is the digit ("The Object Version of an Object is + * composed of 2 digits separated by a dot '.'" [LwM2M Specification]). + * + * Each element of the Instances list of the Security Object (Object ID:0) + * includes the associated Short Server ID - @p ssid and LwM2M Server URI: @p + * uri in its parameters list (except the Bootstrap-Server Security Object + * Instance) while the elements of the Instances list of the Server Object + * (Object ID:1) also report the associated Short Server ID: @p ssid in their + * parameters list. If the LwM2M Client supports OSCORE, each element of the + * Instances list of the OSCORE Object (Object ID:21) includes the associated + * Short Server ID - @p ssid in its parameters list, except the OSCORE Object + * Instance which is associated with LwM2M Bootstrap-Server. For others Objects + * @p ssid and @p uri values presence, is treated as an error. + * + * IMPORTANT: Add Object to the BOOTSTRAP-DISCOVER payload only if @version is + * defined or this Object doesn't have any Instance. + * + * @param ctx Context to operate on. + * @param path Object Instance path. + * @param version Object version, ignored if NULL. + * @param ssid Short server ID of Object Instance, relevant for Security, + * Server and OSCORE Object Instances. + * @param uri Server URI relevant for Security Object Instances. + * + * @return + * - 0 on success, + * - FLUF_IO_ERR_INPUT_ARG if: + * - @p ssid for Servers Object Instance are not provided, + * - @p uri is provided for Object Instance other than Security Object + * Instance, + * - @p ssid is provided for Object Instance other than Security, Server and + * Oscore Object Instance, + * - given @p path is outside the base_path or it's not Object or Object + * Instance path, + * - ascending order of @p path is not respected, + * - @p version format is incorrect, + * - @p ssid or @p uri is given for Object @p path, + * - @p version is given for Object Instance @p path, + * - FLUF_IO_ERR_LOGIC if the internal buffer is not empty. + */ +int fluf_io_bootstrap_discover_ctx_new_entry( + fluf_io_bootstrap_discover_ctx_t *ctx, + const fluf_uri_path_t *path, + const char *version, + const uint16_t *ssid, + const char *uri); + +/** + * Call to copy encoded message to payload buffer. + * + * The buffer into which the encoded message is written is given by @p out_buff. + * @p out_copied_bytes returns information about the number of bytes written + * during a single function call, If the function return @ref + * FLUF_IO_NEED_NEXT_CALL it means that the buffer space has run out. If you + * support block operation then at this point you can send the buffer as one + * message block and call this function again and the rest of the record will be + * written. In case return value equals 0 then you can call @ref + * fluf_io_bootstrap_discover_ctx_new_entry with another Object/Object Instance + * and @ref fluf_io_bootstrap_discover_ctx_get_payload with pointer to the + * buffer shifted by the value of @p out_copied_bytes, also remember to update + * the value of @p out_buff_len. + * + * The @p out_buff already contains the LWM2M message payload. + * + * @param ctx Context to operate on. + * @param[out] out_buff Payload buffer. + * @param out_buff_len Length of payload buffer. + * @param[out] out_copied_bytes Number of bytes that are written into the + * buffer. + * + * @return + * - 0 on success, + * - FLUF_IO_NEED_NEXT_CALL if record didn't fit in the output buffer and this + * function have to be called again, + * - FLUF_IO_ERR_LOGIC if this function is called but there is no more data in + * internal buffer. + */ +int fluf_io_bootstrap_discover_ctx_get_payload( + fluf_io_bootstrap_discover_ctx_t *ctx, + void *out_buff, + size_t out_buff_len, + size_t *out_copied_bytes); + +#endif // FLUF_WITHOUT_BOOTSTRAP_DISCOVER_CTX + +#ifndef FLUF_WITHOUT_DISCOVER_CTX + +/** + * Must be called to prepare @p ctx to build message payload of the DISCOVER + * operation. + * + * Example use of discover_ctx API (error checking omitted for brevity): + * @code + * #define BUFF_SIZE 100 + * // New discover payload ctx + * fluf_io_discover_ctx_t ctx; + * uint8_t out_buff[BUFF_SIZE]; + * size_t out_copied_bytes = 0; + * size_t offset = 0; + * + * fluf_attr_notification_t device_obj_attr = { + * .has_min_period = true, + * .min_period = 10, + * .has_max_period = true, + * .max_period = 60 + * }; + * uint8_t depth = 3; + * uint8_t dim = 2; + * + * fluf_io_discover_ctx_init(&ctx, &FLUF_MAKE_OBJECT_PATH(3), &depth, + * NULL,NULL); + * + * fluf_io_discover_ctx_new_entry(ctx, &FLUF_MAKE_OBJECT_PATH(3), + * &device_obj_attr, "1.2", NULL); + * fluf_io_discover_ctx_get_payload(&ctx, &out_buff[offset], BUFF_SIZE - offset, + * &out_copied_bytes); + * offset += out_copied_bytes; + * fluf_io_discover_ctx_new_entry(ctx, &FLUF_MAKE_INSTANCE_PATH(3, 0), + * NULL, NULL, NULL); + * fluf_io_discover_ctx_get_payload(&ctx, &out_buff[offset], BUFF_SIZE - offset, + * &out_copied_bytes); + * offset += out_copied_bytes; + * fluf_io_discover_ctx_new_entry(ctx, &FLUF_MAKE_RESOURCE_PATH(3, 0, 1), + * NULL, NULL, NULL); + * fluf_io_discover_ctx_get_payload(&ctx, &out_buff[offset], BUFF_SIZE - offset, + * &out_copied_bytes); + * offset += out_copied_bytes; + * fluf_io_discover_ctx_new_entry(ctx, &FLUF_MAKE_RESOURCE_PATH(3, 0, 2), + * NULL, NULL, NULL); + * fluf_io_discover_ctx_get_payload(&ctx, &out_buff[offset], BUFF_SIZE - offset, + * &out_copied_bytes); + * offset += out_copied_bytes; + * fluf_io_discover_ctx_new_entry(ctx, &FLUF_MAKE_RESOURCE_PATH(3, 0, 3), + * NULL, NULL, NULL); + * fluf_io_discover_ctx_get_payload(&ctx, &out_buff[offset], BUFF_SIZE - offset, + * &out_copied_bytes); + * offset += out_copied_bytes; + * fluf_io_discover_ctx_new_entry(ctx, &FLUF_MAKE_RESOURCE_PATH(3, 0, 4), + * NULL, NULL, NULL); + * fluf_io_discover_ctx_get_payload(&ctx, &out_buff[offset], BUFF_SIZE - offset, + * &out_copied_bytes); + * offset += out_copied_bytes; + * fluf_io_discover_ctx_new_entry(ctx, &FLUF_MAKE_RESOURCE_PATH(3, 0, 6), + * NULL, NULL, &dim); + * fluf_io_discover_ctx_get_payload(&ctx, &out_buff[offset], BUFF_SIZE - offset, + * &out_copied_bytes); + * offset += out_copied_bytes; + * fluf_io_discover_ctx_new_entry(ctx, + * &FLUF_MAKE_RESOURCE_INSTANCE_PATH(3, 0, 6, 0), NULL, NULL, NULL); + * fluf_io_discover_ctx_get_payload(&ctx, &out_buff[offset], BUFF_SIZE - offset, + * &out_copied_bytes); + * offset += out_copied_bytes; + * fluf_io_discover_ctx_new_entry(ctx, + * &FLUF_MAKE_RESOURCE_INSTANCE_PATH(3, 0, 6, 1), NULL, NULL, NULL); + * fluf_io_discover_ctx_get_payload(&ctx, &out_buff[offset], BUFF_SIZE - offset, + * &out_copied_bytes); + * offset += out_copied_bytes; + * + * fluf_attr_notification_t baterry_level_res_attr = { + * .has_greater_than = true, + * .greater_than = 50.0, + * }; + * fluf_io_discover_ctx_new_entry(ctx, &FLUF_MAKE_RESOURCE_PATH(3, 0, 9), + * &baterry_level_res_attr, NULL, NULL); + * fluf_io_discover_ctx_get_payload(&ctx, &out_buff[offset], BUFF_SIZE - offset, + * &out_copied_bytes); + * offset += out_copied_bytes; + * + * // outgoing message: + * ;ver=1.2;pmin=10;pmax=60,,,,,, + * ;dim=2,,,;gt=50 + * @endcode + * + * @param ctx Context to operate on. + * @param base_path Targeted path from request. + * @param depth Depth parameter from request, if not present, defualt + * value will be used. + * @return + * - 0 on success, + * - FLUF_IO_ERR_INPUT_ARG if: + * - @p base_path is root ("/") or Resource Instance path, + * - @p depth is greater than 3. + */ +int fluf_io_discover_ctx_init(fluf_io_discover_ctx_t *ctx, + const fluf_uri_path_t *base_path, + const uint8_t *depth); + +/** + * Adds another Object, Object Instance Resource or Resource Instance to the + * buffer. Remember to keep the order, the values of @p path must be increasing. + * For @p path that is not Object @p version presence is treated as error, also + * for @p path that is not Resource, the presence of @p dim is not allowed. + * + * @p attributes must be given at the level from which they apply. For example + * if base_path from @ref fluf_io_discover_ctx_init contains Object Instance + * value, but there are some attributes assigned to Object then they should be + * given in @ref fluf_io_discover_ctx_new_entry call on the Object Instance + * level. If some @p attributes are assigned on the Resource level, then then + * they are omitted at the Resource Instances level. + * + * For Core Objects if the version does not match the LwM2M version being used + * (@ref FLUF_WITH_LWM2M12) @p version must be provided. For Non-core Object + * missing @p version is equivalent to 1.0 and is optional. The @p version + * format is X.X where X is the digit ("The Object Version of an Object is + * composed of 2 digits separated by a dot '.'" [LwM2M Specification]). + * + * Remember to keep the order, the values of the path must be + * increasing. This function checks if given @p path is in accordance with depth + * paramter. If @ref depth is not defined, default values are: + * @ref base_path points to Object ID -> message contains Object, Object + * Instances, and Resources + * @ref base_path points to Object Instance ID -> message contains Object + * Instances, and Resources + * @ref base_path points to Resource ID -> message contains Resource and + * Resource Instances + * + * IMPORTANT: The user doesn't need to check compliance with parameter @p depth + * and ignore the appearance of FLUF_IO_WARNING_DEPTH warning. In this + * situation, you can iterate over all elements of the the data model and this + * function will not add records outside the defined range. + * + * @param ctx Context to operate on. + * @param path Object Instance path. + * @param attributes Attributes assigned to the @p path. + * @param version Object version, ignored if NULL. + * @param dim Number of the Resource Instances. Ignored if NULL. + * + * @return + * - 0 on success, + * - FLUF_IO_ERR_INPUT_ARG if: + * - given @p path is outside the base_path, + * - ascending order of @p path is not respected, + * - @p version format is incorrect, + * - @p dim is given for @p path that is not Resource, + * - @p version is given for @p path that is not Object, + * - FLUF_IO_ERR_LOGIC if: + * - internal buffer is not empty, + * - if number of provided Resource Instances doesn't match last provided + * @p dim value (if depth parameter allows to write Resource Instance). + */ +int fluf_io_discover_ctx_new_entry(fluf_io_discover_ctx_t *ctx, + const fluf_uri_path_t *path, + const fluf_attr_notification_t *attributes, + const char *version, + const uint16_t *dim); + +/** + * Call to copy encoded message to payload buffer. + * + * The buffer into which the encoded message is written is given by @p out_buff. + * @p out_copied_bytes returns information about the number of bytes written + * during a single function call, If the function return @ref + * FLUF_IO_NEED_NEXT_CALL it means that the buffer space has run out. If you + * support block operation then at this point you can send the buffer as one + * message block and call this function again and the rest of the record will be + * written. In case return value equals 0 then you can call @ref + * fluf_io_discover_ctx_new_entry with another Object/Object Instance and @ref + * fluf_io_discover_ctx_get_payload with pointer to the buffer shifted by the + * value of @p out_copied_bytes, also remember to update the value of @p + * out_buff_len. + * + * The @p out_buff already contains the LWM2M message payload. + * + * @param ctx Context to operate on. + * @param[out] out_buff Payload buffer. + * @param out_buff_len Length of payload buffer. + * @param[out] out_copied_bytes Number of bytes that are written into the + * buffer. + * + * @return + * - 0 on success, + * - FLUF_IO_NEED_NEXT_CALL if record didn't fit in the output buffer and this + * function have to be called again, + * - FLUF_IO_ERR_LOGIC if this function is called but there is no more data in + * internal buffer. + */ +int fluf_io_discover_ctx_get_payload(fluf_io_discover_ctx_t *ctx, + void *out_buff, + size_t out_buff_len, + size_t *out_copied_bytes); + +#endif // FLUF_WITHOUT_DISCOVER_CTX + +#ifdef __cplusplus +} +#endif + +#endif // FLUF_IO_H diff --git a/include_public/fluf/fluf_io_ctx.h b/include_public/fluf/fluf_io_ctx.h new file mode 100644 index 00000000..39b39947 --- /dev/null +++ b/include_public/fluf/fluf_io_ctx.h @@ -0,0 +1,169 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#ifndef FLUF_IO_CTX_H +#define FLUF_IO_CTX_H + +#include +#include +#include + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define _FLUF_IO_CBOR_MAX_OBJLNK_STRING_SIZE sizeof("65535:65535") +// objlink is the largest possible simple variable + 1 byte for header +#define _FLUF_IO_CBOR_SIMPLE_RECORD_MAX_LENGTH \ + (_FLUF_IO_CBOR_MAX_OBJLNK_STRING_SIZE + 1) + +// max 3 bytes for array UINT16_MAX elements +// 1 byte for map +// 14 bytes for basename 21 65 2F36353533352F3635353334 as /65534/65534 +// 14 bytes for name 00 63 2F36353533352F3635353334 as /65534/65534 +// 10 bytes for basetime 22 FB 1122334455667788 +// 4 bytes for objlink header +// 1 bytes for string value header +// resource with objlink is biggest possible value that can be directly written +// into the internal_buff +#define _FLUF_IO_SENML_CBOR_SIMPLE_RECORD_MAX_LENGTH \ + (3 + 1 + 14 + 14 + 10 + 4 + 1 + _FLUF_IO_CBOR_MAX_OBJLNK_STRING_SIZE) + +#define _FLUF_IO_BOOT_DISC_RECORD_MAX_LENGTH \ + sizeof(";lwm2m=1.2,;ssid=65534;uri=\"") + +#define _FLUF_IO_REGISTER_RECORD_MAX_LENGTH sizeof(",;ver=9.9") + +#define _FLUF_IO_ATTRIBUTE_RECORD_MAX_LEN sizeof(";gt=-2.2250738585072014E-308") + +#define _FLUF_IO_DISCOVER_RECORD_MAX_LEN \ + sizeof(",;dim=65534") + +// TODO: expand at new encoders +#define _FLUF_IO_CTX_BUFFER_LENGTH _FLUF_IO_SENML_CBOR_SIMPLE_RECORD_MAX_LENGTH + +AVS_STATIC_ASSERT(_FLUF_IO_CTX_BUFFER_LENGTH + >= _FLUF_IO_CBOR_SIMPLE_RECORD_MAX_LENGTH + && _FLUF_IO_CTX_BUFFER_LENGTH + >= _FLUF_IO_BOOT_DISC_RECORD_MAX_LENGTH + && _FLUF_IO_CTX_BUFFER_LENGTH + >= _FLUF_IO_REGISTER_RECORD_MAX_LENGTH + && _FLUF_IO_CTX_BUFFER_LENGTH + >= _FLUF_IO_ATTRIBUTE_RECORD_MAX_LEN + && _FLUF_IO_CTX_BUFFER_LENGTH + >= _FLUF_IO_DISCOVER_RECORD_MAX_LEN, + internal_buff_badly_defined); + +typedef struct { + size_t remaining_bytes; + size_t offset; + size_t bytes_in_internal_buff; + bool is_extended_type; + uint8_t internal_buff[_FLUF_IO_CTX_BUFFER_LENGTH]; +} fluf_io_buff_t; + +typedef struct { + bool entry_added; +} fluf_internal_cbor_encoder_t; + +typedef struct { + bool encode_time; + double last_timestamp; + size_t items_count; + fluf_uri_path_t base_path; + size_t base_path_len; + bool first_entry_added; +} fluf_internal_senml_cbor_encoder_t; + +typedef struct { + fluf_id_type_t type; + size_t length; + size_t bytes_read; +} tlv_entry_t; + +#define FLUF_TLV_MAX_DEPTH 3 +typedef struct { + bool want_payload; + bool want_disambiguation; + /** buffer provided by fluf_io_in_ctx_feed_payload */ + void *buff; + size_t buff_size; + size_t buff_offset; + bool payload_finished; + + fluf_uri_path_t uri_path; + + // Currently processed path + bool has_path; + fluf_uri_path_t current_path; + + uint8_t type_field; + size_t id_length_buff_bytes_need; + uint8_t id_length_buff[5]; + size_t id_length_buff_read_offset; + size_t id_length_buff_write_offset; + + tlv_entry_t *entries; + tlv_entry_t entries_block[FLUF_TLV_MAX_DEPTH]; +} fluf_internal_tlv_decoder_t; + +#ifndef FLUF_WITHOUT_REGISTER_CTX +/** Register payload context + * Do not modify this structure directly, its fields are changed during fluf_io + * api calls. + */ +typedef struct { + fluf_io_buff_t buff; + fluf_uri_path_t last_path; + bool first_record_added; +} fluf_io_register_ctx_t; +#endif // FLUF_WITHOUT_REGISTER_CTX + +#ifndef FLUF_WITHOUT_BOOTSTRAP_DISCOVER_CTX +/** Bootstrap-Discovery payload context + * Do not modify this structure directly, its fields are changed during fluf_io + * api calls. + */ +typedef struct { + fluf_io_buff_t buff; + fluf_uri_path_t last_path; + fluf_uri_path_t base_path; + bool first_record_added; + const char *uri; +} fluf_io_bootstrap_discover_ctx_t; +#endif // FLUF_WITHOUT_BOOTSTRAP_DISCOVER_CTX + +#ifndef FLUF_WITHOUT_DISCOVER_CTX +/** Discovery payload context + * Do not modify this structure directly, its fields are changed during fluf_io + * api calls. + */ +typedef struct { + fluf_io_buff_t buff; + fluf_uri_path_t last_path; + fluf_uri_path_t base_path; + uint8_t depth; + uint16_t dim_counter; + bool first_record_added; + fluf_attr_notification_t attr; + size_t attr_record_len; + size_t attr_record_offset; +} fluf_io_discover_ctx_t; +#endif // FLUF_WITHOUT_DISCOVER_CTX + +#ifdef __cplusplus +} +#endif + +#endif /* FLUF_IO_CTX_H */ diff --git a/include_public/fluf/fluf_utils.h b/include_public/fluf/fluf_utils.h new file mode 100644 index 00000000..cce1a478 --- /dev/null +++ b/include_public/fluf/fluf_utils.h @@ -0,0 +1,216 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#ifndef FLUF_UTILS_H +#define FLUF_UTILS_H + +#include +#include +#include + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define FLUF_U16_STR_MAX_LEN (sizeof("65535") - 1) +#define FLUF_U32_STR_MAX_LEN (sizeof("4294967295") - 1) +#define FLUF_U64_STR_MAX_LEN (sizeof("18446744073709551615") - 1) +#define FLUF_DOUBLE_STR_MAX_LEN (sizeof("-2.2250738585072014E-308") - 1) + +#define FLUF_ID_INVALID UINT16_MAX + +#define _FLUF_URI_PATH_INITIALIZER(Oid, Iid, Rid, Riid, Len) \ + { \ + .uri_len = Len, \ + .ids = { \ + [FLUF_ID_OID] = (Oid), \ + [FLUF_ID_IID] = (Iid), \ + [FLUF_ID_RID] = (Rid), \ + [FLUF_ID_RIID] = (Riid) \ + } \ + } + +#define _FLUF_MAKE_URI_PATH(...) \ + ((fluf_uri_path_t) _FLUF_URI_PATH_INITIALIZER(__VA_ARGS__)) + +#define FLUF_MAKE_RESOURCE_INSTANCE_PATH(Oid, Iid, Rid, Riid) \ + _FLUF_MAKE_URI_PATH(Oid, Iid, Rid, Riid, 4) + +#define FLUF_MAKE_RESOURCE_PATH(Oid, Iid, Rid) \ + _FLUF_MAKE_URI_PATH(Oid, Iid, Rid, FLUF_ID_INVALID, 3) + +#define FLUF_MAKE_INSTANCE_PATH(Oid, Iid) \ + _FLUF_MAKE_URI_PATH(Oid, Iid, FLUF_ID_INVALID, FLUF_ID_INVALID, 2) + +#define FLUF_MAKE_OBJECT_PATH(Oid) \ + _FLUF_MAKE_URI_PATH( \ + Oid, FLUF_ID_INVALID, FLUF_ID_INVALID, FLUF_ID_INVALID, 1) + +#define FLUF_MAKE_ROOT_PATH() \ + _FLUF_MAKE_URI_PATH(FLUF_ID_INVALID, \ + FLUF_ID_INVALID, \ + FLUF_ID_INVALID, \ + FLUF_ID_INVALID, \ + 0) + +static inline bool fluf_uri_path_equal(const fluf_uri_path_t *left, + const fluf_uri_path_t *right) { + if (left->uri_len != right->uri_len) { + return false; + } + for (size_t i = 0; i < left->uri_len; ++i) { + if (left->ids[i] < right->ids[i]) { + return false; + } else if (left->ids[i] > right->ids[i]) { + return false; + } + } + return true; +} + +static inline size_t fluf_uri_path_length(const fluf_uri_path_t *path) { + return path->uri_len; +} + +static inline bool fluf_uri_path_has(const fluf_uri_path_t *path, + fluf_id_type_t id_type) { + return path->uri_len > id_type; +} + +static inline bool fluf_uri_path_is(const fluf_uri_path_t *path, + fluf_id_type_t id_type) { + return path->uri_len == (size_t) id_type + 1u; +} + +static inline bool fluf_uri_path_outside_base(const fluf_uri_path_t *path, + const fluf_uri_path_t *base) { + if (path->uri_len < base->uri_len) { + return true; + } + for (size_t i = 0; i < base->uri_len; ++i) { + if (path->ids[i] != base->ids[i]) { + return true; + } + } + return false; +} + +bool fluf_uri_path_increasing(const fluf_uri_path_t *previous_path, + const fluf_uri_path_t *current_path); + +/** + * Validates the version of the object - accepted format is X.Y where X and Y + * are digits. + * + * @param version Object version. + * + * @returns 0 on success or NULL @p version pointer, a @ref + * FLUF_IO_ERR_INPUT_ARG value in case of incorrect format. + */ +int fluf_validate_obj_version(const char *version); + +/** + * Converts uint16_t value to string and copies it to @p out_buff (without the + * terminating nullbyte). The minimum required @p out_buff size is @ref + * FLUF_U16_STR_MAX_LEN. + * + * @param value Input value. + * @param[out] out_buff Output buffer. + * + * @return Number of bytes written. + */ +size_t fluf_uint16_to_string_value(uint16_t value, char *out_buff); + +/** + * Converts uint32_t value to string and copies it to @p out_buff (without the + * terminating nullbyte). The minimum required @p out_buff size is @ref + * FLUF_U32_STR_MAX_LEN. + * + * @param value Input value. + * @param[out] out_buff Output buffer. + * + * @return Number of bytes written. + */ +size_t fluf_uint32_to_string_value(uint32_t value, char *out_buff); + +/** + * Converts uint64_t value to string and copies it to @p out_buff (without the + * terminating nullbyte). The minimum required @p out_buff size is @ref + * FLUF_U64_STR_MAX_LEN. + * + * @param value Input value. + * @param[out] out_buff Output buffer. + * + * @return Number of bytes written. + */ +size_t fluf_uint64_to_string_value(uint64_t value, char *out_buff); + +/** + * Converts double value to string and copies it to @p out_buff (without the + * terminating nullbyte). The minimum required @p out_buff size is @ref + * FLUF_DOUBLE_STR_MAX_LEN. + * + * IMPORTANT: This function is used to encode LwM2M attributes whose + * float/double format is defined by LwM2M Specification: 1*DIGIT ["."1*DIGIT]. + * However for absolute values greater than @ref UINT64_MAX and less than + * 1e-10 exponential notation is used. Since the specification does not + * define the format for the value of NaN and infinite, so in this case "nan" + * and "inf" will be set. + * + * IMPORTANT: This function doesn't use sprintf() and is intended to be + * lightweight. For very large and very small numbers, a rounding error may + * occur. + * + * @param value Input value. + * @param[out] out_buff Output buffer. + * + * @return Number of bytes written. + */ +size_t fluf_double_to_simple_str_value(double value, char *out_buff); + +/** + * Converts string representation of numerical value to uint32_t value. + * + * @param buff Input buffer. + * @param buff_len Input buffer length. + * @param[out] out_val Output value. + * + * @return 0 in case of success and -1 if @p buff_len is equal to 0 or there are + * characters in the @p buff that are not digits. + */ +int fluf_string_to_uint32_value(const char *buff, + size_t buff_len, + uint32_t *out_val); + +/** + * Converts string representation of numerical value to double value. Does not + * supports exponential notation, infinitive and NAN values (LwM2M attributes + * representation doesn't allow for this). + * + * @param buff Input buffer. + * @param buff_len Input buffer length. + * @param[out] out_val Output value. + * + * @return 0 in case of success and -1 if @p buff_len is equal to 0 or there are + * characters in the @p buff that are not digits (exceptions shown above). + */ +int fluf_string_to_simple_double_value(const char *buff, + size_t buff_len, + double *out_val); + +#ifdef __cplusplus +} +#endif + +#endif // FLUF_UTILS_H diff --git a/linux_config/anj/anj_config.h b/linux_config/anj/anj_config.h new file mode 100644 index 00000000..09c4d722 --- /dev/null +++ b/linux_config/anj/anj_config.h @@ -0,0 +1,25 @@ +/* + * Copyright 2023-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_CONFIG_H +#define ANJAY_CONFIG_H + +#ifndef ANJ_FOTA_PULL_METHOD_SUPPORTED +# define ANJ_FOTA_PULL_METHOD_SUPPORTED 0 +#endif // ANJ_FOTA_PULL_METHOD_SUPPORTED + +#ifndef ANJ_FOTA_PUSH_METHOD_SUPPORTED +# define ANJ_FOTA_PUSH_METHOD_SUPPORTED 1 +#endif // ANJ_FOTA_PUSH_METHOD_SUPPORTED + +#ifndef ANJ_TIME_POSIX_COMPAT +# define ANJ_TIME_POSIX_COMPAT 1 +#endif // ANJ_TIME_POSIX_COMPAT + +#endif // ANJAY_CONFIG_H diff --git a/linux_config/avsystem/commons/avs_commons_config.h b/linux_config/avsystem/commons/avs_commons_config.h new file mode 100644 index 00000000..b13a2219 --- /dev/null +++ b/linux_config/avsystem/commons/avs_commons_config.h @@ -0,0 +1,839 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#ifndef AVS_COMMONS_CONFIG_H +#define AVS_COMMONS_CONFIG_H + +/** + * @file avs_commons_config.h + * + * avs_commons library configuration. + * + * The preferred way to compile avs_commons is to use CMake, in which case this + * file will be generated automatically by CMake. + * + * However, to provide compatibility with various build systems used especially + * by embedded platforms, it is alternatively supported to compile avs_commons + * by other means, in which case this file will need to be provided manually. + * + * In the repository, this file is provided as avs_commons_config.h.in, + * intended to be processed by CMake. If editing this file manually, please copy + * or rename it to avs_commons_config.h and for each of the + * #cmakedefine directives, please either replace it with regular + * #define to enable it, or comment it out to disable. You may also need + * to replace variables wrapped in @ signs with concrete values. Please + * refer to the comments above each of the specific definition for details. + * + * If you are editing a file previously generated by CMake, these + * #cmakedefines will be already replaced by either #define or + * commented out #undef directives. + */ + +/** + * Options that describe capabilities of the build environment. + * + * NOTE: If you leave some of these macros undefined, even though the given + * feature is actually available in the system, avs_commons will attempt to use + * its own substitutes, which may be incompatible with the definition in the + * system and lead to undefined behaviour. + */ +/**@{*/ +/** + * Is the target platform big-endian? + * + * If undefined, little-endian is assumed. Mixed-endian architectures are not + * supported. + * + * Affects avs_convert_be*() and avs_[hn]to[hn]*() calls in + * avs_utils and, by extension, avs_persistence. + */ +/* #undef AVS_COMMONS_BIG_ENDIAN */ + +/** + * Is GNU __builtin_add_overflow() extension available? + * + * Affects time handling functions in avs_utils. If disabled, software overflow + * checking will be compiled. Note that this software overflow checking code + * relies on U2 representation of signed integers. + */ +#define AVS_COMMONS_HAVE_BUILTIN_ADD_OVERFLOW + +/** + * Is GNU __builtin_mul_overflow() extension available? + * + * Affects time handling functions in avs_utils. If disabled, software overflow + * checking will be compiled. Note that this software overflow checking code + * relies on U2 representation of signed integers. + */ +#define AVS_COMMONS_HAVE_BUILTIN_MUL_OVERFLOW + +/** + * Is net/if.h available in the system? + * + * NOTE: If the header is indeed available, but this option is not defined, the + * IF_NAMESIZE macro will be defined publicly by avs_commons + * headers, which may conflict with system definitions. + */ +#define AVS_COMMONS_HAVE_NET_IF_H + +/** + * Are GNU diagnostic pragmas (#pragma GCC diagnostic push/pop/ignored) + * available? + * + * If defined, those pragmas will be used to suppress compiler warnings for some + * code known to generate them and cannot be improved in a more robust way, e.g. + * for code that is known to generate warnings from within system headers. + */ +#define AVS_COMMONS_HAVE_PRAGMA_DIAGNOSTIC + +/** + * Are GNU visibility pragmas (#pragma GCC visibility push/pop) available? + * + * Meaningful mostly if avs_commons will be directly or indirectly linked into + * a shared library. Causes all symbols except those declared in public headers + * to be hidden, i.e. not exported outside the shared library. If not defined, + * default compiler visibility settings will be used, but you still may use + * compiler flags and linker version scripts to replicate this manually if + * needed. + */ +#define AVS_COMMONS_HAVE_VISIBILITY + +/** + * Specify an optional compatibility header that allows use of POSIX-specific + * code that is not compliant with POSIX enough to be compiled directly. + * + * This header, if specified, will be included only by the following components, + * which may be enabled or disabled depending on state of the referenced flags: + * - avs_compat_threading implementation based on POSIX Threads + * (@ref AVS_COMMONS_COMPAT_THREADING_WITH_PTHREAD) + * - default avs_net socket implementation + * (@ref AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET) + * - avs_unit (@ref AVS_COMMONS_WITH_AVS_UNIT) + * - default implementation of avs_time routines + * (@ref AVS_COMMONS_UTILS_WITH_POSIX_AVS_TIME) + * + * Compatibility headers for lwIP and Microsoft Windows are provided with the + * library (see the compat directory). + * + * If this macro is not defined, the afore-mentioned components, if enabled, + * will use system headers directly, assuming they are POSIX-compliant. + * + * If this macro is enabled, the specified file will be included through an + * #include AVS_COMMONS_POSIX_COMPAT_HEADER statement. Thus, if editing + * this file manually, shall be + * replaced with a path to such file. + */ +/* #undef AVS_COMMONS_POSIX_COMPAT_HEADER */ + +/** + * Set if printf implementation doesn't support 64-bit format specifiers. + * If defined, custom implementation of conversion is used in + * @c AVS_UINT64_AS_STRING instead of using @c snprintf . + */ +/* #undef AVS_COMMONS_WITHOUT_64BIT_FORMAT_SPECIFIERS */ + +/** + * Set if printf implementation doesn't support floating-point numbers. + * If defined, custom implementation of conversion is used in + * @c AVS_DOUBLE_AS_STRING instead of using @c snprintf . This might increase + * compatibility with some embedded libc implementations that do not provide + * this functionality. + * + * NOTE: In order to keep the custom implementation small in code size, it is + * not intended to be 100% accurate. Rounding errors may occur - according to + * empirical checks, they show up around the 16th significant decimal digit. + */ +/* #undef AVS_COMMONS_WITHOUT_FLOAT_FORMAT_SPECIFIERS */ +/**@}*/ + +/** + * Enable poisoning of unwanted symbols when compiling avs_commons. + * + * Requires a compiler that supports #pragma GCC poison. + * + * This is mostly useful during development, to ensure that avs_commons do not + * attempt to call functions considered harmful in this library, such as printf. + * This is not guaranteed to work as intended on every platform, e.g. on macOS + * it is known to generate false positives due to different dependencies between + * system headers. + */ +/* #undef AVS_COMMONS_WITH_POISONING */ + +/** + * Options that control compilation of avs_commons components. + * + * Each of the configuration options below enables, if defined, one of the core + * components of the avs_commons library. + * + * NOTE: Enabling avs_unit will cause an object file with an implementation of + * main() to be generated. + */ +/**@{*/ +#define AVS_COMMONS_WITH_AVS_ALGORITHM +#define AVS_COMMONS_WITH_AVS_BUFFER +#define AVS_COMMONS_WITH_AVS_COMPAT_THREADING +#define AVS_COMMONS_WITH_AVS_CRYPTO +/* #undef AVS_COMMONS_WITH_AVS_HTTP */ +#define AVS_COMMONS_WITH_AVS_LIST +#define AVS_COMMONS_WITH_AVS_LOG +#define AVS_COMMONS_WITH_AVS_NET +#define AVS_COMMONS_WITH_AVS_PERSISTENCE +#define AVS_COMMONS_WITH_AVS_RBTREE +/* #undef AVS_COMMONS_WITH_AVS_SORTED_SET */ +#define AVS_COMMONS_WITH_AVS_SCHED +#define AVS_COMMONS_WITH_AVS_STREAM +#define AVS_COMMONS_WITH_AVS_UNIT +#define AVS_COMMONS_WITH_AVS_URL +#define AVS_COMMONS_WITH_AVS_UTILS +/* #undef AVS_COMMONS_WITH_AVS_VECTOR */ +/**@}*/ + +/** + * Options that control compilation of avs_compat_threading implementations. + * + * If CMake is not used, in the typical scenario at most one of the following + * implementations may be enabled at the same time. If none is enabled, the + * relevant symbols will need to be provided by the user, if used. + * + * These are meaningful only if AVS_COMMONS_WITH_AVS_COMPAT_THREADING is + * defined. + */ +/**@{*/ +/** + * Enable implementation based on spinlocks. + * + * This implementation is usually very inefficient, and requires C11 stdatomic.h + * header to be available. + */ +/* #undef AVS_COMMONS_COMPAT_THREADING_WITH_ATOMIC_SPINLOCK */ + +/** + * Enable implementation based on the POSIX Threads library. + * + * This implementation is preferred over the spinlock-based one, but the POSIX + * Threads library is normally available only in UNIX-like environments. + */ +#define AVS_COMMONS_COMPAT_THREADING_WITH_PTHREAD + +/** + * Is the pthread_condattr_setclock() function available? + * + * This flag only makes sense when + * AVS_COMMONS_COMPAT_THREADING_WITH_PTHREAD is enabled. + * + * If this flag is disabled, or if CLOCK_MONOTONIC macro is not + * available, the avs_condvar_wait() will internally use the real-time + * clock instead of the monotonic clock. Time values will be converted so that + * this change does not affect API usage. + */ +#define AVS_COMMONS_COMPAT_THREADING_PTHREAD_HAVE_PTHREAD_CONDATTR_SETCLOCK +/**@}*/ + +/** + * Options that control compilation of code depending on TLS backend library. + * + * If CMake is not used, in the typical scenario at most one of the following + * DTLS backends may be enabled at the same time. If none is enabled, + * functionalities that depends on cryptography will be disabled. + * + * Affects avs_crypto, avs_net, and avs_stream (for the MD5 implementation). + * + * mbed TLS is the main development backend, and is preferred as such. OpenSSL + * backend supports most functionality as well, but is not as thoroughly tested. + * TinyDTLS support is only rudimentary. + */ +/**@{*/ +//#define AVS_COMMONS_WITH_MBEDTLS +/* #undef AVS_COMMONS_WITH_OPENSSL */ +/* #undef AVS_COMMONS_WITH_TINYDTLS */ + +/** + * Enable support for custom TLS socket implementation. + * + * If enabled, the user needs to provide their own implementations of + * _avs_net_create_ssl_socket(), _avs_net_create_dtls_socket(), + * _avs_net_initialize_global_ssl_state() and + * _avs_net_cleanup_global_ssl_state(). + */ +/* #undef AVS_COMMONS_WITH_CUSTOM_TLS */ +/**@}*/ + +/** + * Options related to avs_crypto. + */ +/**@{*/ +/** + * Enable AEAD and HKDF support in avs_crypto. Requires MbedTLS in version at + * least 2.14.0, OpenSSL in version at least 1.1.0, or custom implementation in + * case of AVS_COMMONS_WITH_CUSTOM_TLS. + */ +#define AVS_COMMONS_WITH_AVS_CRYPTO_ADVANCED_FEATURES + +/** + * If the TLS backend is either mbed TLS or OpenSSL, enables APIs related to + * public-key cryptography. + * + * Public-key cryptography is not currently supported with TinyDTLS. + * + * It also enables support for X.509 certificates in avs_net, if that module is + * also enabled. + */ +//#define AVS_COMMONS_WITH_AVS_CRYPTO_PKI + +/** + * If the TLS backend is either mbed TLS, OpenSSL or TinyDTLS, enables support + * of pre-shared key security. + * + * PSK is the only supported security mode for the TinyDTLS backend, so this + * option MUST be enabled to utilize it. + * + * It also enables support for pre-shared key security in avs_net, if that + * module is also enabled. + */ +//#define AVS_COMMONS_WITH_AVS_CRYPTO_PSK + +/** + * Enables usage of Valgrind API to suppress some of the false positives + * generated by the OpenSSL backend. + */ +/* #undef AVS_COMMONS_WITH_AVS_CRYPTO_VALGRIND */ + +/** + * Enables high-level support for hardware-based PKI security, i.e. loading, + * generating and managing key pairs and certificates via external engines. + * + * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI to be enabled. + * + * An actual implementation is required to use this feature. You may provide + * your own, or use one of the default ones that come with the HSM engine + * commercial feature (see @ref AVS_COMMONS_WITH_MBEDTLS_PKCS11_ENGINE, + * @ref AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE and + * @ref AVS_COMMONS_WITH_OPENSSL_PKCS11_ENGINE). + * + * The functions that need to be provided in case of a custom implementation: + * - avs_crypto_pki_engine_certificate_rm() + * - avs_crypto_pki_engine_certificate_store() + * - avs_crypto_pki_engine_key_gen() + * - avs_crypto_pki_engine_key_rm() + * - avs_crypto_pki_engine_key_store() + * - When targeting the Mbed TLS backend: + * - _avs_crypto_mbedtls_engine_initialize_global_state() + * - _avs_crypto_mbedtls_engine_cleanup_global_state() + * - _avs_crypto_mbedtls_engine_append_cert() + * - _avs_crypto_mbedtls_engine_append_crl() + * - _avs_crypto_mbedtls_engine_load_private_key() + * - When targeting the OpenSSL backend: + * - _avs_crypto_openssl_engine_initialize_global_state() + * - _avs_crypto_openssl_engine_cleanup_global_state() + * - _avs_crypto_openssl_engine_load_certs() + * - _avs_crypto_openssl_engine_load_crls() + * - _avs_crypto_openssl_engine_load_private_key() + * + * External PKI engines are NOT supported in the TinyDTLS backend. + */ +/* #undef AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE */ + +/** + * Enables high-level support for hardware-based PSK security, i.e. loading + * and managing PSK keys and identities via external engine. + * + * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI to be enabled. + * + * An actual implementation is required to use this feature. You may provide + * your own, or use the default PSA-based one that comes with the HSM engine + * commercial feature (see @ref AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE). + * + * The functions that need to be provided in case of a custom implementation: + * - avs_crypto_psk_engine_identity_store() + * - avs_crypto_psk_engine_identity_rm() + * - avs_crypto_psk_engine_key_store() + * - avs_crypto_psk_engine_key_rm() + * - When targeting the Mbed TLS backend: + * - _avs_crypto_mbedtls_engine_initialize_global_state() + * - _avs_crypto_mbedtls_engine_cleanup_global_state() + * - _avs_crypto_mbedtls_engine_load_psk_key() + * + * External PSK engines are NOT supported in the OpenSSL and TinyDTLS backend. + */ +/* #undef AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE */ + +/** + * Enables the default implementation of avs_crypto engine, based on Mbed TLS + * and PKCS#11. + * + * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE to be enabled. + * + * NOTE: Query string format for this engine is a subset of the PKCS#11 URI + * scheme (see RFC 7512), modelled after the format accepted by libp11 OpenSSL + * engine. + * + * NOTE: The unit tests for this feature depend on SoftHSM and pkcs11-tool. + * These must be installed for the tests to pass. + * + * IMPORTANT: Only available as part of the HSM support commercial feature. + * Ignored in the open source version. + */ +/* #undef AVS_COMMONS_WITH_MBEDTLS_PKCS11_ENGINE */ + +/** + * Enables the default implementation of avs_crypto engine, based on Mbed TLS + * and Platform Security Architecture (PSA). + * + * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE or + * @ref AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE to be enabled. + * + * NOTE: Query string format for this engine is: + * + *
+ * kid=[,lifetime=]|uid=
+ * 
+ * + * The values are parsed using strtoull() with base=0, so may be in decimal, + * 0-prefixed octal or 0x-prefixed hexadecimal. On key generation and + * certificate storage, the specified lifetime will be used, or lifetime 1 + * (default persistent storage) will be used if not. On key or certificate use, + * the lifetime of the actual key will be verified if present on the query + * string and the key will be rejected if different. + * + * Certificates are stored as PSA_KEY_TYPE_RAW_DATA key entries containing + * X.509 DER data. Alternatively, the PSA Protected Storage API can be used if + * @ref AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE_PROTECTED_STORAGE is enabled, by + * using the uid=... syntax. + * + * IMPORTANT: Only available as part of the HSM support commercial feature. + * Ignored in the open source version. + */ +/* #undef AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE */ + +/** + * Enables support for the PSA Protected Storage API in the PSA-based avs_crypto + * engine. + * + * Requires @ref AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE to be enabled. + * + * IMPORTANT: Only available as part of the HSM support commercial feature. + * Ignored in the open source version. + */ +/* #undef AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE_PROTECTED_STORAGE */ + +/** + * Is the dlsym() function available? + * + * This is currently only used if @ref AVS_COMMONS_WITH_MBEDTLS_PKCS11_ENGINE is + * enabled. If enabled, the PKCS#11 module is loaded dynamically from a library + * specified by the PKCS11_MODULE_PATH environment variable. If disabled, + * a function with the following signature, realizing the PKCS#11 + * C_GetFunctionList method, must be provided manually: + * + *
+ * CK_RV _avs_crypto_mbedtls_pkcs11_get_function_list(CK_FUNCTION_LIST_PTR_PTR);
+ * 
+ */ +#define AVS_COMMONS_HAVE_DLSYM + +/** + * Enables the default implementation of avs_crypto engine, based on OpenSSL and + * PKCS#11. + * + * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE to be enabled. + * + * NOTE: Query string format for this engine is a subset of the PKCS#11 URI + * scheme (see RFC 7512), modelled after the format accepted by libp11 OpenSSL + * engine. + * + * NOTE: The unit tests for this feature depend on SoftHSM and pkcs11-tool. + * These must be installed for the tests to pass. + * + * IMPORTANT: Only available as part of the HSM support commercial feature. + * Ignored in the open source version. + */ +/* #undef AVS_COMMONS_WITH_OPENSSL_PKCS11_ENGINE */ +/**@}*/ + +/** + * Enable support for HTTP content compression in avs_http. + * + * Requires linking with zlib. + */ +/* #undef AVS_COMMONS_HTTP_WITH_ZLIB */ + +/** + * Options related to avs_log and logging support within avs_commons. + */ +/**@{*/ +/* clang-format off */ +/** + * Size, in bytes, of the avs_log buffer. + * + * Log messages that would (including the level, module name and code location) + * otherwise be longer than this value minus one (for the terminating null + * character) will be truncated. + * + * NOTE: This macro MUST be defined if avs_log is enabled. + * + * If editing this file manually, 512 shall + * be replaced with a positive integer literal. The default value defined in + * CMake build scripts is 512. + */ +#define AVS_COMMONS_LOG_MAX_LINE_LENGTH 512 +/* clang-format on */ + +/** + * Configures avs_log to use a synchronized global buffer instead of allocating + * a buffer on the stack when constructing log messages. + * + * Requires avs_compat_threading to be enabled. + * + * Enabling this option would reduce the stack space required to use avs_log, at + * the expense of global storage and the complexity of using a mutex. + */ +/* #undef AVS_COMMONS_LOG_USE_GLOBAL_BUFFER */ + +/** + * Provides a default avs_log handler that prints log messages on stderr. + * + * Disabling this option will cause logs to be discarded by default, until a + * custom log handler is set using avs_log_set_handler(). + */ +#define AVS_COMMONS_LOG_WITH_DEFAULT_HANDLER + +/** + * Enables the "micro logs" feature. + * + * Replaces all occurrences of the AVS_DISPOSABLE_LOG() macro with single + * space strings. This is intended to reduce the size of the compiled code, by + * stripping it of almost all log string data. + * + * Note that this setting will propagate both to avs_commons components + * themselves (as all its internal logs make use of AVS_DISPOSABLE_LOG()) + * and the user code that uses it. + */ +/* #undef AVS_COMMONS_WITH_MICRO_LOGS */ + +/** + * Enables logging inside avs_commons. + * + * Requires @ref AVS_COMMONS_WITH_AVS_LOG to be enabled. + * + * If this macro is not defined at avs_commons compile time, calls to avs_log + * will not be generated inside avs_commons components. + */ +#define AVS_COMMONS_WITH_INTERNAL_LOGS + +/** + * Enables TRACE-level logs inside avs_commons. + * + * Only meaningful if AVS_COMMONS_WITH_INTERNAL_LOGS is enabled. + * + * If this macro is not defined at avs_commons compile time, calls to avs_log + * with the level set to TRACE will not be generated inside avs_commons + * components. + */ +/* #undef AVS_COMMONS_WITH_INTERNAL_TRACE */ + +/** + * Enables external implementation of logger subsystem with provided header. + * + * Default logger implementation can be found in avs_log_impl.h + */ +/* #undef AVS_COMMONS_WITH_EXTERNAL_LOGGER_HEADER */ + +/** + * If specified, the process of checking if avs_log should be written out + * takes place in compile time. + * + * Specify an optional header with a list of modules for which log level + * is set. If a log level for specific module is not set, the DEFAULT level + * will be taken into account. Value of the default logging level is set to + * DEBUG, but can be overwritten in this header file with AVS_LOG_LEVEL_DEFAULT + * define. Messages with lower level than the one set will be removed during + * compile time. Possible values match @ref avs_log_level_t. + * + * That file should contain C preprocesor defines in the: + * - "#define AVS_LOG_LEVEL_FOR_MODULE_ " format, + * where is the module name and is allowed logging level + * - "#define AVS_LOG_LEVEL_DEFAULT " format, where is the + * allowed logging level + * + * Example file content: + * + * + * #ifndef AVS_COMMONS_EXTERNAL_LOG_LEVELS_H + * #define AVS_COMMONS_EXTERNAL_LOG_LEVELS_H + * + * // global log level value + * #define AVS_LOG_LEVEL_DEFAULT INFO + * + * //for "coap" messages only WARNING and ERROR messages will be present + * #define AVS_LOG_LEVEL_FOR_MODULE_coap WARNING + * + * //logs are disable for "net" module + * #define AVS_LOG_LEVEL_FOR_MODULE_net QUIET + * + * #endif + * + */ +/* #undef AVS_COMMONS_WITH_EXTERNAL_LOG_LEVELS_HEADER */ + +/** + * Disable log level check in runtime. Allows to save at least 1.3kB of memory. + * + * The macros avs_log_set_level and avs_log_set_default_level + * will not be available. + * + */ +/* #undef AVS_COMMONS_WITHOUT_LOG_CHECK_IN_RUNTIME */ +/**@}*/ + +/** + * Options related to avs_net. + */ +/**@{*/ +/** + * Enables support for IPv4 connectivity. + * + * At least one of AVS_COMMONS_NET_WITH_IPV4 and AVS_COMMONS_NET_WITH_IPV6 + * MUST be defined if avs_net is enabled. + */ +#define AVS_COMMONS_NET_WITH_IPV4 + +/** + * Enables support for IPv6 connectivity. + * + * At least one of AVS_COMMONS_NET_WITH_IPV4 and AVS_COMMONS_NET_WITH_IPV6 + * MUST be defined if avs_net is enabled. + */ +#define AVS_COMMONS_NET_WITH_IPV6 + +/** + * If the TLS backend is set to OpenSSL, enables support for DTLS. + * + * DTLS is always enabled for the mbed TLS and TinyDTLS backends. + */ +/* #undef AVS_COMMONS_NET_WITH_DTLS */ + +/** + * Enables debug logs generated by mbed TLS. + * + * An avs_log-backed handler, logging for the "mbedtls" module on the TRACE + * level, is installed using mbedtls_ssl_conf_dbg() for each (D)TLS + * socket created if this option is enabled. + */ +/* #undef AVS_COMMONS_NET_WITH_MBEDTLS_LOGS */ + +/** + * Enables the default implementation of avs_net TCP and UDP sockets. + * + * Requires either a UNIX-like operating environment, or a compatibility layer + * with a high degree of compatibility with standard BSD sockets with an + * appropriate compatibility header (see @ref AVS_COMMONS_POSIX_COMPAT_HEADER) - + * lwIP and Winsock are currently supported for this scenario. + */ +#define AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET + +/** + * Enables support for logging socket communication to file. + * + * If this option is enabled, avs_net_socket_debug() can be used to enable + * logging all communication to a file called DEBUG.log. If disabled, + * avs_net_socket_debug() will always return an error. + */ +/* #undef AVS_COMMONS_NET_WITH_SOCKET_LOG */ + +/** + * If the TLS backend is either mbed TLS or OpenSSL, enables support for (D)TLS + * session persistence. + * + * Session persistence is not currently supported for the TinyDTLS backend. + */ +#define AVS_COMMONS_NET_WITH_TLS_SESSION_PERSISTENCE +/**@}*/ + +/** + * Options related to avs_net's default implementation of TCP and UDP sockets. + * + * These options make sense only when @ref AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET + * is enabled. They describe capabilities of the Unix-like environment in which + * the library is built. + * + * Note that if @ref AVS_COMMONS_POSIX_COMPAT_HEADER is defined, it might + * redefine these flags independently of the settings in this file. + */ +/**@{*/ +/** + * Is the gai_strerror() function available? + * + * Enabling this flag will provide more detailed log messages in case that + * getaddrinfo() fails. If this flag is disabled, numeric error codes + * values will be logged. + */ +#define AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GAI_STRERROR + +/** + * Is the getifaddrs() function available? + * + * Disabling this flag will cause avs_net_socket_interface_name() to use + * a less optimal implementation based on the SIOCGIFCONF ioctl. + * + * If SIOCGIFCONF is not defined, either, then + * avs_net_socket_interface_name() will always return an error. + */ +#define AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GETIFADDRS + +/** + * Is the getnameinfo() function available? + * + * Disabling this flag will cause avs_net_socket_receive_from(), + * avs_net_socket_accept(), + * avs_net_resolved_endpoint_get_host_port(), + * avs_net_resolved_endpoint_get_host() and + * avs_net_resolve_host_simple() to use a custom reimplementation of + * getnameinfo() based on inet_ntop(). + */ +#define AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GETNAMEINFO + +/** + * Is the IN6_IS_ADDR_V4MAPPED macro available and usable? + * + * Disabling this flag will cause a custom code that compares IPv6 addresses + * with the ::ffff:0.0.0.0/32 mask to be used instead. + */ +#define AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_IN6_IS_ADDR_V4MAPPED + +/** + * Should be defined if IPv4-mapped IPv6 addresses (::ffff:0.0.0.0/32) + * are NOT supported by the underlying platform. + * + * Enabling this flag will prevent avs_net from using IPv4-mapped IPv6 addresses + * and instead re-open and re-bind the socket if a connection to an IPv4 address + * is requested on a previously created IPv6 socket. + * + * This may result in otherwise redundant socket(), bind() and + * close() system calls to be performed, but may be necessary for + * interoperability with some platforms. + */ +/* #undef AVS_COMMONS_NET_POSIX_AVS_SOCKET_WITHOUT_IN6_V4MAPPED_SUPPORT */ + +/** + * Is the inet_ntop() function available? + * + * Disabling this flag will cause an internal implementation of this function + * adapted from BIND 4.9.4 to be used instead. + */ +#define AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_INET_NTOP + +/** + * Is the poll() function available? + * + * Disabling this flag will cause a less robust code based on select() to + * be used instead. + */ +#define AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_POLL + +/** + * Is the recvmsg() function available? + * + * Disabling this flag will cause recvfrom() to be used instead. Note + * that for UDP sockets, this will cause false positives for datagram truncation + * detection (AVS_EMSGSIZE) to be reported when the received message is + * exactly the size of the buffer. + */ +#define AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_RECVMSG +/**@}*/ + +/** + * Enable thread safety in avs_sched. + * + * Makes all scheduler accesses synchronized and thread-safe, at the cost of + * requiring avs_compat_threading to be enabled, and higher resource usage. + */ +#define AVS_COMMONS_SCHED_THREAD_SAFE + +/** + * Enable support for file I/O in avs_stream. + * + * Disabling this flag will cause the functions declared in + * avs_stream_file.h to not be defined. + */ +#define AVS_COMMONS_STREAM_WITH_FILE + +/** + * Enable usage of backtrace() and backtrace_symbols() when + * reporting assertion failures from avs_unit. + * + * Requires the afore-mentioned GNU-specific functions to be available. + * + * If this flag is disabled, stack traces will not be displayed with assertion + * failures. + */ +#define AVS_COMMONS_UNIT_POSIX_HAVE_BACKTRACE + +/** + * Options related to avs_utils. + */ +/**@{*/ +/** + * Enable the default implementation of avs_time_real_now() and + * avs_time_monotonic_now(). + * + * Requires an operating environment that supports a clock_gettime() call + * compatible with POSIX. + */ +#define AVS_COMMONS_UTILS_WITH_POSIX_AVS_TIME + +/** + * Enable the default implementation of avs_malloc(), avs_free(), avs_calloc() + * and avs_realloc() that forwards to system malloc(), free(), calloc() and + * realloc() calls. + * + * You might disable this option if for any reason you need to use a custom + * allocator. + */ +#define AVS_COMMONS_UTILS_WITH_STANDARD_ALLOCATOR + +/** + * Enable the alternate implementation of avs_malloc(), avs_free(), avs_calloc() + * and avs_realloc() that uses system malloc(), free() and realloc() calls, but + * includes additional fixup code that ensures proper alignment to + * AVS_ALIGNOF(avs_max_align_t) (usually 8 bytes on common platforms). + * + * AVS_COMMONS_UTILS_WITH_STANDARD_ALLOCATOR and + * AVS_COMMONS_UTILS_WITH_ALIGNFIX_ALLOCATOR cannot be enabled at the + * same time. + * + * NOTE: This implementation is only intended for platforms where the system + * allocator does not properly conform to the alignment requirements. + * + * It comes with an additional runtime costs: + * + * - AVS_ALIGNOF(avs_max_align_t) bytes (usually 8) of additional + * overhead for each allocated memory block + * - Additional memmove() for every realloc() that returned a block that is not + * properly aligned + * - avs_calloc() is implemented as avs_malloc() followed by an explicit + * memset(); this may be suboptimal on some platforms + * + * If these costs are unacceptable for you, you may want to consider fixing, + * replacing or reconfiguring your system allocator for conformance, or + * implementing a custom one instead. + * + * Please note that some code in avs_commons and dependent projects (e.g. Anjay) + * may include runtime assertions for proper memory alignment that will be + * triggered when using a non-conformant standard allocator. Such allocators are + * relatively common in embedded SDKs. This "alignfix" allocator is intended to + * work around these issues. On some platforms (e.g. x86) those alignment issues + * may not actually cause any problems - so you may want to consider disabling + * runtime assertions instead. Please carefully examine your target platform's + * alignment requirements and behavior of misaligned memory accesses (including + * 64-bit data types such as int64_t and double) before doing so. + */ +/* #undef AVS_COMMONS_UTILS_WITH_ALIGNFIX_ALLOCATOR */ +/**@}*/ + +#endif /* AVS_COMMONS_CONFIG_H */ diff --git a/src/anj/compat/posix/anj_time.c b/src/anj/compat/posix/anj_time.c new file mode 100644 index 00000000..aebc6738 --- /dev/null +++ b/src/anj/compat/posix/anj_time.c @@ -0,0 +1,28 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#define _GNU_SOURCE + +#include +#include + +#if ANJ_TIME_POSIX_COMPAT + +# include + +uint64_t anj_time_now(void) { + struct timespec res; + if (clock_gettime(CLOCK_MONOTONIC, &res)) { + return 0; + } + return (uint64_t) res.tv_sec * 1000 + + (uint64_t) res.tv_nsec / (1000 * 1000); +} + +#endif // ANJ_TIME_POSIX_COMPAT diff --git a/src/anj/dm/dm_execute.c b/src/anj/dm/dm_execute.c new file mode 100644 index 00000000..119e76cf --- /dev/null +++ b/src/anj/dm/dm_execute.c @@ -0,0 +1,53 @@ +/* + * Copyright 2023-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 "../dm_core.h" +#include "../dm_utils/dm_utils_core.h" + +int dm_execute(dm_t *dm, const fluf_uri_path_t *uri) { + AVS_ASSERT(dm, "dm is NULL"); + AVS_ASSERT(uri, "uri is NULL"); + + dm_log(DEBUG, _("Execute ") "%s", DM_DEBUG_MAKE_PATH(uri)); + if (!fluf_uri_path_is(uri, FLUF_ID_RID)) { + dm_log(WARNING, + _("Executable URI must point to resource. Actual: ") "%s", + DM_DEBUG_MAKE_PATH(uri)); + return FLUF_COAP_CODE_METHOD_NOT_ALLOWED; + } + + const dm_installed_object_t *obj; + int result = _dm_find_object(dm, uri, &obj); + if (result) { + return result; + } + + if ((result = + _dm_verify_instance_present(dm, obj, uri->ids[FLUF_ID_IID]))) { + dm_log(WARNING, _("Instance is not present.")); + return result; + } + dm_resource_kind_t kind; + if ((result = _dm_verify_resource_present(dm, obj, uri->ids[FLUF_ID_IID], + uri->ids[FLUF_ID_RID], &kind))) { + dm_log(WARNING, _("Resource is not present.")); + return result; + } + if (!_dm_res_kind_executable(kind)) { + dm_log(DEBUG, "%s" _(" is not executable"), DM_DEBUG_MAKE_PATH(uri)); + return FLUF_COAP_CODE_METHOD_NOT_ALLOWED; + } + if ((result = _dm_call_resource_execute(dm, obj, uri->ids[FLUF_ID_IID], + uri->ids[FLUF_ID_RID]))) { + dm_log(WARNING, _("Resource execute handler failed: ") "%d", result); + } + return result; +} diff --git a/src/anj/dm/dm_handlers.c b/src/anj/dm/dm_handlers.c new file mode 100644 index 00000000..42671915 --- /dev/null +++ b/src/anj/dm/dm_handlers.c @@ -0,0 +1,123 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#include "../dm_core.h" +#include "../dm_utils/dm_utils_core.h" + +int _dm_call_list_instances(dm_t *dm, + const dm_installed_object_t *obj, + dm_list_ctx_t *instance_list_ctx) { + dm_log(TRACE, _("list_instances ") "/%" PRIu16, + _dm_installed_object_oid(obj)); + if (!(*obj->def)->handlers.list_instances) { + dm_log(DEBUG, + "list_instances" _(" handler not set for object ") "/%" PRIu16, + _dm_installed_object_oid(obj)); + return FLUF_COAP_CODE_METHOD_NOT_ALLOWED; + } + return (*obj->def)->handlers.list_instances(dm, obj->def, + instance_list_ctx); +} + +int _dm_call_list_resources(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + dm_resource_list_ctx_t *resource_list_ctx) { + dm_log(TRACE, _("list_resources ") "/%" PRIu16 "/%" PRIu16, + _dm_installed_object_oid(obj), iid); + if (!(*obj->def)->handlers.list_resources) { + dm_log(DEBUG, + "list_resources" _(" handler not set for object ") "/%" PRIu16, + _dm_installed_object_oid(obj)); + return FLUF_COAP_CODE_METHOD_NOT_ALLOWED; + } + return (*obj->def)->handlers.list_resources(dm, obj->def, iid, + resource_list_ctx); +} + +int _dm_call_list_resource_instances(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + dm_list_ctx_t *list_ctx) { + dm_log(TRACE, + _("list_resource_instances ") "/%" PRIu16 "/%" PRIu16 "/%" PRIu16, + _dm_installed_object_oid(obj), iid, rid); + if (!(*obj->def)->handlers.list_resource_instances) { + dm_log(DEBUG, + "list_resource_instances" _( + " handler not set for object ") "/%" PRIu16, + _dm_installed_object_oid(obj)); + return FLUF_COAP_CODE_METHOD_NOT_ALLOWED; + } + return (*obj->def)->handlers.list_resource_instances(dm, obj->def, iid, rid, + list_ctx); +} + +int _dm_call_resource_read(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + fluf_riid_t riid, + dm_output_internal_ctx_t *internal_out_ctx) { + dm_log(TRACE, _("resource_read ") "%s", + DM_DEBUG_MAKE_PATH(&FLUF_MAKE_RESOURCE_INSTANCE_PATH( + _dm_installed_object_oid(obj), iid, rid, riid))); + if (!(*obj->def)->handlers.resource_read) { + dm_log(DEBUG, + "resource_read" _(" handler not set for object ") "/%" PRIu16, + _dm_installed_object_oid(obj)); + return FLUF_COAP_CODE_METHOD_NOT_ALLOWED; + } + return (*obj->def)->handlers.resource_read( + dm, obj->def, iid, rid, riid, (dm_output_ctx_t *) internal_out_ctx); +} + +int dm_list_instances_SINGLE(dm_t *dm, + const dm_object_def_t *const *obj_ptr, + dm_list_ctx_t *ctx) { + (void) dm; + (void) obj_ptr; + dm_emit(ctx, 0); + return 0; +} + +int _dm_call_resource_write(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + fluf_riid_t riid, + dm_input_internal_ctx_t *internal_in_ctx) { + dm_log(TRACE, _("resource_write ") "%s", + DM_DEBUG_MAKE_PATH(&FLUF_MAKE_RESOURCE_INSTANCE_PATH( + _dm_installed_object_oid(obj), iid, rid, riid))); + if (!(*obj->def)->handlers.resource_write) { + dm_log(DEBUG, + "resource_read" _(" handler not set for object ") "/%" PRIu16, + _dm_installed_object_oid(obj)); + return FLUF_COAP_CODE_METHOD_NOT_ALLOWED; + } + return (*obj->def)->handlers.resource_write( + dm, obj->def, iid, rid, riid, (dm_input_ctx_t *) internal_in_ctx); +} + +int _dm_call_resource_execute(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid) { + dm_log(TRACE, _("resource_execute ") "/%" PRIu16 "/%" PRIu16 "/%" PRIu16, + _dm_installed_object_oid(obj), iid, rid); + if (!(*obj->def)->handlers.resource_execute) { + dm_log(DEBUG, + "resource_read" _(" handler not set for object ") "/%" PRIu16, + _dm_installed_object_oid(obj)); + return FLUF_COAP_CODE_METHOD_NOT_ALLOWED; + } + return (*obj->def)->handlers.resource_execute(dm, obj->def, iid, rid, NULL); +} diff --git a/src/anj/dm/dm_read.c b/src/anj/dm/dm_read.c new file mode 100644 index 00000000..8c3a0e5d --- /dev/null +++ b/src/anj/dm/dm_read.c @@ -0,0 +1,235 @@ +/* + * Copyright 2023-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 "../dm_core.h" +#include "../dm_utils/dm_utils_core.h" + +static int read_resource_instance_internal(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + fluf_riid_t riid, + dm_output_ctx_t *output_ctx) { + dm_output_internal_ctx_t internal_out_ctx = { + .output_ctx = output_ctx, + .path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(_dm_installed_object_oid(obj), + iid, rid, riid), + }; + return _dm_call_resource_read(dm, obj, iid, rid, riid, &internal_out_ctx); +} + +static int read_resource_instance_clb(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + fluf_riid_t riid, + void *out_ctx_) { + dm_output_ctx_t *out_ctx = (dm_output_ctx_t *) out_ctx_; + return read_resource_instance_internal(dm, obj, iid, rid, riid, out_ctx); +} + +static int read_multiple_resource(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + dm_output_ctx_t *out_ctx) { + return _dm_foreach_resource_instance(dm, obj, iid, rid, + read_resource_instance_clb, out_ctx); +} + +static int read_resource_internal(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + dm_resource_kind_t kind, + dm_output_ctx_t *output_ctx) { + if (_dm_res_kind_multiple(kind)) { + return read_multiple_resource(dm, obj, iid, rid, output_ctx); + } + dm_output_internal_ctx_t internal_out_ctx = { + .output_ctx = output_ctx, + .path = FLUF_MAKE_RESOURCE_PATH(_dm_installed_object_oid(obj), iid, + rid), + }; + return _dm_call_resource_read(dm, obj, iid, rid, FLUF_ID_INVALID, + &internal_out_ctx); +} + +static int verify_resource(dm_resource_kind_t kind, + dm_resource_presence_t presence) { + if (!(_dm_res_kind_readable(kind))) { + return FLUF_COAP_CODE_METHOD_NOT_ALLOWED; + } + if (presence != DM_RES_PRESENT) { + return FLUF_COAP_CODE_NOT_FOUND; + } + return 0; +} + +static int read_resource_instance(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + fluf_riid_t riid, + dm_output_ctx_t *out_ctx) { + assert(riid != FLUF_ID_INVALID); + dm_resource_kind_t kind; + dm_resource_presence_t presence; + int result = + _dm_resource_kind_and_presence(dm, obj, iid, rid, &kind, &presence); + if (result) { + return result; + } + if ((result = verify_resource(kind, presence))) { + dm_log(DEBUG, + "/%" PRIu16 "/%" PRIu16 + "/%" PRIu16 _(" not present or not readable"), + _dm_installed_object_oid(obj), iid, rid); + return result; + } + if (!_dm_res_kind_multiple(kind)) { + dm_log(DEBUG, + "/%" PRIu16 "/%" PRIu16 "/%" PRIu16 + "/%" PRIu16 _(" points to resource instance but is not " + "a multiple resource"), + _dm_installed_object_oid(obj), iid, rid, riid); + return FLUF_COAP_CODE_METHOD_NOT_ALLOWED; + } + if ((result = _dm_resource_instance_existence(dm, obj, iid, rid, riid))) { + dm_log(DEBUG, + "/%" PRIu16 "/%" PRIu16 "/%" PRIu16 + "/%" PRIu16 _(" not present"), + _dm_installed_object_oid(obj), iid, rid, riid); + return result; + } + return read_resource_instance_internal(dm, obj, iid, rid, riid, out_ctx); +} + +static int read_resource(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + dm_output_ctx_t *out_ctx) { + + dm_resource_kind_t kind; + dm_resource_presence_t presence; + int result = + _dm_resource_kind_and_presence(dm, obj, iid, rid, &kind, &presence); + if (result) { + return result; + } + result = verify_resource(kind, presence); + if (result) { + dm_log(DEBUG, + "/%" PRIu16 "/%" PRIu16 + "/%" PRIu16 _(" not present or not readable"), + _dm_installed_object_oid(obj), iid, rid); + return result; + } + return read_resource_internal(dm, obj, iid, rid, kind, out_ctx); +} + +static int read_instance_resource_clb(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + dm_resource_kind_t kind, + dm_resource_presence_t presence, + void *args_) { + dm_output_ctx_t *args = (dm_output_ctx_t *) args_; + + if (verify_resource(kind, presence)) { + dm_log(DEBUG, + "/%" PRIu16 "/%" PRIu16 + "/%" PRIu16 _(" not present or not readable, skipping"), + _dm_installed_object_oid(obj), iid, rid); + return 0; + } + return read_resource_internal(dm, obj, iid, rid, kind, args); +} + +static int read_instance(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + dm_output_ctx_t *out_ctx) { + int result = _dm_verify_instance_present(dm, obj, iid); + if (result) { + return result; + } + return _dm_foreach_resource(dm, obj, iid, read_instance_resource_clb, + out_ctx); +} + +static int read_instance_clb(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + void *out_ctx_) { + dm_output_ctx_t *out_ctx = (dm_output_ctx_t *) out_ctx_; + return read_instance(dm, obj, iid, out_ctx); +} + +static int read_object(dm_t *dm, + const dm_installed_object_t *obj, + const fluf_uri_path_t *uri, + dm_output_ctx_t *out_ctx) { + assert(fluf_uri_path_has(uri, FLUF_ID_OID)); + return _dm_foreach_instance(dm, obj, read_instance_clb, out_ctx); +} + +static int +read_object_clb(dm_t *dm, const dm_installed_object_t *obj, void *out_ctx_) { + if (_dm_installed_object_oid(obj) == DM_OID_SECURITY) { + return DM_FOREACH_CONTINUE; + } + dm_output_ctx_t *out_ctx = (dm_output_ctx_t *) out_ctx_; + return read_object(dm, obj, + &FLUF_MAKE_OBJECT_PATH(_dm_installed_object_oid(obj)), + out_ctx); +} + +static int read_root(dm_t *dm, dm_output_ctx_t *out_ctx) { + return _dm_foreach_object(dm, read_object_clb, out_ctx); +} + +int dm_read(dm_t *dm, const fluf_uri_path_t *uri, dm_output_ctx_t *out_ctx) { + AVS_ASSERT(dm, "dm is NULL"); + AVS_ASSERT(uri, "uri is NULL"); + AVS_ASSERT(out_ctx, "input_ctx is NULL"); + AVS_ASSERT(out_ctx->callback, "input_ctx->callback is NULL"); + + if (!dm->objects_count) { + return FLUF_COAP_CODE_NOT_FOUND; + } + if (fluf_uri_path_length(uri) == 0) { + return read_root(dm, out_ctx); + } + const dm_installed_object_t *obj; + int result = _dm_find_object(dm, uri, &obj); + if (result) { + return result; + } + assert(obj); + assert(uri->ids[FLUF_ID_OID] == _dm_installed_object_oid(obj)); + if (fluf_uri_path_is(uri, FLUF_ID_OID)) { + return read_object(dm, obj, uri, out_ctx); + } + if (fluf_uri_path_is(uri, FLUF_ID_IID)) { + return read_instance(dm, obj, uri->ids[FLUF_ID_IID], out_ctx); + } + if (fluf_uri_path_is(uri, FLUF_ID_RID)) { + return read_resource(dm, obj, uri->ids[FLUF_ID_IID], + uri->ids[FLUF_ID_RID], out_ctx); + } + assert(fluf_uri_path_is(uri, FLUF_ID_RIID)); + return read_resource_instance(dm, obj, uri->ids[FLUF_ID_IID], + uri->ids[FLUF_ID_RID], uri->ids[FLUF_ID_RIID], + out_ctx); +} diff --git a/src/anj/dm/dm_write.c b/src/anj/dm/dm_write.c new file mode 100644 index 00000000..dcea9d98 --- /dev/null +++ b/src/anj/dm/dm_write.c @@ -0,0 +1,220 @@ +/* + * Copyright 2023-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 "../dm_core.h" +#include "../dm_utils/dm_utils.h" +#include "../dm_utils/dm_utils_core.h" + +static int +preverify_resource_before_writing(dm_t *dm, + const dm_installed_object_t *obj, + const fluf_uri_path_t *payload_path, + dm_resource_kind_t *out_kind, + dm_resource_presence_t *out_presence) { + assert(payload_path); + assert(out_kind); + assert(fluf_uri_path_has(payload_path, FLUF_ID_RID)); + assert(payload_path->ids[FLUF_ID_OID] == _dm_installed_object_oid(obj)); + int result = _dm_resource_kind_and_presence(dm, obj, + payload_path->ids[FLUF_ID_IID], + payload_path->ids[FLUF_ID_RID], + out_kind, out_presence); + if (result) { + return result; + } + if (!_dm_res_kind_writable(*out_kind)) { + dm_log(DEBUG, "%s" _(" is not writable"), + DM_DEBUG_MAKE_PATH(payload_path)); + return FLUF_COAP_CODE_METHOD_NOT_ALLOWED; + } + + if (fluf_uri_path_has(payload_path, FLUF_ID_RIID) + && !_dm_res_kind_multiple(*out_kind)) { + dm_log(DEBUG, + _("cannot write ") "%s" _(" because the path does not point " + "inside a multiple resource"), + DM_DEBUG_MAKE_PATH(payload_path)); + return FLUF_COAP_CODE_METHOD_NOT_ALLOWED; + } + + return 0; +} + +static int call_resource_write(dm_t *dm, + const dm_installed_object_t *obj, + const fluf_uri_path_t *path, + dm_input_ctx_t *input_ctx) { + fluf_io_out_entry_t entry; + memset(&entry, 0, sizeof(entry)); + dm_input_internal_ctx_t internal_input_ctx = { + .input_ctx = input_ctx, + .provided_entry = &entry, + }; + + return _dm_call_resource_write(dm, obj, path->ids[FLUF_ID_IID], + path->ids[FLUF_ID_RID], + path->ids[FLUF_ID_RIID], + &internal_input_ctx); +} + +static int write_resource_instance(dm_t *dm, + const dm_installed_object_t *obj, + const fluf_uri_path_t *path, + dm_input_ctx_t *in_ctx) { + assert(fluf_uri_path_is(path, FLUF_ID_RIID)); + + int result = _dm_verify_resource_instance_present(dm, obj, + path->ids[FLUF_ID_IID], + path->ids[FLUF_ID_RID], + path->ids[FLUF_ID_RIID]); + if (result) { + return result; + } + + return call_resource_write(dm, obj, path, in_ctx); +} + +static int write_single_resource(dm_t *dm, + const dm_installed_object_t *obj, + const fluf_uri_path_t *path, + dm_input_ctx_t *in_ctx) { + assert(fluf_uri_path_has(path, FLUF_ID_RID)); + return call_resource_write(dm, obj, path, in_ctx); +} + +static int write_resource_instance_clb(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + fluf_riid_t riid, + void *in_ctx_) { + dm_input_ctx_t *in_ctx = (dm_input_ctx_t *) in_ctx_; + return call_resource_write( + dm, obj, + &FLUF_MAKE_RESOURCE_INSTANCE_PATH(_dm_installed_object_oid(obj), + iid, rid, riid), + in_ctx); +} + +static int write_multiple_resource(dm_t *dm, + const dm_installed_object_t *obj, + const fluf_uri_path_t *first_path, + dm_input_ctx_t *in_ctx) { + fluf_uri_path_t path = *first_path; + assert(fluf_uri_path_has(&path, FLUF_ID_RID)); + return (_dm_foreach_resource_instance(dm, obj, first_path->ids[FLUF_ID_IID], + first_path->ids[FLUF_ID_RID], + write_resource_instance_clb, in_ctx)); +} + +static int write_resource(dm_t *dm, + const dm_installed_object_t *obj, + const fluf_uri_path_t *path, + dm_resource_kind_t kind, + dm_input_ctx_t *in_ctx) { + if (_dm_res_kind_multiple(kind)) { + return write_multiple_resource(dm, obj, path, in_ctx); + } + return write_single_resource(dm, obj, path, in_ctx); +} + +static int write_instance_resource_clb(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + dm_resource_kind_t kind, + dm_resource_presence_t presence, + void *args_) { + dm_input_ctx_t *args = (dm_input_ctx_t *) args_; + if (presence == DM_RES_ABSENT) { + dm_log(DEBUG, + "/%" PRIu16 "/%" PRIu16 + "/%" PRIu16 _(" is not present, skipping"), + _dm_installed_object_oid(obj), iid, rid); + return 0; + } + bool write_allowed = _dm_res_kind_writable(kind); + if (!write_allowed) { + dm_log(DEBUG, + "/%" PRIu16 "/%" PRIu16 + "/%" PRIu16 _(" is not writeable, skipping"), + _dm_installed_object_oid(obj), iid, rid); + return 0; + } + + return write_resource( + dm, obj, + &FLUF_MAKE_RESOURCE_PATH(_dm_installed_object_oid(obj), iid, rid), + kind, args); +} + +static int write_instance(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + dm_input_ctx_t *in_ctx) { + return _dm_foreach_resource(dm, obj, iid, write_instance_resource_clb, + in_ctx); +} + +/** Writes to data model. + * Actually only WRITE_TYPE_UPDATE (without creating instances or resource + * instances) is supported. WRITE_TYPE_REPLACE is not possible. */ +int dm_write(dm_t *dm, const fluf_uri_path_t *uri, dm_input_ctx_t *in_ctx) { + AVS_ASSERT(dm, "dm is NULL"); + AVS_ASSERT(uri, "uri is NULL"); + AVS_ASSERT(in_ctx, "input_ctx is NULL"); + AVS_ASSERT(in_ctx->callback, "input_ctx->callback is NULL"); + + dm_log(DEBUG, _("Write ") "%s", DM_DEBUG_MAKE_PATH(uri)); + if (!fluf_uri_path_has(uri, FLUF_ID_IID)) { + return FLUF_COAP_CODE_METHOD_NOT_ALLOWED; + } + + const dm_installed_object_t *obj; + int result = _dm_find_object(dm, uri, &obj); + if (result) { + return result; + } + + if ((result = + _dm_verify_instance_present(dm, obj, uri->ids[FLUF_ID_IID]))) { + return result; + } + + if (fluf_uri_path_is(uri, FLUF_ID_IID)) { + return write_instance(dm, obj, uri->ids[FLUF_ID_IID], in_ctx); + } + dm_resource_kind_t kind; + dm_resource_presence_t presence; + if ((result = preverify_resource_before_writing(dm, obj, uri, &kind, + &presence))) { + return result; + } + if (presence == DM_RES_ABSENT) { + return FLUF_COAP_CODE_NOT_FOUND; + } + if (fluf_uri_path_is(uri, FLUF_ID_RID)) { + return write_resource(dm, obj, uri, kind, in_ctx); + } + assert(fluf_uri_path_is(uri, FLUF_ID_RIID)); + return write_resource_instance(dm, obj, uri, in_ctx); +} diff --git a/src/anj/dm_core.c b/src/anj/dm_core.c new file mode 100644 index 00000000..e9737014 --- /dev/null +++ b/src/anj/dm_core.c @@ -0,0 +1,957 @@ +/* + * Copyright 2023-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 "dm_core.h" +#include "dm_utils/dm_utils.h" +#include "dm_utils/dm_utils_core.h" + +static int validate_version(const dm_object_def_t *const *def_ptr) { + const char *version = (*def_ptr)->version; + if (!version) { + return 0; + } + // accepted format is X.Y where X and Y are digits + if (!isdigit(version[0]) || version[1] != '.' || !isdigit(version[2]) + || version[3] != '\0') { + return FLUF_IO_ERR_INPUT_ARG; + } + return 0; +} + +int dm_initialize(dm_t *dm, dm_installed_object_t *objects, size_t max_count) { + dm->objects = objects; + dm->objects_count = 0; + dm->objects_count_max = max_count; + return 0; +} + +int dm_register_object(dm_t *dm, const dm_object_def_t *const *def_ptr) { + AVS_ASSERT(dm, "dm is NULL"); + AVS_ASSERT(def_ptr, "def_ptr is NULL"); + + if ((*def_ptr)->oid == FLUF_ID_INVALID) { + dm_log(ERROR, + _("Object ID ") "%" PRIu16 _( + " is forbidden by the LwM2M 1.1 specification"), + FLUF_ID_INVALID); + return FLUF_COAP_CODE_INTERNAL_SERVER_ERROR; + } + + if (dm->objects_count == dm->objects_count_max) { + dm_log(ERROR, _("Too many objects registered")); + return FLUF_COAP_CODE_INTERNAL_SERVER_ERROR; + } + + if (validate_version(def_ptr)) { + return FLUF_COAP_CODE_INTERNAL_SERVER_ERROR; + } + + for (size_t i = 0; i < dm->objects_count; ++i) { + if (_dm_installed_object_oid(&dm->objects[i]) == (*def_ptr)->oid) { + dm_log(ERROR, + _("object ") "%" PRIu16 _(" is already registered"), + (*def_ptr)->oid); + return FLUF_COAP_CODE_INTERNAL_SERVER_ERROR; + } + } + + size_t i; + for (i = dm->objects_count; + i > 0 + && _dm_installed_object_oid(&dm->objects[i - 1]) > (*def_ptr)->oid; + --i) { + dm->objects[i] = dm->objects[i - 1]; + } + dm->objects[i].def = def_ptr; + dm->objects_count++; + dm_log(INFO, _("successfully registered object ") "/%" PRIu16, + (*def_ptr)->oid); + return 0; +} + +int dm_unregister_object(dm_t *dm, const dm_object_def_t *const *def_ptr) { + AVS_ASSERT(dm, "dm is NULL"); + AVS_ASSERT(def_ptr, "def_ptr is NULL"); + + if (!_dm_find_object_by_oid(dm, (*def_ptr)->oid)) { + dm_log(ERROR, + _("object ") "%" PRIu16 _(" is not currently registered"), + (*def_ptr)->oid); + return FLUF_COAP_CODE_INTERNAL_SERVER_ERROR; + } + + // if def_ptr points to the last object, we don't need to move anything + for (size_t i = 0; i < dm->objects_count - 1; ++i) { + if (_dm_installed_object_oid(&dm->objects[i]) >= (*def_ptr)->oid) { + dm->objects[i] = dm->objects[i + 1]; + } + } + dm->objects_count--; + + return 0; +} + +dm_installed_object_t *_dm_find_object_by_oid(dm_t *dm, fluf_oid_t oid) { + for (size_t i = 0; i < dm->objects_count; ++i) { + if (_dm_installed_object_oid(&dm->objects[i]) == oid) { + return &dm->objects[i]; + } + } + return NULL; +} + +int _dm_verify_instance_present(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid) { + return _dm_map_present_result(_dm_instance_present(dm, obj, iid)); +} + +int _dm_verify_resource_present(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + dm_resource_kind_t *out_kind) { + dm_resource_presence_t presence; + int retval = _dm_resource_kind_and_presence(dm, obj, iid, rid, out_kind, + &presence); + if (retval) { + return retval; + } + if (presence == DM_RES_ABSENT) { + return FLUF_COAP_CODE_NOT_FOUND; + } + return 0; +} + +typedef struct { + fluf_riid_t riid_to_find; + bool found; +} resource_instance_present_args_t; + +static int dm_resource_instance_present_clb(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + fluf_riid_t riid, + void *args_) { + (void) dm; + (void) obj; + (void) iid; + (void) rid; + resource_instance_present_args_t *args = + (resource_instance_present_args_t *) args_; + if (riid == args->riid_to_find) { + args->found = true; + return DM_FOREACH_BREAK; + } + return DM_FOREACH_CONTINUE; +} + +static int dm_resource_instance_present(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + fluf_riid_t riid) { + resource_instance_present_args_t args = { + .riid_to_find = riid, + .found = false + }; + int result = _dm_foreach_resource_instance( + dm, obj, iid, rid, dm_resource_instance_present_clb, &args); + if (result < 0) { + return result; + } + return args.found ? 1 : 0; +} + +int _dm_verify_resource_instance_present(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + fluf_riid_t riid) { + return _dm_map_present_result( + dm_resource_instance_present(dm, obj, iid, rid, riid)); +} + +int _dm_foreach_object(dm_t *dm, + dm_foreach_object_handler_t *handler, + void *data) { + for (size_t i = 0; i < dm->objects_count; ++i) { + dm_installed_object_t *obj = &dm->objects[i]; + int result = handler(dm, obj, data); + if (result == DM_FOREACH_BREAK) { + dm_log(TRACE, _("foreach_object: break on ") "/%" PRIu16, + _dm_installed_object_oid(obj)); + return 0; + } else if (result) { + dm_log(DEBUG, + _("foreach_object_handler failed for ") "/%" PRIu16 _( + " (") "%d" _(")"), + _dm_installed_object_oid(obj), result); + return result; + } + } + + return 0; +} + +typedef struct { + dm_list_ctx_emit_t *emit; + dm_t *dm; + const dm_installed_object_t *obj; + int32_t last_iid; + dm_foreach_instance_handler_t *handler; + void *handler_data; + int result; +} dm_foreach_instance_ctx_t; + +static void foreach_instance_emit(dm_list_ctx_t *ctx_, uint16_t iid) { + dm_foreach_instance_ctx_t *ctx = (dm_foreach_instance_ctx_t *) ctx_; + if (ctx->result) { + return; + } + if (iid == FLUF_ID_INVALID) { + dm_log(ERROR, "%" PRIu16 _(" is not a valid Instance ID"), iid); + ctx->result = FLUF_COAP_CODE_INTERNAL_SERVER_ERROR; + return; + } + if (iid <= ctx->last_iid) { + dm_log(ERROR, + _("list_instances MUST return Instance IDs in strictly " + "ascending order; ") "%" PRIu16 + _(" returned after ") "%" PRId32, + iid, ctx->last_iid); + ctx->result = FLUF_COAP_CODE_INTERNAL_SERVER_ERROR; + return; + } + ctx->last_iid = iid; + ctx->result = ctx->handler(ctx->dm, ctx->obj, iid, ctx->handler_data); + if (ctx->result == DM_FOREACH_BREAK) { + dm_log(TRACE, _("foreach_instance: break on ") "/%" PRIu16 "/%" PRIu16, + _dm_installed_object_oid(ctx->obj), iid); + } else if (ctx->result) { + dm_log(DEBUG, + _("foreach_instance_handler failed for ") "/%" PRIu16 "/%" PRIu16 + _(" (") "%d" _(")"), + _dm_installed_object_oid(ctx->obj), iid, ctx->result); + } +} + +int _dm_foreach_instance(dm_t *dm, + const dm_installed_object_t *obj, + dm_foreach_instance_handler_t *handler, + void *data) { + if (!obj) { + dm_log(ERROR, _("attempt to iterate through NULL Object")); + return FLUF_COAP_CODE_INTERNAL_SERVER_ERROR; + } + + dm_foreach_instance_ctx_t ctx = { + .emit = foreach_instance_emit, + .obj = obj, + .last_iid = -1, + .handler = handler, + .handler_data = data, + .result = 0 + }; + int result = _dm_call_list_instances(dm, obj, (dm_list_ctx_t *) &ctx); + if (result < 0) { + dm_log(WARNING, + _("list_instances handler for ") "/%" PRIu16 _( + " failed (") "%d" _(")"), + _dm_installed_object_oid(obj), result); + return result; + } + return ctx.result == DM_FOREACH_BREAK ? 0 : ctx.result; +} + +typedef struct { + fluf_iid_t iid_to_find; + bool found; +} instance_present_args_t; + +static int instance_present_clb(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + void *args_) { + (void) dm; + (void) obj; + instance_present_args_t *args = (instance_present_args_t *) args_; + if (iid >= args->iid_to_find) { + args->found = (iid == args->iid_to_find); + return DM_FOREACH_BREAK; + } + return DM_FOREACH_CONTINUE; +} + +int _dm_instance_present(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid) { + instance_present_args_t args = { + .iid_to_find = iid, + .found = false + }; + int retval = _dm_foreach_instance(dm, obj, instance_present_clb, &args); + if (retval < 0) { + return retval; + } + return args.found ? 1 : 0; +} + +struct dm_resource_list_ctx_struct { + dm_t *dm; + const dm_installed_object_t *obj; + fluf_iid_t iid; + int32_t last_rid; + dm_foreach_resource_handler_t *handler; + void *handler_data; + int result; +}; + +static bool presence_valid(dm_resource_presence_t presence) { + return presence == DM_RES_ABSENT || presence == DM_RES_PRESENT; +} + +void dm_emit_res(dm_resource_list_ctx_t *ctx, + fluf_rid_t rid, + dm_resource_kind_t kind, + dm_resource_presence_t presence) { + if (ctx->result) { + return; + } + if (rid == FLUF_ID_INVALID) { + dm_log(ERROR, "%" PRIu16 _(" is not a valid Resource ID"), rid); + ctx->result = FLUF_COAP_CODE_INTERNAL_SERVER_ERROR; + return; + } + if (rid <= ctx->last_rid) { + dm_log(ERROR, + _("list_resources MUST return Resource IDs in strictly " + "ascending order; ") "%" PRIu16 + _(" returned after ") "%" PRId32, + rid, ctx->last_rid); + ctx->result = FLUF_COAP_CODE_INTERNAL_SERVER_ERROR; + return; + } + ctx->last_rid = rid; + if (!_dm_res_kind_valid(kind)) { + dm_log(ERROR, "%d" _(" is not valid dm_resource_kind_t"), (int) kind); + ctx->result = FLUF_COAP_CODE_INTERNAL_SERVER_ERROR; + return; + } + if (!presence_valid(presence)) { + dm_log(ERROR, "%d" _(" is not valid dm_resource_presence_t"), + (int) presence); + ctx->result = FLUF_COAP_CODE_INTERNAL_SERVER_ERROR; + return; + } + ctx->result = ctx->handler(ctx->dm, ctx->obj, ctx->iid, rid, kind, presence, + ctx->handler_data); + if (ctx->result == DM_FOREACH_BREAK) { + dm_log(TRACE, + _("foreach_resource: break on ") "/%" PRIu16 "/%" PRIu16 + "/%" PRIu16, + _dm_installed_object_oid(ctx->obj), ctx->iid, rid); + } else if (ctx->result) { + dm_log(DEBUG, + _("foreach_resource_handler failed for ") "/%" PRIu16 "/%" PRIu16 + "/%" PRIu16 + _(" (") "%d" _( + ")"), + _dm_installed_object_oid(ctx->obj), ctx->iid, rid, ctx->result); + } +} + +int _dm_foreach_resource(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + dm_foreach_resource_handler_t *handler, + void *data) { + if (!obj) { + dm_log(ERROR, _("attempt to iterate through NULL Object")); + return FLUF_COAP_CODE_INTERNAL_SERVER_ERROR; + } + + dm_resource_list_ctx_t resource_list_ctx = { + .obj = obj, + .iid = iid, + .last_rid = -1, + .handler = handler, + .handler_data = data, + .result = 0 + }; + int result = _dm_call_list_resources(dm, obj, iid, &resource_list_ctx); + if (result < 0) { + dm_log(ERROR, + _("list_resources handler for ") "/%" PRIu16 "/%" PRIu16 _( + " failed (") "%d" _(")"), + _dm_installed_object_oid(obj), iid, result); + return result; + } + return resource_list_ctx.result == DM_FOREACH_BREAK + ? 0 + : resource_list_ctx.result; +} + +typedef struct { + fluf_rid_t rid_to_find; + dm_resource_kind_t kind; + dm_resource_presence_t presence; +} resource_present_args_t; + +static int kind_and_presence_clb(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + dm_resource_kind_t kind, + dm_resource_presence_t presence, + void *args_) { + (void) dm; + (void) obj; + (void) iid; + resource_present_args_t *args = (resource_present_args_t *) args_; + if (rid >= args->rid_to_find) { + if (rid == args->rid_to_find) { + args->kind = kind; + args->presence = presence; + } + return DM_FOREACH_BREAK; + } + return DM_FOREACH_CONTINUE; +} + +int _dm_resource_kind_and_presence(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + dm_resource_kind_t *out_kind, + dm_resource_presence_t *out_presence) { + resource_present_args_t args = { + .rid_to_find = rid, + .kind = (dm_resource_kind_t) -1, + .presence = DM_RES_ABSENT + }; + assert(!_dm_res_kind_valid(args.kind)); + int retval = + _dm_foreach_resource(dm, obj, iid, kind_and_presence_clb, &args); + if (retval) { + return retval; + } + // if resource not exists, _dm_foreach_resource return success but + // kind and presence are not set + if (args.kind == (dm_resource_kind_t) -1 + || args.presence == DM_RES_ABSENT) { + return FLUF_COAP_CODE_NOT_FOUND; + } + if (!_dm_res_kind_valid(args.kind) || !presence_valid(args.presence)) { + return FLUF_COAP_CODE_INTERNAL_SERVER_ERROR; + } + if (out_kind) { + *out_kind = args.kind; + } + if (out_presence) { + *out_presence = args.presence; + } + return 0; +} + +typedef struct { + fluf_riid_t riid_to_find; + bool found; +} resource_instance_exist_args_t; + +static int resource_instance_existence_clb(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + fluf_riid_t riid, + void *args_) { + (void) dm; + (void) obj; + (void) iid; + (void) rid; + resource_instance_exist_args_t *args = + (resource_instance_exist_args_t *) args_; + if (riid == args->riid_to_find) { + args->found = true; + return DM_FOREACH_BREAK; + } + return DM_FOREACH_CONTINUE; +} + +int _dm_resource_instance_existence(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + fluf_riid_t riid) { + resource_instance_exist_args_t riid_args = { + .riid_to_find = riid, + .found = false + }; + int result = _dm_foreach_resource_instance( + dm, obj, iid, rid, resource_instance_existence_clb, &riid_args); + if (result) { + return result; + } + return riid_args.found ? 0 : FLUF_COAP_CODE_METHOD_NOT_ALLOWED; +} + +typedef struct { + dm_list_ctx_emit_t *emit; + dm_t *dm; + const dm_installed_object_t *obj; + fluf_iid_t iid; + fluf_rid_t rid; + int32_t last_riid; + dm_foreach_resource_instance_handler_t *handler; + void *handler_data; + int result; +} dm_foreach_resource_instance_ctx_t; + +static void foreach_resource_instance_emit(dm_list_ctx_t *ctx_, uint16_t riid) { + dm_foreach_resource_instance_ctx_t *ctx = + (dm_foreach_resource_instance_ctx_t *) ctx_; + if (ctx->result) { + return; + } + if (riid == FLUF_ID_INVALID) { + dm_log(ERROR, "%" PRIu16 _(" is not a valid Resource Instance ID"), + riid); + ctx->result = FLUF_COAP_CODE_INTERNAL_SERVER_ERROR; + return; + } + if (riid <= ctx->last_riid) { + dm_log(ERROR, + _("list_resource_instances MUST return Resource Instance " + "IDs in strictly ascending order; ") "%" PRIu16 + _(" returned after ") "%" PRId32, + riid, ctx->last_riid); + ctx->result = FLUF_COAP_CODE_INTERNAL_SERVER_ERROR; + return; + } + ctx->last_riid = riid; + ctx->result = ctx->handler(ctx->dm, ctx->obj, ctx->iid, ctx->rid, riid, + ctx->handler_data); + if (ctx->result == DM_FOREACH_BREAK) { + dm_log(TRACE, + _("foreach_resource_instance: break on ") "/%" PRIu16 "/%" PRIu16 + "/%" PRIu16 + "/%" PRIu16, + _dm_installed_object_oid(ctx->obj), ctx->iid, ctx->rid, riid); + } else if (ctx->result) { + dm_log(DEBUG, + _("foreach_resource_handler failed for ") "/%" PRIu16 "/%" PRIu16 + "/%" PRIu16 "/%" PRIu16 + _(" (") "%d" _( + ")"), + _dm_installed_object_oid(ctx->obj), ctx->iid, ctx->rid, riid, + ctx->result); + } +} + +int _dm_foreach_resource_instance( + dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + dm_foreach_resource_instance_handler_t *handler, + void *data) { + if (!obj) { + dm_log(ERROR, _("attempt to iterate through NULL Object")); + return FLUF_COAP_CODE_INTERNAL_SERVER_ERROR; + } + + dm_foreach_resource_instance_ctx_t ctx = { + .emit = foreach_resource_instance_emit, + .obj = obj, + .iid = iid, + .rid = rid, + .last_riid = -1, + .handler = handler, + .handler_data = data, + .result = 0 + }; + int result = _dm_call_list_resource_instances(dm, obj, iid, rid, + (dm_list_ctx_t *) &ctx); + if (result < 0) { + dm_log(ERROR, + _("list_resource_instances handler for ") "/%" PRIu16 "/%" PRIu16 + "/%" PRIu16 _( + " faile" + "d (") "%d" _(")"), + _dm_installed_object_oid(obj), iid, rid, result); + return result; + } + return ctx.result == DM_FOREACH_BREAK ? 0 : ctx.result; +} + +int _dm_find_object(dm_t *dm, + const fluf_uri_path_t *uri, + const dm_installed_object_t **out_obj_ptr) { + if (!fluf_uri_path_has(uri, FLUF_ID_OID)) { + dm_log(DEBUG, _("Provided URI does not contain Object ID")); + return FLUF_COAP_CODE_METHOD_NOT_ALLOWED; + } + if (!(*out_obj_ptr = _dm_find_object_by_oid(dm, uri->ids[FLUF_ID_OID]))) { + dm_log(DEBUG, _("Object not found: ") "%" PRIu16, + uri->ids[FLUF_ID_OID]); + return FLUF_COAP_CODE_NOT_FOUND; + } + return 0; +} + +static int +foreach_resource_instance_res_count_clb(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + fluf_riid_t riid, + void *count_) { + (void) dm; + (void) obj; + (void) iid; + (void) rid; + (void) riid; + size_t *count = (size_t *) count_; + (*count)++; + return 0; +} + +static int resource_count(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + fluf_riid_t riid, + size_t *out_count, + dm_resource_kind_t kind, + dm_resource_presence_t presence) { + if (presence == DM_RES_ABSENT || !_dm_res_kind_readable(kind)) { + // just skip, no error + return 0; + } + if (_dm_res_kind_multiple(kind)) { + if (riid != FLUF_ID_INVALID) { + int result = + _dm_resource_instance_existence(dm, obj, iid, rid, riid); + if (result) { + return result; + } + *out_count += 1; + return 0; + } + return _dm_foreach_resource_instance( + dm, obj, iid, rid, foreach_resource_instance_res_count_clb, + out_count); + } + *out_count += 1; + return 0; +} + +static int foreach_resource_res_count_clb(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + dm_resource_kind_t kind, + dm_resource_presence_t presence, + void *args) { + return resource_count(dm, obj, iid, rid, FLUF_ID_INVALID, (size_t *) args, + kind, presence); +} + +static int foreach_instance_res_count_clb(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + void *args) { + (void) iid; + return _dm_foreach_resource(dm, obj, _dm_installed_object_oid(obj), + foreach_resource_res_count_clb, args); +} + +static int foreach_object_res_count_clb(dm_t *dm, + const dm_installed_object_t *obj, + void *args) { + return _dm_foreach_instance(dm, obj, foreach_instance_res_count_clb, args); +} + +int dm_get_readable_res_count(dm_t *dm, + fluf_uri_path_t *uri, + size_t *out_count) { + if (fluf_uri_path_length(uri) == 0) { + return _dm_foreach_object(dm, foreach_object_res_count_clb, out_count); + } + + const dm_installed_object_t *obj; + int result = _dm_find_object(dm, uri, &obj); + if (result) { + return result; + } + + if (fluf_uri_path_is(uri, FLUF_ID_OID)) { + return _dm_foreach_instance(dm, obj, foreach_instance_res_count_clb, + out_count); + } + if (fluf_uri_path_is(uri, FLUF_ID_IID)) { + return _dm_foreach_resource(dm, obj, uri->ids[FLUF_ID_IID], + foreach_resource_res_count_clb, out_count); + } + dm_resource_kind_t kind; + dm_resource_presence_t presence; + if ((result = _dm_resource_kind_and_presence(dm, obj, uri->ids[FLUF_ID_IID], + uri->ids[FLUF_ID_RID], &kind, + &presence))) { + return result; + } + if (fluf_uri_path_is(uri, FLUF_ID_RID)) { + return resource_count(dm, obj, uri->ids[FLUF_ID_IID], + uri->ids[FLUF_ID_RID], FLUF_ID_INVALID, out_count, + kind, presence); + } + return resource_count(dm, obj, uri->ids[FLUF_ID_IID], uri->ids[FLUF_ID_RID], + uri->ids[FLUF_ID_RIID], out_count, kind, presence); +} + +static int foreach_instance_register_clb(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + void *reg_ctx_) { + (void) dm; + dm_register_ctx_t *reg_ctx = (dm_register_ctx_t *) reg_ctx_; + int res; + if ((res = reg_ctx->callback( + reg_ctx->arg, &FLUF_MAKE_INSTANCE_PATH( + _dm_installed_object_oid(obj), iid)))) { + return res; + } + return 0; +} + +static int foreach_object_register_clb(dm_t *dm, + const dm_installed_object_t *obj, + void *reg_ctx_) { + dm_register_ctx_t *reg_ctx = (dm_register_ctx_t *) reg_ctx_; + int res; + if ((res = reg_ctx->callback(reg_ctx->arg, + &FLUF_MAKE_OBJECT_PATH( + _dm_installed_object_oid(obj))))) { + return res; + } + return _dm_foreach_instance(dm, obj, foreach_instance_register_clb, + reg_ctx); +} + +int dm_register_prepare(dm_t *dm, dm_register_ctx_t *ctx) { + AVS_ASSERT(dm, "dm is NULL"); + AVS_ASSERT(ctx, "ctx is NULL"); + AVS_ASSERT(ctx->callback, "ctx->callback is NULL"); + return _dm_foreach_object(dm, foreach_object_register_clb, ctx); +} + +typedef struct { + dm_discover_ctx_t *ctx; + fluf_id_type_t discover_to; +} dm_discover_internal_ctx_t; + +static int +foreach_resource_instance_discover_clb(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + fluf_riid_t riid, + void *disc_int_ctx_) { + (void) dm; + dm_discover_internal_ctx_t *disc_int_ctx = + (dm_discover_internal_ctx_t *) disc_int_ctx_; + int res; + if ((res = disc_int_ctx->ctx->callback( + disc_int_ctx->ctx->arg, + &FLUF_MAKE_RESOURCE_INSTANCE_PATH( + _dm_installed_object_oid(obj), iid, rid, riid)))) { + return res; + } + return 0; +} + +static int resource_discover(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + dm_discover_internal_ctx_t *disc_int_ctx, + dm_resource_kind_t kind, + dm_resource_presence_t presence) { + int res; + if (presence == DM_RES_ABSENT) { + return 0; + } + if ((res = disc_int_ctx->ctx->callback( + disc_int_ctx->ctx->arg, + &FLUF_MAKE_RESOURCE_PATH(_dm_installed_object_oid(obj), iid, + rid)))) { + return res; + } + if (disc_int_ctx->discover_to == FLUF_ID_RID) { + return 0; + } + if (_dm_res_kind_multiple(kind)) { + return _dm_foreach_resource_instance( + dm, obj, iid, rid, foreach_resource_instance_discover_clb, + disc_int_ctx); + } + return 0; +} + +static int foreach_resource_discover_clb(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + dm_resource_kind_t kind, + dm_resource_presence_t presence, + void *disc_int_ctx_) { + dm_discover_internal_ctx_t *disc_int_ctx = + (dm_discover_internal_ctx_t *) disc_int_ctx_; + return resource_discover(dm, obj, iid, rid, disc_int_ctx, kind, presence); +} + +static int instance_discover(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + dm_discover_internal_ctx_t *disc_int_ctx) { + int res; + if ((res = _dm_verify_instance_present(dm, obj, iid))) { + return res; + } + if ((res = disc_int_ctx->ctx->callback( + disc_int_ctx->ctx->arg, + &FLUF_MAKE_INSTANCE_PATH(_dm_installed_object_oid(obj), + iid)))) { + return res; + } + if (disc_int_ctx->discover_to == FLUF_ID_IID) { + return 0; + } + return _dm_foreach_resource(dm, obj, iid, foreach_resource_discover_clb, + disc_int_ctx); +} + +static int foreach_instance_discover_clb(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + void *disc_int_ctx_) { + dm_discover_internal_ctx_t *disc_int_ctx = + (dm_discover_internal_ctx_t *) disc_int_ctx_; + return instance_discover(dm, obj, iid, disc_int_ctx); +} + +static fluf_id_type_t infer_depth(fluf_uri_path_t *uri, const uint8_t *depth) { + uint8_t actual_depth; + if (depth == NULL) { + if (fluf_uri_path_is(uri, FLUF_ID_OID)) { + actual_depth = 2; + } else { + actual_depth = 1; + } + } else { + actual_depth = *depth; + } + AVS_ASSERT(fluf_uri_path_length(uri) != 0, + "depth with root is not allowed by specification of discover " + "operation"); + fluf_id_type_t type = (fluf_id_type_t) (fluf_uri_path_length(uri) - 1); + return type + actual_depth > FLUF_ID_RIID + ? FLUF_ID_RIID + : (fluf_id_type_t) (type + actual_depth); +} + +int dm_discover_resp_prepare(dm_t *dm, + fluf_uri_path_t *uri, + const uint8_t *depth, + dm_discover_ctx_t *ctx) { + AVS_ASSERT(dm, "dm is NULL"); + AVS_ASSERT(uri, "uri is NULL"); + AVS_ASSERT(ctx, "ctx is NULL"); + AVS_ASSERT(ctx->callback, "ctx->callback is NULL"); + AVS_ASSERT(fluf_uri_path_length(uri) != 0, "uri can't point to root"); + AVS_ASSERT(!fluf_uri_path_is(uri, FLUF_ID_RIID), + "uri can't point to Resource Instance"); + if (depth) { + AVS_ASSERT(*depth <= 3, "depth can't be greater than 3"); + } + + const dm_installed_object_t *obj; + int res = _dm_find_object(dm, uri, &obj); + if (res) { + return res; + } + + dm_discover_internal_ctx_t disc_int_ctx = { + .ctx = ctx, + .discover_to = infer_depth(uri, depth) + }; + + if (fluf_uri_path_is(uri, FLUF_ID_OID)) { + if ((res = ctx->callback(ctx->arg, + &FLUF_MAKE_OBJECT_PATH( + _dm_installed_object_oid(obj))))) { + return res; + } + if (disc_int_ctx.discover_to == FLUF_ID_OID) { + return 0; + } + return _dm_foreach_instance(dm, obj, foreach_instance_discover_clb, + &disc_int_ctx); + } + if (fluf_uri_path_is(uri, FLUF_ID_IID)) { + return instance_discover(dm, obj, uri->ids[FLUF_ID_IID], &disc_int_ctx); + } + dm_resource_kind_t kind; + dm_resource_presence_t presence; + if ((res = _dm_resource_kind_and_presence(dm, obj, uri->ids[FLUF_ID_IID], + uri->ids[FLUF_ID_RID], &kind, + &presence))) { + return res; + } + return resource_discover(dm, obj, uri->ids[FLUF_ID_IID], + uri->ids[FLUF_ID_RID], &disc_int_ctx, kind, + presence); +} + +const char *_dm_debug_make_path__(char *buffer, + size_t buffer_size, + const fluf_uri_path_t *uri) { + assert(uri); + int result = 0; + char *ptr = buffer; + char *buffer_end = buffer + buffer_size; + size_t length = fluf_uri_path_length(uri); + if (!length) { + result = avs_simple_snprintf(buffer, buffer_size, "/"); + } else { + for (size_t i = 0; result >= 0 && i < length; ++i) { + result = avs_simple_snprintf(ptr, (size_t) (buffer_end - ptr), + "/%" PRIu16, (unsigned) uri->ids[i]); + ptr += result; + } + } + if (result < 0) { + AVS_UNREACHABLE("should never happen"); + return ""; + } + return buffer; +} + +#ifdef UNIT_TESTING +# include +#endif diff --git a/src/anj/dm_core.h b/src/anj/dm_core.h new file mode 100644 index 00000000..53b59013 --- /dev/null +++ b/src/anj/dm_core.h @@ -0,0 +1,33 @@ +/* + * Copyright 2023-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_DM_DM_CORE_H +#define ANJAY_DM_DM_CORE_H + +#include + +#include +#include + +#include "dm_utils/dm_utils.h" +#include "dm_utils/dm_utils_core.h" + +typedef void dm_list_ctx_emit_t(dm_list_ctx_t *, uint16_t id); + +static inline int _dm_map_present_result(int result) { + if (!result) { + return FLUF_COAP_CODE_NOT_FOUND; + } else if (result > 0) { + return 0; + } else { + return result; + } +} + +#endif // ANJAY_DM_DM_CORE_H diff --git a/src/anj/dm_io_core.c b/src/anj/dm_io_core.c new file mode 100644 index 00000000..dac7ebaf --- /dev/null +++ b/src/anj/dm_io_core.c @@ -0,0 +1,417 @@ +/* + * Copyright 2023-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 "dm_core.h" +#include "dm_utils/dm_utils.h" + +struct dm_list_ctx_struct { + dm_list_ctx_emit_t *emit; +}; + +void dm_emit(dm_list_ctx_t *ctx, uint16_t id) { + assert(ctx && ctx->emit); + ctx->emit(ctx, id); +} + +int dm_ret_bytes(dm_output_ctx_t *ctx, void *data, size_t data_len) { + dm_output_internal_ctx_t *internal_out_ctx = + (dm_output_internal_ctx_t *) ctx; + fluf_io_out_entry_t entry = { + .path = internal_out_ctx->path, + .type = FLUF_DATA_TYPE_BYTES, + .value.bytes_or_string.data = data, + .value.bytes_or_string.chunk_length = data_len, + .value.bytes_or_string.full_length_hint = data_len + }; + assert(internal_out_ctx->output_ctx->callback); + return internal_out_ctx->output_ctx->callback( + internal_out_ctx->output_ctx->arg, &entry); +} + +int dm_ret_string(dm_output_ctx_t *ctx, char *value) { + dm_output_internal_ctx_t *internal_out_ctx = + (dm_output_internal_ctx_t *) ctx; + fluf_io_out_entry_t entry = { + .path = internal_out_ctx->path, + .type = FLUF_DATA_TYPE_STRING, + .value.bytes_or_string.data = value + }; + entry.value.bytes_or_string.chunk_length = strlen(value); + entry.value.bytes_or_string.full_length_hint = + entry.value.bytes_or_string.chunk_length; + assert(internal_out_ctx->output_ctx->callback); + return internal_out_ctx->output_ctx->callback( + internal_out_ctx->output_ctx->arg, &entry); +} + +int dm_ret_external_bytes(dm_output_ctx_t *ctx, + fluf_get_external_data_t *get_external_data, + void *user_args, + size_t length) { + dm_output_internal_ctx_t *internal_out_ctx = + (dm_output_internal_ctx_t *) ctx; + fluf_io_out_entry_t entry = { + .path = internal_out_ctx->path, + .type = FLUF_DATA_TYPE_EXTERNAL_BYTES, + .value.external_data.get_external_data = get_external_data, + .value.external_data.user_args = user_args, + .value.external_data.length = length + }; + assert(internal_out_ctx->output_ctx->callback); + return internal_out_ctx->output_ctx->callback( + internal_out_ctx->output_ctx->arg, &entry); +} + +int dm_ret_external_string(dm_output_ctx_t *ctx, + fluf_get_external_data_t *get_external_data, + void *user_args, + size_t length) { + dm_output_internal_ctx_t *internal_out_ctx = + (dm_output_internal_ctx_t *) ctx; + fluf_io_out_entry_t entry = { + .path = internal_out_ctx->path, + .type = FLUF_DATA_TYPE_EXTERNAL_STRING, + .value.external_data.get_external_data = get_external_data, + .value.external_data.user_args = user_args, + .value.external_data.length = length + }; + assert(internal_out_ctx->output_ctx->callback); + return internal_out_ctx->output_ctx->callback( + internal_out_ctx->output_ctx->arg, &entry); +} + +int dm_ret_i64(dm_output_ctx_t *ctx, int64_t value) { + dm_output_internal_ctx_t *internal_out_ctx = + (dm_output_internal_ctx_t *) ctx; + fluf_io_out_entry_t entry = { + .path = internal_out_ctx->path, + .type = FLUF_DATA_TYPE_INT, + .value.int_value = value + }; + assert(internal_out_ctx->output_ctx->callback); + return internal_out_ctx->output_ctx->callback( + internal_out_ctx->output_ctx->arg, &entry); +} + +int dm_ret_u64(dm_output_ctx_t *ctx, uint64_t value) { + dm_output_internal_ctx_t *internal_out_ctx = + (dm_output_internal_ctx_t *) ctx; + fluf_io_out_entry_t entry = { + .path = internal_out_ctx->path, + .type = FLUF_DATA_TYPE_UINT, + .value.uint_value = value + }; + assert(internal_out_ctx->output_ctx->callback); + return internal_out_ctx->output_ctx->callback( + internal_out_ctx->output_ctx->arg, &entry); +} + +int dm_ret_double(dm_output_ctx_t *ctx, double value) { + dm_output_internal_ctx_t *internal_out_ctx = + (dm_output_internal_ctx_t *) ctx; + fluf_io_out_entry_t entry = { + .path = internal_out_ctx->path, + .type = FLUF_DATA_TYPE_DOUBLE, + .value.double_value = value + }; + assert(internal_out_ctx->output_ctx->callback); + return internal_out_ctx->output_ctx->callback( + internal_out_ctx->output_ctx->arg, &entry); +} + +int dm_ret_bool(dm_output_ctx_t *ctx, bool value) { + dm_output_internal_ctx_t *internal_out_ctx = + (dm_output_internal_ctx_t *) ctx; + fluf_io_out_entry_t entry = { + .path = internal_out_ctx->path, + .type = FLUF_DATA_TYPE_BOOL, + .value.bool_value = value + }; + assert(internal_out_ctx->output_ctx->callback); + return internal_out_ctx->output_ctx->callback( + internal_out_ctx->output_ctx->arg, &entry); +} + +int dm_ret_objlnk(dm_output_ctx_t *ctx, fluf_oid_t oid, fluf_iid_t iid) { + dm_output_internal_ctx_t *internal_out_ctx = + (dm_output_internal_ctx_t *) ctx; + fluf_io_out_entry_t entry = { + .path = internal_out_ctx->path, + .type = FLUF_DATA_TYPE_OBJLNK, + .value.objlnk.oid = oid, + .value.objlnk.iid = iid + }; + assert(internal_out_ctx->output_ctx->callback); + return internal_out_ctx->output_ctx->callback( + internal_out_ctx->output_ctx->arg, &entry); +} + +int dm_ret_time(dm_output_ctx_t *ctx, uint64_t time) { + dm_output_internal_ctx_t *internal_out_ctx = + (dm_output_internal_ctx_t *) ctx; + fluf_io_out_entry_t entry = { + .path = internal_out_ctx->path, + .type = FLUF_DATA_TYPE_TIME, + .value.uint_value = time + }; + assert(internal_out_ctx->output_ctx->callback); + return internal_out_ctx->output_ctx->callback( + internal_out_ctx->output_ctx->arg, &entry); +} + +static int call_to_user_callback(dm_input_internal_ctx_t *internal_in_ctx, + fluf_data_type_t type) { + int result = 0; + if (!internal_in_ctx->callback_called_flag) { + assert(internal_in_ctx->input_ctx->callback); + if ((result = internal_in_ctx->input_ctx->callback( + internal_in_ctx->input_ctx->arg, + type, + internal_in_ctx->provided_entry))) { + return result; + } + internal_in_ctx->callback_called_flag = true; + if (internal_in_ctx->provided_entry->type != type) { + return FLUF_COAP_CODE_BAD_REQUEST; + } + } + return result; +} + +static int get_bytes_impl(dm_input_internal_ctx_t *internal_in_ctx, + size_t *out_bytes_read, + bool *out_message_finished, + void *out_buf, + size_t buf_size) { + if (buf_size > (internal_in_ctx->provided_entry->value.bytes_or_string + .chunk_length + - internal_in_ctx->buff_indicator)) { + // TODO: Only single chunk values are supported for now + if (internal_in_ctx->provided_entry->value.bytes_or_string.offset + + internal_in_ctx->provided_entry->value.bytes_or_string + .chunk_length + != internal_in_ctx->provided_entry->value.bytes_or_string + .full_length_hint) { + return -1; + } + memcpy(out_buf, + (const char *) internal_in_ctx->provided_entry->value + .bytes_or_string.data + + internal_in_ctx->buff_indicator, + internal_in_ctx->provided_entry->value.bytes_or_string + .chunk_length + - internal_in_ctx->buff_indicator); + if (out_bytes_read) { + *out_bytes_read = internal_in_ctx->provided_entry->value + .bytes_or_string.chunk_length + - internal_in_ctx->buff_indicator; + } + if (out_message_finished) { + *out_message_finished = true; + } + } else { + size_t to_write = AVS_MIN(buf_size, + internal_in_ctx->provided_entry->value + .bytes_or_string.chunk_length + - internal_in_ctx->buff_indicator); + memcpy(out_buf, + (const char *) internal_in_ctx->provided_entry->value + .bytes_or_string.data + + internal_in_ctx->buff_indicator, + to_write); + internal_in_ctx->buff_indicator += to_write; + if (out_bytes_read) { + *out_bytes_read = to_write; + } + if (out_message_finished) { + *out_message_finished = false; + } + } + return 0; +} + +int dm_get_bytes(dm_input_ctx_t *ctx, + size_t *out_bytes_read, + bool *out_message_finished, + void *out_buf, + size_t buf_size) { + if (buf_size == 0) { + return -1; + } + + dm_input_internal_ctx_t *internal_in_ctx = (dm_input_internal_ctx_t *) ctx; + + int result = call_to_user_callback(internal_in_ctx, FLUF_DATA_TYPE_BYTES); + if (result) { + return result; + } + + return get_bytes_impl(internal_in_ctx, + out_bytes_read, + out_message_finished, + out_buf, + buf_size); +} + +int dm_get_string(dm_input_ctx_t *ctx, char *out_buf, size_t buf_size) { + if (buf_size == 0) { + // At least terminating nullbyte must fit into the buffer! + return DM_BUFFER_TOO_SHORT; + } + + dm_input_internal_ctx_t *internal_in_ctx = (dm_input_internal_ctx_t *) ctx; + + int result = call_to_user_callback(internal_in_ctx, FLUF_DATA_TYPE_STRING); + if (result) { + return result; + } + + size_t bytes_read; + bool message_finished; + if ((result = get_bytes_impl(internal_in_ctx, + &bytes_read, + &message_finished, + out_buf, + buf_size - 1))) { + return result; + } + assert(bytes_read < buf_size); + out_buf[bytes_read] = '\0'; + return message_finished ? 0 : DM_BUFFER_TOO_SHORT; +} + +int dm_get_external_bytes(dm_input_ctx_t *ctx, + fluf_get_external_data_t **out_get_external_data, + void **out_user_args, + size_t *out_length) { + dm_input_internal_ctx_t *internal_in_ctx = (dm_input_internal_ctx_t *) ctx; + + int result = call_to_user_callback(internal_in_ctx, + FLUF_DATA_TYPE_EXTERNAL_BYTES); + if (result) { + return result; + } + + *out_get_external_data = internal_in_ctx->provided_entry->value + .external_data.get_external_data; + *out_user_args = + internal_in_ctx->provided_entry->value.external_data.user_args; + *out_length = internal_in_ctx->provided_entry->value.external_data.length; + return 0; +} + +int dm_get_external_string(dm_input_ctx_t *ctx, + fluf_get_external_data_t **out_get_external_data, + void **out_user_args, + size_t *out_length) { + dm_input_internal_ctx_t *internal_in_ctx = (dm_input_internal_ctx_t *) ctx; + + int result = call_to_user_callback(internal_in_ctx, + FLUF_DATA_TYPE_EXTERNAL_BYTES); + if (result) { + return result; + } + + *out_get_external_data = internal_in_ctx->provided_entry->value + .external_data.get_external_data; + *out_user_args = + internal_in_ctx->provided_entry->value.external_data.user_args; + *out_length = internal_in_ctx->provided_entry->value.external_data.length; + return 0; +} + +int dm_get_i64(dm_input_ctx_t *ctx, int64_t *out) { + dm_input_internal_ctx_t *internal_in_ctx = (dm_input_internal_ctx_t *) ctx; + + int result = call_to_user_callback(internal_in_ctx, FLUF_DATA_TYPE_INT); + if (result) { + return result; + } + + *out = internal_in_ctx->provided_entry->value.int_value; + return 0; +} + +int dm_get_u64(dm_input_ctx_t *ctx, uint64_t *out) { + dm_input_internal_ctx_t *internal_in_ctx = (dm_input_internal_ctx_t *) ctx; + + int result = call_to_user_callback(internal_in_ctx, FLUF_DATA_TYPE_UINT); + if (result) { + return result; + } + + *out = internal_in_ctx->provided_entry->value.uint_value; + return 0; +} + +int dm_get_u32(dm_input_ctx_t *ctx, uint32_t *out) { + uint64_t tmp; + int result = dm_get_u64(ctx, &tmp); + if (!result) { + if (tmp > UINT32_MAX) { + result = FLUF_COAP_CODE_BAD_REQUEST; + } else { + *out = (uint32_t) tmp; + } + } + return result; +} + +int dm_get_double(dm_input_ctx_t *ctx, double *out) { + dm_input_internal_ctx_t *internal_in_ctx = (dm_input_internal_ctx_t *) ctx; + + int result = call_to_user_callback(internal_in_ctx, FLUF_DATA_TYPE_DOUBLE); + if (result) { + return result; + } + + *out = internal_in_ctx->provided_entry->value.double_value; + return 0; +} + +int dm_get_bool(dm_input_ctx_t *ctx, bool *out) { + dm_input_internal_ctx_t *internal_in_ctx = (dm_input_internal_ctx_t *) ctx; + + int result = call_to_user_callback(internal_in_ctx, FLUF_DATA_TYPE_BOOL); + if (result) { + return result; + } + + *out = internal_in_ctx->provided_entry->value.bool_value; + return 0; +} + +int dm_get_objlnk(dm_input_ctx_t *ctx, + fluf_oid_t *out_oid, + fluf_iid_t *out_iid) { + dm_input_internal_ctx_t *internal_in_ctx = (dm_input_internal_ctx_t *) ctx; + + int result = call_to_user_callback(internal_in_ctx, FLUF_DATA_TYPE_OBJLNK); + if (result) { + return result; + } + + *out_oid = internal_in_ctx->provided_entry->value.objlnk.oid; + *out_iid = internal_in_ctx->provided_entry->value.objlnk.iid; + return 0; +} + +int dm_get_time(dm_input_ctx_t *ctx, int64_t *time) { + dm_input_internal_ctx_t *internal_in_ctx = (dm_input_internal_ctx_t *) ctx; + + int result = call_to_user_callback(internal_in_ctx, FLUF_DATA_TYPE_TIME); + if (result) { + return result; + } + + *time = internal_in_ctx->provided_entry->value.time_value; + return 0; +} diff --git a/src/anj/dm_utils/dm_utils.h b/src/anj/dm_utils/dm_utils.h new file mode 100644 index 00000000..b4fcadeb --- /dev/null +++ b/src/anj/dm_utils/dm_utils.h @@ -0,0 +1,226 @@ +/* + * Copyright 2023-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_DM_DM_UTILS_H +#define ANJAY_DM_DM_UTILS_H + +#include +#include + +#include + +#include + +#include "dm_utils_core.h" + +typedef struct { + dm_output_ctx_t *output_ctx; + fluf_uri_path_t path; +} dm_output_internal_ctx_t; + +typedef struct { + dm_input_ctx_t *input_ctx; + bool callback_called_flag; + fluf_io_out_entry_t *provided_entry; + size_t buff_indicator; +} dm_input_internal_ctx_t; + +const char *_dm_debug_make_path__(char *buffer, + size_t buffer_size, + const fluf_uri_path_t *uri); + +#define DM_DEBUG_MAKE_PATH(path) \ + (_dm_debug_make_path__(&(char[32]){ 0 }[0], 32, (path))) + +typedef int dm_foreach_object_handler_t(dm_t *dm, + const dm_installed_object_t *obj, + void *data); + +int _dm_foreach_object(dm_t *dm, + dm_foreach_object_handler_t *handler, + void *data); + +typedef int dm_foreach_instance_handler_t(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + void *data); + +int _dm_foreach_instance(dm_t *dm, + const dm_installed_object_t *obj, + dm_foreach_instance_handler_t *handler, + void *data); + +int _dm_instance_present(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid); + +typedef int dm_foreach_resource_handler_t(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + dm_resource_kind_t kind, + dm_resource_presence_t presence, + void *data); + +int _dm_foreach_resource(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + dm_foreach_resource_handler_t *handler, + void *data); + +/** + * Checks if the specific resource is supported and present, and what is its + * kind. This function internally calls @ref _dm_foreach_resource, so it + * is not optimal to use for multiple resources within the same Object Instance. + * + * NOTE: It is REQUIRED that the presence of the Object and Object Instance is + * checked beforehand, this function does not perform such checks. + * + * @param dm Data model object to operate on + * @param obj Definition of an Object in which the queried resource is + * @param iid ID of Object Instance in which the queried resource is + * @param rid ID of the queried Resource + * @param out_kind Pointer to a variable in which the kind of the resource + * will be stored. May be NULL, in which case only the + * presence is checked. + * @param out_presence Pointer to a variable in which the presence information + * about the resource will be stored. May be NULL, in which + * case only the kind is checked. + * + * @returns 0 for success, or a non-zero error code in case of error. + * + * NOTE: Two scenarios are possible if the resource is not currently present in + * the object: + * - If the resource is not Supported (i.e., it has not been enumerated by the + * list_resources handler at all), the function fails, returning + * FLUF_COAP_CODE_NOT_FOUND + * - If the resource is Supported, but not Present (i.e., it has been enumerated + * by the list_resources handler with presence set to DM_RES_ABSENT), + * the function succeeds (returns 0), but *out_presence is set to + * DM_RES_ABSENT + */ +int _dm_resource_kind_and_presence(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + dm_resource_kind_t *out_kind, + dm_resource_presence_t *out_presence); + +int _dm_resource_instance_existence(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + fluf_riid_t riid); + +typedef int +dm_foreach_resource_instance_handler_t(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + fluf_riid_t riid, + void *data); + +int _dm_foreach_resource_instance( + dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + dm_foreach_resource_instance_handler_t *handler, + void *data); + +static inline bool _dm_res_kind_valid(dm_resource_kind_t kind) { + return kind == DM_RES_R || kind == DM_RES_W || kind == DM_RES_RW + || kind == DM_RES_RM || kind == DM_RES_WM || kind == DM_RES_RWM + || kind == DM_RES_E; +} + +static inline bool _dm_res_kind_readable(dm_resource_kind_t kind) { + return kind == DM_RES_R || kind == DM_RES_RW || kind == DM_RES_RM + || kind == DM_RES_RWM; +} + +static inline bool _dm_res_kind_writable(dm_resource_kind_t kind) { + return kind == DM_RES_W || kind == DM_RES_RW || kind == DM_RES_WM + || kind == DM_RES_RWM; +} + +static inline bool _dm_res_kind_executable(dm_resource_kind_t kind) { + return kind == DM_RES_E; +} + +static inline bool _dm_res_kind_multiple(dm_resource_kind_t kind) { + return kind == DM_RES_RM || kind == DM_RES_WM || kind == DM_RES_RWM; +} + +int _dm_call_list_instances(dm_t *dm, + const dm_installed_object_t *obj, + dm_list_ctx_t *instance_list_ctx); +int _dm_call_list_resources(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + dm_resource_list_ctx_t *resource_list_ctx); + +int _dm_call_resource_read(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + fluf_riid_t riid, + dm_output_internal_ctx_t *internal_out_ctx); +int _dm_call_resource_write(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + fluf_riid_t riid, + dm_input_internal_ctx_t *internal_in_ctx); +int _dm_call_resource_execute(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid); +int _dm_call_list_resource_instances(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + dm_list_ctx_t *list_ctx); + +dm_installed_object_t *_dm_find_object_by_oid(dm_t *dm, fluf_oid_t oid); +int _dm_verify_resource_present(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + dm_resource_kind_t *out_kind); + +int _dm_verify_resource_instance_present(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid, + fluf_rid_t rid, + fluf_riid_t riid); + +int _dm_verify_instance_present(dm_t *dm, + const dm_installed_object_t *obj, + fluf_iid_t iid); + +static inline fluf_oid_t +_dm_installed_object_oid(const dm_installed_object_t *obj) { + assert(obj); + assert((*obj).def); + return (*obj->def)->oid; +} + +static inline const char * +_dm_installed_object_version(const dm_installed_object_t *obj) { + assert(obj); + assert((*obj).def); + return (*obj->def)->version; +} + +int _dm_find_object(dm_t *dm, + const fluf_uri_path_t *uri, + const dm_installed_object_t **out_obj_ptr); + +#endif /* ANJAY_DM_DM_UTILS_H */ diff --git a/src/anj/dm_utils/dm_utils_core.h b/src/anj/dm_utils/dm_utils_core.h new file mode 100644 index 00000000..3d5026e6 --- /dev/null +++ b/src/anj/dm_utils/dm_utils_core.h @@ -0,0 +1,57 @@ +/* + * Copyright 2023-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_DM_UTILS_CORE_H +#define ANJAY_DM_UTILS_CORE_H + +#include + +#ifdef AVS_COMMONS_WITH_AVS_LOG +# include +# define _(Arg) AVS_DISPOSABLE_LOG(Arg) +#else // AVS_COMMONS_WITH_AVS_LOG +# define _(Arg) Arg +#endif // AVS_COMMONS_WITH_AVS_LOG + +#ifdef DM_WITH_LOGS +# ifndef AVS_COMMONS_WITH_AVS_LOG +# error "DM_WITH_LOGS requires avs_log to be enabled" +# endif +// these macros interfere with avs_log() macro implementation +# ifdef TRACE +# undef TRACE +# endif +# ifdef DEBUG +# undef DEBUG +# endif +# ifdef INFO +# undef INFO +# endif +# ifdef WARNING +# undef WARNING +# endif +# ifdef ERROR +# undef ERROR +# endif +# include +# define dm_log(...) avs_log(dm, __VA_ARGS__) +#else +# include +# define dm_log(Module, ...) ((void) sizeof(printf(__VA_ARGS__))) + +#endif // DM_WITH_LOGS + +#include +#include + +#define DM_FOREACH_BREAK INT_MIN +#define DM_FOREACH_CONTINUE 0 +#define DM_OID_SECURITY 0 + +#endif /* ANJAY_DM_UTILS_CORE_H */ diff --git a/src/anj/sdm/sdm_core.c b/src/anj/sdm/sdm_core.c new file mode 100644 index 00000000..9b936a97 --- /dev/null +++ b/src/anj/sdm/sdm_core.c @@ -0,0 +1,418 @@ +/* + * Copyright 2023-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 + +#include "sdm_core.h" + +static int finish_ongoing_operation(sdm_data_model_t *dm) { + sdm_op_result_t op_result; + if (!dm->is_transactional) { + op_result = SDM_OP_RESULT_SUCCESS_NOT_MODIFIED; + } else { + op_result = SDM_OP_RESULT_SUCCESS_MODIFIED; + for (uint16_t idx = 0; idx < dm->objs_count && !dm->result; idx++) { + sdm_obj_t *obj = dm->objs[idx]; + if (obj->in_transaction && obj->obj_handlers + && obj->obj_handlers->operation_validate) { + dm->result = obj->obj_handlers->operation_validate(obj); + } + } + } + for (uint16_t idx = 0; idx < dm->objs_count; idx++) { + sdm_obj_t *obj = dm->objs[idx]; + if (obj->in_transaction) { + obj->in_transaction = false; + if (obj->obj_handlers && obj->obj_handlers->operation_end) { + if (dm->result) { + obj->obj_handlers->operation_end(obj, + SDM_OP_RESULT_FAILURE); + } else { + dm->result = + obj->obj_handlers->operation_end(obj, op_result); + } + } + } + } + dm->op_in_progress = false; + return dm->result; +} + +static inline sdm_obj_t *find_obj(sdm_data_model_t *dm, fluf_oid_t oid) { + for (uint16_t idx = 0; idx < dm->objs_count; idx++) { + if (dm->objs[idx]->oid == oid) { + return dm->objs[idx]; + } + } + return NULL; +} + +static inline sdm_obj_inst_t *find_inst(sdm_obj_t *obj, fluf_iid_t iid) { + for (uint16_t idx = 0; idx < obj->inst_count; idx++) { + if (obj->insts[idx]->iid == iid) { + return obj->insts[idx]; + } + } + return NULL; +} + +static inline sdm_res_t *find_res(sdm_obj_inst_t *inst, fluf_rid_t rid) { + for (uint16_t idx = 0; idx < inst->res_count; idx++) { + if (inst->resources[idx].res_spec->rid == rid) { + return &inst->resources[idx]; + } + } + return NULL; +} + +static inline sdm_res_inst_t *find_res_inst(sdm_res_t *res, fluf_riid_t riid) { + for (uint16_t idx = 0; idx < res->value.res_inst.inst_count; idx++) { + if (res->value.res_inst.insts[idx]->riid == riid) { + return res->value.res_inst.insts[idx]; + } + } + return NULL; +} + +static int call_operation_begin(sdm_obj_t *obj, fluf_op_t operation) { + if (!obj->in_transaction) { + obj->in_transaction = true; + if (obj->obj_handlers && obj->obj_handlers->operation_begin) { + return obj->obj_handlers->operation_begin(obj, operation); + } + } + return 0; +} + +int _sdm_get_obj_ptr_call_operation_begin(sdm_data_model_t *dm, + fluf_oid_t oid, + sdm_obj_t **out_obj) { + *out_obj = NULL; + *out_obj = find_obj(dm, oid); + if (!*out_obj) { + sdm_log(ERROR, "Object not found in data model"); + return SDM_ERR_NOT_FOUND; + } + return call_operation_begin(*out_obj, dm->operation); +} + +int _sdm_get_obj_ptrs(sdm_obj_t *obj, + const fluf_uri_path_t *path, + _sdm_entity_ptrs_t *out_ptrs) { + assert(fluf_uri_path_has(path, FLUF_ID_OID)); + assert(obj); + + sdm_obj_inst_t *inst = NULL; + sdm_res_t *res = NULL; + sdm_res_inst_t *res_inst = NULL; + + if (!fluf_uri_path_has(path, FLUF_ID_IID)) { + goto finalize; + } + + inst = find_inst(obj, path->ids[FLUF_ID_IID]); + if (!inst) { + goto not_found; + } + if (!fluf_uri_path_has(path, FLUF_ID_RID)) { + goto finalize; + } + + res = find_res(inst, path->ids[FLUF_ID_RID]); + if (!res) { + goto not_found; + } + if (!fluf_uri_path_has(path, FLUF_ID_RIID)) { + goto finalize; + } + if (!_sdm_is_multi_instance_resource(res->res_spec->operation)) { + sdm_log(ERROR, "Resource is not multi-instance"); + return SDM_ERR_NOT_FOUND; + } + + res_inst = find_res_inst(res, path->ids[FLUF_ID_RIID]); + if (!res_inst) { + goto not_found; + } + +finalize: + out_ptrs->obj = obj; + out_ptrs->inst = inst; + out_ptrs->res = res; + out_ptrs->res_inst = res_inst; + + return 0; + +not_found: + sdm_log(ERROR, "Record not found in data model"); + return SDM_ERR_NOT_FOUND; +} + +int _sdm_get_entity_ptrs(sdm_data_model_t *dm, + const fluf_uri_path_t *path, + _sdm_entity_ptrs_t *out_ptrs) { + assert(fluf_uri_path_has(path, FLUF_ID_OID)); + sdm_obj_t *obj = NULL; + + obj = find_obj(dm, path->ids[FLUF_ID_OID]); + if (!obj) { + sdm_log(ERROR, "Object not found in data model"); + return SDM_ERR_NOT_FOUND; + } + return _sdm_get_obj_ptrs(obj, path, out_ptrs); +} + +int sdm_operation_begin(sdm_data_model_t *dm, + fluf_op_t operation, + bool is_bootstrap_request, + const fluf_uri_path_t *path) { + assert(dm); + if (dm->op_in_progress) { + sdm_log(ERROR, "Operation already underway"); + return SDM_ERR_LOGIC; + } + + dm->operation = operation; + dm->boostrap_operation = is_bootstrap_request; + dm->is_transactional = false; + dm->op_in_progress = true; + dm->result = 0; + + switch (operation) { + case FLUF_OP_DM_READ_COMP: + dm->op_count = 0; + dm->is_transactional = true; + dm->op_ctx.read_ctx.path = FLUF_MAKE_ROOT_PATH(); + return 0; + case FLUF_OP_DM_WRITE_COMP: + sdm_log(ERROR, "Composite operations are not supported yet"); + return SDM_ERR_INPUT_ARG; + case FLUF_OP_REGISTER: + case FLUF_OP_UPDATE: + return _sdm_begin_register_op(dm); + case FLUF_OP_DM_DISCOVER: + if (dm->boostrap_operation) { + return _sdm_begin_bootstrap_discover_op(dm, path); + } else { + return _sdm_begin_discover_op(dm, path); + } + case FLUF_OP_DM_EXECUTE: + return _sdm_begin_execute_op(dm, path); + case FLUF_OP_DM_READ: + return _sdm_begin_read_op(dm, path); + case FLUF_OP_DM_WRITE_REPLACE: + case FLUF_OP_DM_WRITE_PARTIAL_UPDATE: + return _sdm_begin_write_op(dm, path); + case FLUF_OP_DM_CREATE: + return _sdm_begin_create_op(dm, path); + case FLUF_OP_DM_DELETE: + return _sdm_process_delete_op(dm, path); + default: + break; + } + + sdm_log(ERROR, "Incorrect operation type"); + return SDM_ERR_INPUT_ARG; +} + +int sdm_operation_end(sdm_data_model_t *dm) { + assert(dm); + _SDM_ONGOING_OP_ERROR_CHECK(dm); + + if (dm->operation == FLUF_OP_DM_CREATE && !dm->result) { + // HACK: Create operation is ended without any record being added so we + // create an instance without IID specified. + if (!dm->op_ctx.write_ctx.instance_created) { + dm->result = _sdm_create_object_instance(dm, FLUF_ID_INVALID); + } + } + + return finish_ongoing_operation(dm); +} + +void sdm_initialize(sdm_data_model_t *dm, + sdm_obj_t **objs_array, + uint16_t objs_array_size) { + assert(dm && objs_array && objs_array_size); + + memset(dm, 0, sizeof(*dm)); + dm->objs = objs_array; + dm->max_allowed_objs_number = objs_array_size; +} + +#ifndef NDEBUG +static int check_res(sdm_res_t *res) { + if (res->res_spec->operation == SDM_RES_E + && (!res->res_handlers || !res->res_handlers->res_execute)) { + goto res_error; + } + if (res->res_spec->operation != SDM_RES_E + && !(res->res_spec->type == FLUF_DATA_TYPE_BYTES + || res->res_spec->type == FLUF_DATA_TYPE_STRING + || res->res_spec->type == FLUF_DATA_TYPE_INT + || res->res_spec->type == FLUF_DATA_TYPE_DOUBLE + || res->res_spec->type == FLUF_DATA_TYPE_BOOL + || res->res_spec->type == FLUF_DATA_TYPE_OBJLNK + || res->res_spec->type == FLUF_DATA_TYPE_UINT + || res->res_spec->type == FLUF_DATA_TYPE_TIME + || res->res_spec->type == FLUF_DATA_TYPE_EXTERNAL_BYTES + || res->res_spec->type == FLUF_DATA_TYPE_EXTERNAL_STRING)) { + goto res_error; + } + if (_sdm_is_multi_instance_resource(res->res_spec->operation) + && res->value.res_inst.inst_count) { + if (res->value.res_inst.inst_count > res->value.res_inst.max_inst_count + || res->value.res_inst.max_inst_count == UINT16_MAX) { + goto res_error; + } + fluf_rid_t last_riid; + for (uint16_t idx = 0; idx < res->value.res_inst.inst_count; idx++) { + if (!res->value.res_inst.insts[idx] + || res->value.res_inst.insts[idx]->riid == FLUF_ID_INVALID + || (idx != 0 + && res->value.res_inst.insts[idx]->riid <= last_riid)) { + goto res_error; + } + last_riid = res->value.res_inst.insts[idx]->riid; + } + } + return 0; + +res_error: + sdm_log(ERROR, "Incorrectly defined resource %" PRIu16, res->res_spec->rid); + return SDM_ERR_INPUT_ARG; +} + +int _sdm_check_obj(sdm_obj_t *obj) { + if (obj->inst_count == 0) { + return 0; + } + if (!obj->insts) { + goto obj_error; + } + if (obj->max_inst_count < obj->inst_count + || obj->max_inst_count == UINT16_MAX) { + goto obj_error; + } + + fluf_iid_t last_iid; + for (uint16_t idx = 0; idx < obj->inst_count; idx++) { + sdm_obj_inst_t *inst = obj->insts[idx]; + if (!inst || inst->iid == FLUF_ID_INVALID + || (idx != 0 && inst->iid <= last_iid) + || _sdm_check_obj_instance(inst)) { + goto obj_error; + } + last_iid = inst->iid; + } + return 0; + +obj_error: + sdm_log(ERROR, "Incorrectly defined object %" PRIu16, obj->oid); + return SDM_ERR_INPUT_ARG; +} + +int _sdm_check_obj_instance(sdm_obj_inst_t *inst) { + if (inst->res_count && !inst->resources) { + goto instance_error; + } + if (inst->res_count == 0) { + return 0; + } + + fluf_rid_t last_rid; + for (uint16_t res_idx = 0; res_idx < inst->res_count; res_idx++) { + sdm_res_t *res = &inst->resources[res_idx]; + if (!res->res_spec || res->res_spec->rid == FLUF_ID_INVALID + || (res_idx != 0 && res->res_spec->rid <= last_rid) + || check_res(res)) { + goto instance_error; + } + last_rid = res->res_spec->rid; + } + + return 0; + +instance_error: + sdm_log(ERROR, "Incorrectly defined instance %" PRIu16, inst->iid); + return SDM_ERR_INPUT_ARG; +} + +#endif // NDEBUG + +int sdm_add_obj(sdm_data_model_t *dm, sdm_obj_t *obj) { + assert(dm && obj); + assert(!fluf_validate_obj_version(obj->version)); + assert(!_sdm_check_obj(obj)); + + if (dm->op_in_progress) { + return SDM_ERR_LOGIC; + } + if (dm->max_allowed_objs_number == dm->objs_count) { + sdm_log(ERROR, "No space for a new object"); + return SDM_ERR_MEMORY; + } + + uint16_t idx; + for (idx = 0; idx < dm->objs_count; idx++) { + if (dm->objs[idx]->oid > obj->oid) { + break; + } + if (dm->objs[idx]->oid == obj->oid) { + sdm_log(ERROR, "Object %" PRIu16 " exists", obj->oid); + return SDM_ERR_LOGIC; + } + } + + for (uint16_t i = dm->objs_count; i > idx; i--) { + dm->objs[i] = dm->objs[i - 1]; + } + dm->objs[idx] = obj; + dm->objs_count++; + + obj->in_transaction = false; + return 0; +} + +int sdm_remove_obj(sdm_data_model_t *dm, fluf_oid_t oid) { + assert(dm); + if (dm->op_in_progress) { + return SDM_ERR_LOGIC; + } + + uint16_t idx; + bool found = false; + for (idx = 0; idx < dm->objs_count; idx++) { + if (dm->objs[idx]->oid == oid) { + found = true; + break; + } + } + if (!found) { + sdm_log(ERROR, "Object %" PRIu16 " not found", oid); + return SDM_ERR_NOT_FOUND; + } + dm->objs[idx] = NULL; + for (uint16_t i = idx; i < dm->objs_count - 1; i++) { + dm->objs[i] = dm->objs[i + 1]; + } + dm->objs_count--; + return 0; +} diff --git a/src/anj/sdm/sdm_core.h b/src/anj/sdm/sdm_core.h new file mode 100644 index 00000000..d805d42f --- /dev/null +++ b/src/anj/sdm/sdm_core.h @@ -0,0 +1,104 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#ifndef SDM_CORE_H +#define SDM_CORE_H + +#include "sdm_core.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef SDM_WITH_LOGS +# ifndef AVS_COMMONS_WITH_AVS_LOG +# error "SDM_WITH_LOGS requires avs_log to be enabled" +# endif +# include +# define sdm_log(...) avs_log(sdm, __VA_ARGS__) +#else // SDM_WITH_LOGS +# define sdm_log(...) \ + do { \ + } while (0) +#endif // SDM_WITH_LOGS + +#define _SDM_ONGOING_OP_ERROR_CHECK(Dm) \ + do { \ + if (!Dm->op_in_progress) { \ + sdm_log(ERROR, "No ongoing operation"); \ + return SDM_ERR_LOGIC; \ + } \ + } while (0) + +#define _SDM_ONGOING_OP_COUNT_ERROR_CHECK(Dm) \ + do { \ + if (!Dm->op_count) { \ + sdm_log(ERROR, "No more records to read"); \ + Dm->result = SDM_ERR_LOGIC; \ + return Dm->result; \ + } \ + } while (0) + +int _sdm_begin_register_op(sdm_data_model_t *dm); + +int _sdm_begin_bootstrap_discover_op(sdm_data_model_t *dm, + const fluf_uri_path_t *base_path); + +int _sdm_begin_discover_op(sdm_data_model_t *dm, + const fluf_uri_path_t *base_path); + +int _sdm_begin_execute_op(sdm_data_model_t *dm, + const fluf_uri_path_t *base_path); + +int _sdm_begin_read_op(sdm_data_model_t *dm, const fluf_uri_path_t *base_path); + +int _sdm_get_resource_value(sdm_data_model_t *dm, + const fluf_uri_path_t *path, + fluf_res_value_t *out_value, + fluf_data_type_t *out_type); + +int _sdm_begin_write_op(sdm_data_model_t *dm, const fluf_uri_path_t *base_path); + +int _sdm_begin_create_op(sdm_data_model_t *dm, + const fluf_uri_path_t *base_path); + +int _sdm_create_object_instance(sdm_data_model_t *dm, fluf_iid_t iid); + +int _sdm_process_delete_op(sdm_data_model_t *dm, + const fluf_uri_path_t *base_path); + +int _sdm_delete_res_instance(sdm_data_model_t *dm); + +int _sdm_get_obj_ptr_call_operation_begin(sdm_data_model_t *dm, + fluf_oid_t oid, + sdm_obj_t **out_obj); + +int _sdm_get_obj_ptrs(sdm_obj_t *obj, + const fluf_uri_path_t *path, + _sdm_entity_ptrs_t *out_ptrs); + +int _sdm_get_entity_ptrs(sdm_data_model_t *dm, + const fluf_uri_path_t *path, + _sdm_entity_ptrs_t *out_ptrs); + +#ifndef NDEBUG +int _sdm_check_obj_instance(sdm_obj_inst_t *inst); + +int _sdm_check_obj(sdm_obj_t *obj); +#endif // NDEBUG + +static inline bool _sdm_is_multi_instance_resource(sdm_res_operation_t op) { + return op == SDM_RES_RM || op == SDM_RES_WM || op == SDM_RES_RWM; +} + +#ifdef __cplusplus +} +#endif + +#endif // SDM_CORE_H diff --git a/src/anj/sdm/sdm_create.c b/src/anj/sdm/sdm_create.c new file mode 100644 index 00000000..1d7c3f77 --- /dev/null +++ b/src/anj/sdm/sdm_create.c @@ -0,0 +1,112 @@ +/* + * Copyright 2023-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 +#include + +#include "sdm_core.h" + +static fluf_iid_t find_free_iid(sdm_obj_t *obj) { + for (uint16_t idx = 0; idx < UINT16_MAX; idx++) { + if (idx >= obj->inst_count || idx != obj->insts[idx]->iid) { + return idx; + } + } + AVS_UNREACHABLE("object has more than 65534 instances"); + return 0; +} + +int _sdm_begin_create_op(sdm_data_model_t *dm, + const fluf_uri_path_t *base_path) { + assert(base_path && fluf_uri_path_is(base_path, FLUF_ID_OID)); + + dm->is_transactional = true; + dm->op_ctx.write_ctx.path = *base_path; + dm->op_ctx.write_ctx.instance_created = false; + + sdm_obj_t *obj; + dm->result = _sdm_get_obj_ptr_call_operation_begin( + dm, base_path->ids[FLUF_ID_OID], &obj); + if (dm->result) { + return dm->result; + } + dm->result = _sdm_get_obj_ptrs(obj, base_path, &dm->entity_ptrs); + if (dm->result) { + return dm->result; + } + if (dm->entity_ptrs.obj->inst_count + == dm->entity_ptrs.obj->max_inst_count) { + sdm_log(ERROR, "Maximum number of instances reached"); + dm->result = SDM_ERR_MEMORY; + } + return dm->result; +} + +int _sdm_create_object_instance(sdm_data_model_t *dm, fluf_iid_t iid) { + sdm_obj_t *obj = dm->entity_ptrs.obj; + if (iid == FLUF_ID_INVALID) { + iid = find_free_iid(obj); + } else { + for (uint16_t idx = 0; idx < obj->inst_count; idx++) { + if (iid == obj->insts[idx]->iid) { + sdm_log(ERROR, "Instance already exists"); + return SDM_ERR_METHOD_NOT_ALLOWED; + } + } + } + dm->entity_ptrs.inst = NULL; + + if (!obj->obj_handlers || !obj->obj_handlers->inst_create) { + sdm_log(ERROR, "inst_create handler not defined"); + return SDM_ERR_INTERNAL; + } + + int res = obj->obj_handlers->inst_create(obj, &dm->entity_ptrs.inst, iid); + if (res || !dm->entity_ptrs.inst) { + sdm_log(ERROR, "inst_create failed"); + if (!res) { + // operation failed but inst_create didn't return error + res = SDM_ERR_INTERNAL; + } + return res; + } + dm->entity_ptrs.inst->iid = iid; + assert(!_sdm_check_obj_instance(dm->entity_ptrs.inst)); + + uint16_t idx_to_write = UINT16_MAX; + for (uint16_t idx = 0; idx < obj->inst_count; idx++) { + if (obj->insts[idx]->iid > iid) { + idx_to_write = idx; + break; + } + } + if (idx_to_write == UINT16_MAX) { + obj->insts[obj->inst_count] = dm->entity_ptrs.inst; + } else { + for (uint16_t idx = obj->inst_count; idx > idx_to_write; idx--) { + obj->insts[idx] = obj->insts[idx - 1]; + } + obj->insts[idx_to_write] = dm->entity_ptrs.inst; + } + obj->inst_count++; + + return 0; +} diff --git a/src/anj/sdm/sdm_delete.c b/src/anj/sdm/sdm_delete.c new file mode 100644 index 00000000..1b7de0ba --- /dev/null +++ b/src/anj/sdm/sdm_delete.c @@ -0,0 +1,102 @@ +/* + * Copyright 2023-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 + +#include "sdm_core.h" + +static int delete_instance(sdm_data_model_t *dm) { + sdm_obj_t *obj = dm->entity_ptrs.obj; + if (!obj->obj_handlers || !obj->obj_handlers->inst_delete) { + sdm_log(ERROR, "inst_delete handler not defined"); + return SDM_ERR_INTERNAL; + } + + int ret = obj->obj_handlers->inst_delete(obj, dm->entity_ptrs.inst); + if (ret) { + sdm_log(ERROR, "inst_delete failed"); + return ret; + } + + // udpate inst array + for (uint16_t idx = 0; idx < obj->inst_count; idx++) { + if (obj->insts[idx]->iid == dm->entity_ptrs.inst->iid) { + obj->insts[idx] = NULL; + } else if (obj->insts[idx]->iid > dm->entity_ptrs.inst->iid && idx) { + obj->insts[idx - 1] = obj->insts[idx]; + } + } + obj->inst_count--; + return 0; +} + +int _sdm_process_delete_op(sdm_data_model_t *dm, + const fluf_uri_path_t *base_path) { + assert(fluf_uri_path_is(base_path, FLUF_ID_IID) + || fluf_uri_path_is(base_path, FLUF_ID_RIID)); + + dm->is_transactional = true; + + sdm_obj_t *obj; + dm->result = _sdm_get_obj_ptr_call_operation_begin( + dm, base_path->ids[FLUF_ID_OID], &obj); + if (dm->result) { + return dm->result; + } + dm->result = _sdm_get_obj_ptrs(obj, base_path, &dm->entity_ptrs); + if (dm->result) { + return dm->result; + } + + dm->result = fluf_uri_path_is(base_path, FLUF_ID_IID) + ? delete_instance(dm) + : _sdm_delete_res_instance(dm); + return dm->result; +} + +int _sdm_delete_res_instance(sdm_data_model_t *dm) { + sdm_res_t *res = dm->entity_ptrs.res; + if (!res->res_handlers || !res->res_handlers->res_inst_delete) { + sdm_log(ERROR, "res_inst_delete handler not defined"); + return SDM_ERR_BAD_REQUEST; + } + + int ret = res->res_handlers->res_inst_delete(dm->entity_ptrs.obj, + dm->entity_ptrs.inst, res, + dm->entity_ptrs.res_inst); + if (ret) { + sdm_log(ERROR, "res_inst_delete failed"); + return ret; + } + + // udpate res_inst array + for (uint16_t idx = 0; idx < res->value.res_inst.inst_count; idx++) { + if (res->value.res_inst.insts[idx]->riid + == dm->entity_ptrs.res_inst->riid) { + res->value.res_inst.insts[idx] = NULL; + } else if (res->value.res_inst.insts[idx]->riid + > dm->entity_ptrs.res_inst->riid + && idx) { + res->value.res_inst.insts[idx - 1] = res->value.res_inst.insts[idx]; + } + } + res->value.res_inst.inst_count--; + return 0; +} diff --git a/src/anj/sdm/sdm_device_object.c b/src/anj/sdm/sdm_device_object.c new file mode 100644 index 00000000..20206471 --- /dev/null +++ b/src/anj/sdm/sdm_device_object.c @@ -0,0 +1,235 @@ +/* + * Copyright 2023-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 + +#define ERR_CODE_RES_INST_MAX_COUNT 1 +#define DEVICE_OID 3 + +enum { + RID_MANUFACTURER = 0, + RID_MODEL_NUMBER = 1, + RID_SERIAL_NUMBER = 2, + RID_FIRMWARE_VERSION = 3, + RID_REBOOT = 4, + RID_ERROR_CODE = 11, + RID_BINDING_MODES = 16, +}; + +static const sdm_res_spec_t manufacturer_spec = { + .rid = RID_MANUFACTURER, + .type = FLUF_DATA_TYPE_STRING, + .operation = SDM_RES_R +}; + +static const sdm_res_spec_t model_number_spec = { + .rid = RID_MODEL_NUMBER, + .type = FLUF_DATA_TYPE_STRING, + .operation = SDM_RES_R +}; + +static const sdm_res_spec_t serial_number_spec = { + .rid = RID_SERIAL_NUMBER, + .type = FLUF_DATA_TYPE_STRING, + .operation = SDM_RES_R +}; + +static const sdm_res_spec_t firmware_version_spec = { + .rid = RID_FIRMWARE_VERSION, + .type = FLUF_DATA_TYPE_STRING, + .operation = SDM_RES_R +}; + +static const sdm_res_spec_t reboot_spec = { + .rid = RID_REBOOT, + .operation = SDM_RES_E +}; + +static const sdm_res_spec_t error_code_spec = { + .rid = RID_ERROR_CODE, + .type = FLUF_DATA_TYPE_INT, + .operation = SDM_RES_RM +}; + +static const sdm_res_spec_t supported_binding_modes_spec = { + .rid = RID_BINDING_MODES, + .type = FLUF_DATA_TYPE_STRING, + .operation = SDM_RES_R +}; + +static sdm_res_inst_t err_code_res_inst[ERR_CODE_RES_INST_MAX_COUNT] = { + { + .riid = 0, + .res_value.value.int_value = SDM_DEVICE_OBJ_ERR_CODE_NO_ERROR + } +}; + +static sdm_res_inst_t *err_code_res_insts[ERR_CODE_RES_INST_MAX_COUNT] = { + &err_code_res_inst[0], +}; + +static sdm_res_execute_t *reboot_cb; + +static int res_execute(sdm_obj_t *obj, + sdm_obj_inst_t *obj_inst, + sdm_res_t *res, + const char *execute_arg, + size_t execute_arg_len) { + (void) obj; + (void) obj_inst; + (void) execute_arg; + (void) execute_arg_len; + + fluf_rid_t rid = res->res_spec->rid; + + switch (rid) { + case RID_REBOOT: + return !reboot_cb ? SDM_ERR_INPUT_ARG + : reboot_cb(obj, + obj_inst, + res, + execute_arg, + execute_arg_len); + default: + break; + } + + return SDM_ERR_LOGIC; +} + +static int res_read(sdm_obj_t *obj, + sdm_obj_inst_t *obj_inst, + sdm_res_t *res, + sdm_res_inst_t *res_inst, + fluf_res_value_t *out_value) { + (void) obj; + (void) obj_inst; + (void) res_inst; + (void) out_value; + + fluf_rid_t rid = res->res_spec->rid; + fluf_rid_t riid = res_inst->riid; + + switch (rid) { + case RID_ERROR_CODE: { + if (res->value.res_inst.inst_count < riid - 1) { + return SDM_ERR_INPUT_ARG; + } + *out_value = res_inst->res_value.value; + return 0; + } + default: + break; + } + + return SDM_ERR_LOGIC; +} + +static const sdm_res_handlers_t res_handlers = { + .res_execute = res_execute, + .res_read = res_read, +}; + +static sdm_res_t device_res[] = { + { + .res_spec = &manufacturer_spec, + }, + { + .res_spec = &model_number_spec, + }, + { + .res_spec = &serial_number_spec, + }, + { + .res_spec = &firmware_version_spec, + }, + { + .res_spec = &reboot_spec, + .res_handlers = &res_handlers + }, + { + .res_spec = &error_code_spec, + .value.res_inst.insts = err_code_res_insts, + .value.res_inst.inst_count = 1, + .value.res_inst.max_inst_count = ERR_CODE_RES_INST_MAX_COUNT, + .res_handlers = &res_handlers + }, + { + .res_spec = &supported_binding_modes_spec, + }, +}; + +static sdm_obj_inst_t obj_inst = { + .iid = 0, + .resources = device_res, + .res_count = AVS_ARRAY_SIZE(device_res) +}; + +static sdm_obj_inst_t *device_obj_inst[1] = { &obj_inst }; + +static sdm_obj_t device_obj = { + .oid = DEVICE_OID, + .version = "1.2", + .inst_count = 1, + .max_inst_count = 1, + .insts = device_obj_inst, + .obj_handlers = NULL +}; + +static int res_values_initialize(sdm_device_object_init_t *obj_init) { + for (size_t i = 0; i < AVS_ARRAY_SIZE(device_res); i++) { + fluf_rid_t curr_rid = device_res[i].res_spec->rid; + switch (curr_rid) { + case RID_MANUFACTURER: + device_res[i].value.res_value.value.bytes_or_string.data = + obj_init->manufacturer ? obj_init->manufacturer : ""; + break; + case RID_MODEL_NUMBER: + device_res[i].value.res_value.value.bytes_or_string.data = + obj_init->model_number ? obj_init->model_number : ""; + break; + case RID_SERIAL_NUMBER: + device_res[i].value.res_value.value.bytes_or_string.data = + obj_init->serial_number ? obj_init->serial_number : ""; + break; + case RID_FIRMWARE_VERSION: + device_res[i].value.res_value.value.bytes_or_string.data = + obj_init->firmware_version ? obj_init->firmware_version + : ""; + break; + case RID_BINDING_MODES: + device_res[i].value.res_value.value.bytes_or_string.data = + obj_init->supported_binding_modes + ? obj_init->supported_binding_modes + : ""; + break; + default: + break; + } + } + + reboot_cb = obj_init->reboot_handler; + + return 0; +} + +int sdm_device_object_install(sdm_data_model_t *dm, + sdm_device_object_init_t *obj_init) { + assert(dm); + assert(obj_init); + + if (res_values_initialize(obj_init)) { + return -1; + } + + return sdm_add_obj(dm, &device_obj); +} diff --git a/src/anj/sdm/sdm_discover.c b/src/anj/sdm/sdm_discover.c new file mode 100644 index 00000000..24d40bf6 --- /dev/null +++ b/src/anj/sdm/sdm_discover.c @@ -0,0 +1,381 @@ +/* + * Copyright 2023-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 + +#include "sdm_core.h" + +#define _SDM_OBJ_SERVER_SSID_RID 0 +#define _SDM_OBJ_SECURITY_SERVER_URI_RID 0 +#define _SDM_OBJ_SECURITY_BOOTSTRAP_SERVER_RID 1 +#define _SDM_OBJ_SECURITY_SSID_RID 10 +#define _SDM_OBJ_SECURITY_OSCORE_RID 17 + +static void get_security_obj_ssid_value(sdm_data_model_t *dm, + sdm_obj_t *obj, + sdm_obj_inst_t *inst, + const uint16_t **ssid) { + fluf_res_value_t value; + fluf_data_type_t type; + *ssid = NULL; + _sdm_disc_ctx_t *disc_ctx = &dm->op_ctx.disc_ctx; + if (!_sdm_get_resource_value( + dm, + &FLUF_MAKE_RESOURCE_PATH( + obj->oid, + inst->iid, + _SDM_OBJ_SECURITY_BOOTSTRAP_SERVER_RID), + &value, &type) + && type == FLUF_DATA_TYPE_BOOL && !value.bool_value + && !_sdm_get_resource_value( + dm, + &FLUF_MAKE_RESOURCE_PATH(obj->oid, inst->iid, + _SDM_OBJ_SECURITY_SSID_RID), + &value, &type) + && type == FLUF_DATA_TYPE_INT) { + disc_ctx->ssid = (uint16_t) value.uint_value; + *ssid = &disc_ctx->ssid; + } +} + +static void get_security_instance_ssid_for_oscore_obj(sdm_data_model_t *dm, + fluf_iid_t iid, + const uint16_t **ssid) { + // Instance have _SDM_OBJ_SECURITY_OSCORE_RID equal to given oid and iid, + // it's not a bootstrap server instance and SSID value is present. + fluf_res_value_t value; + fluf_data_type_t type; + for (uint16_t idx = 0; idx < dm->objs[0]->inst_count; idx++) { + if (!_sdm_get_resource_value( + dm, + &FLUF_MAKE_RESOURCE_PATH(dm->objs[0]->oid, + dm->objs[0]->insts[idx]->iid, + _SDM_OBJ_SECURITY_OSCORE_RID), + &value, &type) + && type == FLUF_DATA_TYPE_OBJLNK && value.objlnk.iid == iid) { + assert(value.objlnk.oid == FLUF_OBJ_ID_OSCORE); + get_security_obj_ssid_value(dm, dm->objs[0], + dm->objs[0]->insts[idx], ssid); + } + } +} + +static void get_ssid_and_uri(sdm_data_model_t *dm, + sdm_obj_t *obj, + sdm_obj_inst_t *inst, + const uint16_t **ssid, + const char **uri) { + if (obj->oid != FLUF_OBJ_ID_SECURITY && obj->oid != FLUF_OBJ_ID_SERVER + && obj->oid != FLUF_OBJ_ID_OSCORE) { + return; + } + *ssid = NULL; + *uri = NULL; + + _sdm_disc_ctx_t *disc_ctx = &dm->op_ctx.disc_ctx; + fluf_res_value_t value; + fluf_data_type_t type; + // SSID and URI are added if instance is not related to Bootstrap-Server. + // Resource /1 of Security Object is checked to determine SSID and URI + // presence. If there are no resources needed in the operation, we will not + // add the URI and SSID to the message, without any error returns. + if (obj->oid == FLUF_OBJ_ID_SECURITY) { + get_security_obj_ssid_value(dm, obj, inst, ssid); + if (!*ssid) { + return; + } + fluf_uri_path_t server_uri_path = + FLUF_MAKE_RESOURCE_PATH(obj->oid, inst->iid, + _SDM_OBJ_SECURITY_SERVER_URI_RID); + // FLUF_DATA_TYPE_EXTERNAL_STRING is not allowed here + if (!_sdm_get_resource_value(dm, &server_uri_path, &value, &type) + && type == FLUF_DATA_TYPE_STRING) { + *uri = (const char *) value.bytes_or_string.data; + } + } else if (obj->oid == FLUF_OBJ_ID_SERVER) { + if (!_sdm_get_resource_value( + dm, + &FLUF_MAKE_RESOURCE_PATH(obj->oid, inst->iid, + _SDM_OBJ_SERVER_SSID_RID), + &value, &type) + && type == FLUF_DATA_TYPE_INT) { + disc_ctx->ssid = (uint16_t) value.int_value; + *ssid = &disc_ctx->ssid; + } + } else if (obj->oid == FLUF_OBJ_ID_OSCORE) { + // find Security Object Instance related with this OSCORE Instance and + // read SSID + get_security_instance_ssid_for_oscore_obj(dm, inst->iid, ssid); + } +} + +int _sdm_begin_bootstrap_discover_op(sdm_data_model_t *dm, + const fluf_uri_path_t *base_path) { + assert(dm); + if (base_path && fluf_uri_path_has(base_path, FLUF_ID_IID)) { + sdm_log(ERROR, "Bootstrap discover can't target object instance"); + dm->result = SDM_ERR_INPUT_ARG; + return dm->result; + } + _sdm_disc_ctx_t *disc_ctx = &dm->op_ctx.disc_ctx; + + dm->op_count = 0; + disc_ctx->obj_idx = 0; + disc_ctx->inst_idx = 0; + disc_ctx->level = FLUF_ID_OID; + bool all_objects = true; + if (base_path && fluf_uri_path_has(base_path, FLUF_ID_OID)) { + all_objects = false; + } + for (uint16_t idx = 0; idx < dm->objs_count; idx++) { + if (all_objects || dm->objs[idx]->oid == base_path->ids[FLUF_ID_OID]) { + if (!all_objects) { + disc_ctx->obj_idx = idx; + } + dm->objs[idx]->in_transaction = true; + if (dm->objs[idx]->obj_handlers + && dm->objs[idx]->obj_handlers->operation_begin) { + dm->result = dm->objs[idx]->obj_handlers->operation_begin( + dm->objs[idx], dm->operation); + if (dm->result) { + return dm->result; + } + } + dm->op_count = dm->op_count + 1 + dm->objs[idx]->inst_count; + } + } + return 0; +} + +int sdm_get_bootstrap_discover_record(sdm_data_model_t *dm, + fluf_uri_path_t *out_path, + const char **out_version, + const uint16_t **ssid, + const char **uri) { + assert(dm && out_path && out_version && ssid && uri); + + _sdm_disc_ctx_t *disc_ctx = &dm->op_ctx.disc_ctx; + assert(disc_ctx->obj_idx < dm->objs_count); + + if (dm->operation != FLUF_OP_DM_DISCOVER || !dm->boostrap_operation) { + sdm_log(ERROR, "Incorrect operation"); + dm->result = SDM_ERR_LOGIC; + return dm->result; + } + _SDM_ONGOING_OP_ERROR_CHECK(dm); + _SDM_ONGOING_OP_COUNT_ERROR_CHECK(dm); + + *out_version = NULL; + *ssid = NULL; + *uri = NULL; + + sdm_obj_t *obj = dm->objs[disc_ctx->obj_idx]; + + if (disc_ctx->level == FLUF_ID_OID) { + *out_path = FLUF_MAKE_OBJECT_PATH(obj->oid); + *out_version = obj->version; + + if (obj->inst_count) { + disc_ctx->level = FLUF_ID_IID; + } else { + disc_ctx->obj_idx++; + } + } else { + assert(disc_ctx->inst_idx < obj->inst_count); + sdm_obj_inst_t *inst = obj->insts[disc_ctx->inst_idx]; + *out_path = FLUF_MAKE_INSTANCE_PATH(obj->oid, inst->iid); + get_ssid_and_uri(dm, obj, inst, ssid, uri); + + disc_ctx->inst_idx++; + if (disc_ctx->inst_idx == obj->inst_count) { + disc_ctx->inst_idx = 0; + disc_ctx->obj_idx++; + disc_ctx->level = FLUF_ID_OID; + } + } + + dm->op_count--; + return dm->op_count > 0 ? 0 : SDM_LAST_RECORD; +} + +int _sdm_begin_discover_op(sdm_data_model_t *dm, + const fluf_uri_path_t *base_path) { + assert(dm); + assert(base_path && fluf_uri_path_has(base_path, FLUF_ID_OID) + && !fluf_uri_path_has(base_path, FLUF_ID_RIID)); + _sdm_disc_ctx_t *disc_ctx = &dm->op_ctx.disc_ctx; + dm->op_count = 0; + bool all_instances = !fluf_uri_path_has(base_path, FLUF_ID_IID); + bool all_resources = + all_instances || !fluf_uri_path_has(base_path, FLUF_ID_RID); + disc_ctx->inst_idx = 0; + disc_ctx->res_idx = 0; + disc_ctx->res_inst_idx = 0; + if (all_instances) { + disc_ctx->level = FLUF_ID_OID; + dm->op_count++; + } else if (all_resources) { + disc_ctx->level = FLUF_ID_IID; + } else { + disc_ctx->level = FLUF_ID_RID; + } + + dm->result = _sdm_get_obj_ptr_call_operation_begin( + dm, base_path->ids[FLUF_ID_OID], &dm->entity_ptrs.obj); + if (dm->result) { + return dm->result; + } + + sdm_obj_t *obj = dm->entity_ptrs.obj; + + for (uint16_t idx = 0; idx < obj->inst_count; idx++) { + sdm_obj_inst_t *inst = obj->insts[idx]; + if (!all_instances && base_path->ids[FLUF_ID_IID] == inst->iid) { + disc_ctx->inst_idx = idx; + } + if (all_instances || base_path->ids[FLUF_ID_IID] == inst->iid) { + if (all_resources) { + dm->op_count++; + } + for (uint16_t res_idx = 0; res_idx < inst->res_count; res_idx++) { + sdm_res_t *res = &inst->resources[res_idx]; + if (!all_resources + && base_path->ids[FLUF_ID_RID] == res->res_spec->rid) { + disc_ctx->res_idx = res_idx; + } + if (all_resources + || base_path->ids[FLUF_ID_RID] == res->res_spec->rid) { + dm->op_count++; + if (_sdm_is_multi_instance_resource( + res->res_spec->operation)) { + dm->op_count += res->value.res_inst.inst_count; + } + } + } + } + } + return 0; +} + +static void get_inst_record(sdm_data_model_t *dm, fluf_uri_path_t *out_path) { + _sdm_disc_ctx_t *disc_ctx = &dm->op_ctx.disc_ctx; + sdm_obj_t *obj = dm->entity_ptrs.obj; + assert(disc_ctx->inst_idx < obj->inst_count); + sdm_obj_inst_t *inst = obj->insts[disc_ctx->inst_idx]; + *out_path = FLUF_MAKE_INSTANCE_PATH(obj->oid, inst->iid); + if (inst->res_count) { + disc_ctx->level = FLUF_ID_RID; + } else { + disc_ctx->inst_idx++; + } +} + +static void increment_idx_starting_from_res(_sdm_disc_ctx_t *disc_ctx, + uint16_t res_count) { + disc_ctx->res_idx++; + if (disc_ctx->res_idx == res_count) { + disc_ctx->res_idx = 0; + disc_ctx->inst_idx++; + disc_ctx->level = FLUF_ID_IID; + } +} + +static void increment_idx_starting_from_res_inst(_sdm_disc_ctx_t *disc_ctx, + uint16_t res_count, + uint16_t res_inst_count) { + disc_ctx->res_inst_idx++; + if (disc_ctx->res_inst_idx == res_inst_count) { + disc_ctx->res_inst_idx = 0; + disc_ctx->level = FLUF_ID_RID; + increment_idx_starting_from_res(disc_ctx, res_count); + } +} + +static void get_res_record(sdm_data_model_t *dm, + fluf_uri_path_t *out_path, + const uint16_t **out_dim) { + _sdm_disc_ctx_t *disc_ctx = &dm->op_ctx.disc_ctx; + sdm_obj_t *obj = dm->entity_ptrs.obj; + sdm_obj_inst_t *inst = obj->insts[disc_ctx->inst_idx]; + assert(disc_ctx->res_idx < inst->res_count); + sdm_res_t *res = &inst->resources[disc_ctx->res_idx]; + *out_path = + FLUF_MAKE_RESOURCE_PATH(obj->oid, inst->iid, res->res_spec->rid); + bool is_multi_instance = + _sdm_is_multi_instance_resource(res->res_spec->operation); + if (is_multi_instance) { + *out_dim = &res->value.res_inst.inst_count; + if (res->value.res_inst.inst_count) { + disc_ctx->level = FLUF_ID_RIID; + } + } + if (!is_multi_instance || !res->value.res_inst.inst_count) { + increment_idx_starting_from_res(disc_ctx, inst->res_count); + } +} + +static void get_res_inst_record(sdm_data_model_t *dm, + fluf_uri_path_t *out_path) { + _sdm_disc_ctx_t *disc_ctx = &dm->op_ctx.disc_ctx; + sdm_obj_t *obj = dm->entity_ptrs.obj; + sdm_obj_inst_t *inst = obj->insts[disc_ctx->inst_idx]; + sdm_res_t *res = &inst->resources[disc_ctx->res_idx]; + assert(disc_ctx->res_inst_idx < res->value.res_inst.inst_count); + sdm_res_inst_t *res_inst = + res->value.res_inst.insts[disc_ctx->res_inst_idx]; + *out_path = FLUF_MAKE_RESOURCE_INSTANCE_PATH( + obj->oid, inst->iid, res->res_spec->rid, res_inst->riid); + increment_idx_starting_from_res_inst(disc_ctx, inst->res_count, + res->value.res_inst.inst_count); +} + +int sdm_get_discover_record(sdm_data_model_t *dm, + fluf_uri_path_t *out_path, + const char **out_version, + const uint16_t **out_dim) { + assert(dm && out_path && out_version && out_dim); + _sdm_disc_ctx_t *disc_ctx = &dm->op_ctx.disc_ctx; + + if (dm->operation != FLUF_OP_DM_DISCOVER || dm->boostrap_operation) { + sdm_log(ERROR, "Incorrect operation"); + dm->result = SDM_ERR_LOGIC; + return dm->result; + } + _SDM_ONGOING_OP_ERROR_CHECK(dm); + _SDM_ONGOING_OP_COUNT_ERROR_CHECK(dm); + + *out_version = NULL; + *out_dim = NULL; + + if (disc_ctx->level == FLUF_ID_OID) { + *out_path = FLUF_MAKE_OBJECT_PATH(dm->entity_ptrs.obj->oid); + *out_version = dm->entity_ptrs.obj->version; + disc_ctx->level = FLUF_ID_IID; + } else if (disc_ctx->level == FLUF_ID_IID) { + get_inst_record(dm, out_path); + } else if (disc_ctx->level == FLUF_ID_RID) { + get_res_record(dm, out_path, out_dim); + } else if (disc_ctx->level == FLUF_ID_RIID) { + get_res_inst_record(dm, out_path); + } + + dm->op_count--; + return dm->op_count > 0 ? 0 : SDM_LAST_RECORD; +} diff --git a/src/anj/sdm/sdm_execute.c b/src/anj/sdm/sdm_execute.c new file mode 100644 index 00000000..9ba87964 --- /dev/null +++ b/src/anj/sdm/sdm_execute.c @@ -0,0 +1,63 @@ +/* + * Copyright 2023-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 + +#include "sdm_core.h" + +int _sdm_begin_execute_op(sdm_data_model_t *dm, + const fluf_uri_path_t *base_path) { + assert(dm && base_path && fluf_uri_path_is(base_path, FLUF_ID_RID)); + sdm_obj_t *obj; + dm->result = _sdm_get_obj_ptr_call_operation_begin( + dm, base_path->ids[FLUF_ID_OID], &obj); + if (dm->result) { + return dm->result; + } + dm->result = _sdm_get_obj_ptrs(obj, base_path, &dm->entity_ptrs); + if (dm->result) { + return dm->result; + } + if (dm->entity_ptrs.res->res_spec->operation != SDM_RES_E) { + sdm_log(ERROR, "Resource is not executable"); + dm->result = SDM_ERR_METHOD_NOT_ALLOWED; + return dm->result; + } + return 0; +} + +int sdm_execute(sdm_data_model_t *dm, + const char *execute_arg, + size_t execute_arg_len) { + assert(dm && dm->entity_ptrs.res && dm->entity_ptrs.res->res_handlers + && dm->entity_ptrs.res->res_handlers->res_execute); + _SDM_ONGOING_OP_ERROR_CHECK(dm); + dm->result = + dm->entity_ptrs.res->res_handlers->res_execute(dm->entity_ptrs.obj, + dm->entity_ptrs.inst, + dm->entity_ptrs.res, + execute_arg, + execute_arg_len); + if (dm->result) { + sdm_log(ERROR, "res_execute handler failed."); + return dm->result; + } + return 0; +} diff --git a/src/anj/sdm/sdm_fw_update.c b/src/anj/sdm/sdm_fw_update.c new file mode 100644 index 00000000..3289e6e6 --- /dev/null +++ b/src/anj/sdm/sdm_fw_update.c @@ -0,0 +1,518 @@ +/* + * Copyright 2023-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 + +#include + +#define FW_UPDATE_OID 5 + +/* FOTA method support */ +#if !ANJ_FOTA_PUSH_METHOD_SUPPORTED +// if push method if not supported, it's safe to assume that pull is - +// guaranteed by a condition check in fluf_config.h 0 -> pull only +# define METHODS_SUPPORTED 0 +#else // !ANJ_FOTA_PUSH_METHOD_SUPPORTED +# if ANJ_FOTA_PULL_METHOD_SUPPORTED +// this means push is supported as well +// 2 -> pull & push +# define METHODS_SUPPORTED 2 +# else // ANJ_FOTA_PULL_METHOD_SUPPORTED +// only push enabled +// 1 -> push +# define METHODS_SUPPORTED 1 +# endif // ANJ_FOTA_PULL_METHOD_SUPPORTED +#endif // !ANJ_FOTA_PUSH_METHOD_SUPPORTED + +enum { + RID_PACKAGE = 0, + RID_PACKAGE_URI = 1, + RID_UPDATE = 2, + RID_STATE = 3, + RID_UPDATE_RESULT = 5, + RID_PKG_NAME = 6, + RID_PKG_VERSION = 7, + RID_UPDATE_PROTOCOL_SUPPORT = 8, + RID_UPDATE_DELIVERY_METHOD = 9 +}; + +typedef enum { + UPDATE_STATE_IDLE = 0, + UPDATE_STATE_DOWNLOADING, + UPDATE_STATE_DOWNLOADED, + UPDATE_STATE_UPDATING +} fw_update_state_t; + +#define PACKAGE_RES_IDX 0 +#define PACKAGE_URI_RES_IDX 1 +#define UPDATE_RES_IDX 2 +#define STATE_RES_IDX 3 +#define UPDATE_RESULT_RES_IDX 4 +#define PKG_NAME_RES_IDX 5 +#define PKG_VER_RES_IDX 6 +#define SUPORTED_PROTOCOLS_RES_IDX 7 +#define DELIVERY_METHOD_RES_IDX 8 + +static struct { + fw_update_state_t state; + sdm_fw_update_result_t result; + char uri[256]; + sdm_fw_update_handlers_t *user_handlers; + uint8_t supported_protocols; + void *user_ptr; + bool write_start_called; +} fw_update_repr; + +static const sdm_res_spec_t package_spec = { + .rid = RID_PACKAGE, + .type = FLUF_DATA_TYPE_BYTES, + .operation = SDM_RES_W +}; + +static const sdm_res_spec_t package_uri_spec = { + .rid = RID_PACKAGE_URI, + .type = FLUF_DATA_TYPE_STRING, + .operation = SDM_RES_RW +}; + +static const sdm_res_spec_t update_spec = { + .rid = RID_UPDATE, + .operation = SDM_RES_E +}; + +static const sdm_res_spec_t state_spec = { + .rid = RID_STATE, + .type = FLUF_DATA_TYPE_INT, + .operation = SDM_RES_R +}; + +static const sdm_res_spec_t update_result_spec = { + .rid = RID_UPDATE_RESULT, + .type = FLUF_DATA_TYPE_INT, + .operation = SDM_RES_R +}; + +static const sdm_res_spec_t pkg_name_spec = { + .rid = RID_PKG_NAME, + .type = FLUF_DATA_TYPE_STRING, + .operation = SDM_RES_R +}; + +static const sdm_res_spec_t pkg_ver_spec = { + .rid = RID_PKG_VERSION, + .type = FLUF_DATA_TYPE_STRING, + .operation = SDM_RES_R +}; + +static const sdm_res_spec_t protocol_support_spec = { + .rid = RID_UPDATE_PROTOCOL_SUPPORT, + .type = FLUF_DATA_TYPE_INT, + .operation = SDM_RES_RM +}; + +static const sdm_res_spec_t delivery_method_spec = { + .rid = RID_UPDATE_DELIVERY_METHOD, + .type = FLUF_DATA_TYPE_INT, + .operation = SDM_RES_R +}; + +static bool is_reset_request(const fluf_res_value_t *value) { + if (((char *) value->bytes_or_string.data)[0] == '\0' + && value->bytes_or_string.full_length_hint == 1) { + return true; + } + return false; +} + +static void reset(void) { + fw_update_repr.user_handlers->reset_handler(fw_update_repr.user_ptr); + fw_update_repr.state = UPDATE_STATE_IDLE; + fw_update_repr.write_start_called = false; +} + +static int res_write(sdm_obj_t *obj, + sdm_obj_inst_t *obj_inst, + sdm_res_t *res, + sdm_res_inst_t *res_inst, + const fluf_res_value_t *value) { + (void) obj; + (void) obj_inst; + (void) res_inst; + + fluf_rid_t rid = res->res_spec->rid; + + switch (rid) { + case RID_PACKAGE: { + if (fw_update_repr.state == UPDATE_STATE_UPDATING) { + return SDM_ERR_METHOD_NOT_ALLOWED; + } + + int ret = 0; + + // handle state machine reset with an empty write + if (is_reset_request(value)) { + if (fw_update_repr.state == UPDATE_STATE_DOWNLOADING) { + fw_update_repr.user_handlers->cancel_download_handler( + fw_update_repr.user_ptr, fw_update_repr.uri); + } + reset(); + return 0; + } + + if (fw_update_repr.state == UPDATE_STATE_IDLE) { + sdm_fw_update_result_t result; + + // handle first chunk if needed + if (!fw_update_repr.write_start_called) { + result = fw_update_repr.user_handlers + ->package_write_start_handler( + fw_update_repr.user_ptr); + if (result != SDM_FW_UPDATE_RESULT_SUCCESS + && result != SDM_FW_UPDATE_RESULT_INITIAL) { + fw_update_repr.result = result; + return SDM_ERR_INTERNAL; + } + fw_update_repr.write_start_called = true; + } + + // write actual data + result = fw_update_repr.user_handlers->package_write_handler( + fw_update_repr.user_ptr, + value->bytes_or_string.data, + value->bytes_or_string.chunk_length); + if (result != SDM_FW_UPDATE_RESULT_SUCCESS + && result != SDM_FW_UPDATE_RESULT_INITIAL) { + fw_update_repr.result = result; + reset(); + return SDM_ERR_INTERNAL; + } + + // check if that's the last chunk (block) + if (value->bytes_or_string.chunk_length + + value->bytes_or_string.offset + == value->bytes_or_string.full_length_hint) { + result = fw_update_repr.user_handlers + ->package_write_finish_handler( + fw_update_repr.user_ptr); + if (result != SDM_FW_UPDATE_RESULT_SUCCESS + && result != SDM_FW_UPDATE_RESULT_INITIAL) { + reset(); + ret = SDM_ERR_INTERNAL; + } else { + fw_update_repr.state = UPDATE_STATE_DOWNLOADED; + } + } + + return ret; + } + + // unhandled cases are Write calls in a wrong state + return SDM_ERR_BAD_REQUEST; + } + case RID_PACKAGE_URI: { + // handle state machine reset with an empty write + if (is_reset_request(value)) { + if (fw_update_repr.state == UPDATE_STATE_UPDATING) { + return SDM_ERR_METHOD_NOT_ALLOWED; + } + fw_update_repr.uri[0] = '\0'; + + fw_update_repr.user_handlers->cancel_download_handler( + fw_update_repr.user_ptr, fw_update_repr.uri); + reset(); + return 0; + } + + // non-empty write can be handled only in IDLE state + if (fw_update_repr.state != UPDATE_STATE_IDLE) { + return SDM_ERR_BAD_REQUEST; + } + + // apply and handle URI write + strcpy(fw_update_repr.uri, (char *) value->bytes_or_string.data); + sdm_fw_update_result_t result = + fw_update_repr.user_handlers->uri_write_handler( + fw_update_repr.user_ptr, fw_update_repr.uri); + if (result == SDM_FW_UPDATE_RESULT_SUCCESS + || result == SDM_FW_UPDATE_RESULT_INITIAL) { + fw_update_repr.state = UPDATE_STATE_DOWNLOADING; + return 0; + } + fw_update_repr.result = result; + return SDM_ERR_BAD_REQUEST; + } + default: + return SDM_ERR_METHOD_NOT_ALLOWED; + } +} + +static int res_read(sdm_obj_t *obj, + sdm_obj_inst_t *obj_inst, + sdm_res_t *res, + sdm_res_inst_t *res_inst, + fluf_res_value_t *out_value) { + (void) obj; + (void) obj_inst; + (void) res_inst; + (void) out_value; + + fluf_rid_t rid = res->res_spec->rid; + + switch (rid) { + case RID_PACKAGE_URI: { + out_value->bytes_or_string.data = fw_update_repr.uri; + return 0; + } + case RID_STATE: { + out_value->int_value = fw_update_repr.state; + return 0; + } + case RID_UPDATE_RESULT: { + out_value->int_value = fw_update_repr.result; + return 0; + } + case RID_PKG_NAME: { + if (!fw_update_repr.user_handlers->get_name) { + out_value->bytes_or_string.data = ""; + out_value->bytes_or_string.chunk_length = 0; + return 0; + } + out_value->bytes_or_string.data = + (char *) (intptr_t) fw_update_repr.user_handlers->get_name( + fw_update_repr.user_ptr); + out_value->bytes_or_string.chunk_length = + strlen((char *) out_value->bytes_or_string.data); + return 0; + } + case RID_PKG_VERSION: { + if (!fw_update_repr.user_handlers->get_version) { + out_value->bytes_or_string.data = ""; + out_value->bytes_or_string.chunk_length = 0; + return 0; + } + out_value->bytes_or_string.data = + (char *) (intptr_t) fw_update_repr.user_handlers->get_version( + fw_update_repr.user_ptr); + out_value->bytes_or_string.chunk_length = + strlen((char *) out_value->bytes_or_string.data); + return 0; + } + case RID_UPDATE_DELIVERY_METHOD: { + out_value->int_value = METHODS_SUPPORTED; + return 0; + } + default: + return SDM_ERR_METHOD_NOT_ALLOWED; + } +} + +static int res_execute(sdm_obj_t *obj, + sdm_obj_inst_t *obj_inst, + sdm_res_t *res, + const char *execute_arg, + size_t execute_arg_len) { + (void) obj; + (void) obj_inst; + (void) execute_arg; + (void) execute_arg_len; + + fluf_rid_t rid = res->res_spec->rid; + + switch (rid) { + case RID_UPDATE: + if (fw_update_repr.state != UPDATE_STATE_DOWNLOADED) { + return SDM_ERR_METHOD_NOT_ALLOWED; + } + sdm_fw_update_result_t result = + fw_update_repr.user_handlers->update_start_handler( + fw_update_repr.user_ptr); + if (result == SDM_FW_UPDATE_RESULT_SUCCESS + || result == SDM_FW_UPDATE_RESULT_INITIAL) { + fw_update_repr.state = UPDATE_STATE_UPDATING; + return 0; + } + fw_update_repr.state = UPDATE_STATE_IDLE; + return SDM_ERR_INTERNAL; + default: + return SDM_ERR_METHOD_NOT_ALLOWED; + } +} + +static const sdm_res_handlers_t update_handler = { + .res_execute = res_execute +}; + +static const sdm_res_handlers_t res_handlers = { + .res_read = res_read, + .res_write = res_write, +}; + +#define PROCOTOLS_COUNT 6 +sdm_res_inst_t protocol_support_instances_1[PROCOTOLS_COUNT]; +sdm_res_inst_t *protocol_support_instances[PROCOTOLS_COUNT]; + +static sdm_res_t fw_update_res[] = { + [PACKAGE_RES_IDX] = { + .res_spec = &package_spec, + .res_handlers = &res_handlers + }, + [PACKAGE_URI_RES_IDX] = { + .res_spec = &package_uri_spec, + .res_handlers = &res_handlers + }, + [UPDATE_RES_IDX] = { + .res_spec = &update_spec, + .res_handlers = &update_handler + }, + [STATE_RES_IDX] = { + .res_spec = &state_spec, + .res_handlers = &res_handlers + }, + [UPDATE_RESULT_RES_IDX] = { + .res_spec = &update_result_spec, + .res_handlers = &res_handlers + }, + [PKG_NAME_RES_IDX] = { + .res_spec = &pkg_name_spec, + .res_handlers = &res_handlers + }, + [PKG_VER_RES_IDX] = { + .res_spec = &pkg_ver_spec, + .res_handlers = &res_handlers + }, + [SUPORTED_PROTOCOLS_RES_IDX] = { + .res_spec = &protocol_support_spec, + .value.res_inst.insts = protocol_support_instances, + .value.res_inst.max_inst_count = PROCOTOLS_COUNT + }, + [DELIVERY_METHOD_RES_IDX] = { + .res_spec = &delivery_method_spec, + .res_handlers = &res_handlers + } +}; + +static sdm_obj_inst_t obj_inst = { + .iid = 0, + .resources = fw_update_res, + .res_count = AVS_ARRAY_SIZE(fw_update_res) +}; + +static sdm_obj_inst_t *fw_update_obj_inst[1] = { &obj_inst }; + +static sdm_obj_t fw_update_obj = { + .oid = FW_UPDATE_OID, + .version = "1.0", + .inst_count = 1, + .max_inst_count = 1, + .insts = fw_update_obj_inst +}; + +static inline bool fw_update_object_exists(sdm_data_model_t *dm) { + for (size_t i = 0; i < dm->objs_count; i++) { + if (dm->objs[i]->oid == FW_UPDATE_OID) { + return true; + } + } + + return false; +} + +int sdm_fw_update_object_install(sdm_data_model_t *dm, + sdm_fw_update_handlers_t *handlers, + void *user_ptr, + uint8_t supported_protocols) { + if (!dm || !handlers || !handlers->update_start_handler + || !supported_protocols) { + return -1; + } + + if (fw_update_object_exists(dm)) { + return -1; + } +#ifdef FLUL_FOTA_PUSH_METHOD_SUPPORTED + if (!handlers->package_write_start_handler + || !handlers->package_write_handler + || !handlers->package_write_finish_handler) { + return -1; + } +#endif // FLUL_FOTA_PUSH_METHOD_SUPPORTED +#ifdef FLUL_FOTA_PULL_METHOD_SUPPORTED + if (!handlers->uri_write_handler || !handlers->cancel_download_handler) { + return -1; + } +#endif // FLUL_FOTA_PULL_METHOD_SUPPORTED + + fw_update_repr.user_ptr = user_ptr; + fw_update_repr.user_handlers = handlers; + fw_update_repr.supported_protocols = supported_protocols; + fw_update_repr.state = UPDATE_STATE_IDLE; + fw_update_repr.write_start_called = false; + fw_update_repr.result = SDM_FW_UPDATE_RESULT_INITIAL; + + /** + * create 5/0/8 Multi-Instance Resource instances "dynamically" - the + * maximum size array of instances is pre-alocated and they are filled + * with proper IID and protocol-coding constant integers. + */ + fluf_riid_t iid = 0; + for (size_t i = 0; i < PROCOTOLS_COUNT; i++) { + if (supported_protocols & (1 << i)) { + protocol_support_instances[iid] = + &protocol_support_instances_1[iid]; + protocol_support_instances_1[iid].riid = iid; + protocol_support_instances_1[iid].res_value.value.int_value = + (int64_t) i; + iid++; + } + } + fw_update_res[SUPORTED_PROTOCOLS_RES_IDX].value.res_inst.inst_count = iid; + + return sdm_add_obj(dm, &fw_update_obj); +} + +int sdm_fw_update_object_set_update_result(sdm_fw_update_result_t result) { + switch (result) { + case SDM_FW_UPDATE_RESULT_SUCCESS: + case SDM_FW_UPDATE_RESULT_INTEGRITY_FAILURE: + case SDM_FW_UPDATE_RESULT_FAILED: + fw_update_repr.result = result; + fw_update_repr.state = UPDATE_STATE_IDLE; + fw_update_repr.write_start_called = false; + return 0; + default: + return -1; + } +} + +int sdm_fw_update_object_set_download_result(sdm_fw_update_result_t result) { + if (fw_update_repr.state != UPDATE_STATE_DOWNLOADING) { + return -1; + } + fw_update_repr.result = result; + if (result != SDM_FW_UPDATE_RESULT_SUCCESS) { + reset(); + fw_update_repr.state = UPDATE_STATE_IDLE; + return 0; + } + fw_update_repr.state = UPDATE_STATE_DOWNLOADED; + + return 0; +} diff --git a/src/anj/sdm/sdm_impl.c b/src/anj/sdm/sdm_impl.c new file mode 100644 index 00000000..fd602fca --- /dev/null +++ b/src/anj/sdm/sdm_impl.c @@ -0,0 +1,554 @@ +/* + * Copyright 2023-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 +#include + +#include "sdm_core.h" + +static uint8_t map_sdm_err_to_coap_code(int error_code) { + switch (error_code) { + case SDM_ERR_INPUT_ARG: + case SDM_ERR_MEMORY: + case SDM_ERR_LOGIC: + return FLUF_COAP_CODE_BAD_REQUEST; + case SDM_ERR_BAD_REQUEST: + return FLUF_COAP_CODE_BAD_REQUEST; + case SDM_ERR_UNAUTHORIZED: + return FLUF_COAP_CODE_UNAUTHORIZED; + case SDM_ERR_NOT_FOUND: + return FLUF_COAP_CODE_NOT_FOUND; + case SDM_ERR_METHOD_NOT_ALLOWED: + return FLUF_COAP_CODE_METHOD_NOT_ALLOWED; + case SDM_ERR_INTERNAL: + return FLUF_COAP_CODE_BAD_REQUEST; + case SDM_ERR_NOT_IMPLEMENTED: + return FLUF_COAP_CODE_NOT_IMPLEMENTED; + case SDM_ERR_SERVICE_UNAVAILABLE: + return FLUF_COAP_CODE_SERVICE_UNAVAILABLE; + default: + return FLUF_COAP_CODE_BAD_REQUEST; + } +} + +static int initialize_fluf_io_ctx(sdm_process_ctx_t *ctx, + sdm_data_model_t *dm, + fluf_data_t *msg, + bool is_bootstrap_server_call) { + size_t res_count = 0; + switch (msg->operation) { + case FLUF_OP_REGISTER: + case FLUF_OP_UPDATE: + fluf_io_register_ctx_init(&ctx->fluf_io.register_ctx); + return 0; + case FLUF_OP_DM_DISCOVER: + if (is_bootstrap_server_call) { + return fluf_io_bootstrap_discover_ctx_init( + &ctx->fluf_io.bootstrap_discover_ctx, &msg->uri); + } else { + uint8_t depth = (uint8_t) msg->attr.discover_attr.depth; + return fluf_io_discover_ctx_init( + &ctx->fluf_io.discover_ctx, &msg->uri, + msg->attr.discover_attr.has_depth ? &depth : NULL); + } + case FLUF_OP_DM_WRITE_REPLACE: + case FLUF_OP_DM_WRITE_PARTIAL_UPDATE: + case FLUF_OP_DM_CREATE: + return fluf_io_in_ctx_init(&ctx->fluf_io.in_ctx, msg->operation, + &msg->uri, msg->content_format); + case FLUF_OP_DM_READ: + if (sdm_get_readable_res_count(dm, &res_count)) { + return -1; + } + return fluf_io_out_ctx_init(&ctx->fluf_io.out_ctx, FLUF_OP_DM_READ, + &msg->uri, res_count, msg->accept); + case FLUF_OP_DM_EXECUTE: + case FLUF_OP_DM_DELETE: + return 0; + default: + break; + } + return -1; +} + +static int process_register(sdm_process_ctx_t *ctx, + sdm_data_model_t *dm, + fluf_data_t *in_out_msg, + char *out_buff, + size_t out_buff_len) { + int ret_sdm = 0; + int ret_fluf = 0; + fluf_uri_path_t path; + const char *version; + size_t offset = 0; + size_t copied_bytes; + + in_out_msg->content_format = FLUF_COAP_FORMAT_LINK_FORMAT; + + while (1) { + if (!ctx->data_to_copy) { + ret_sdm = sdm_get_register_record(dm, &path, &version); + if (ret_sdm && ret_sdm != SDM_LAST_RECORD) { + // no msg_code for register or update + return ret_sdm; + } + ret_fluf = + fluf_io_register_ctx_new_entry(&ctx->fluf_io.register_ctx, + &path, version); + if (ret_fluf) { + sdm_log(ERROR, "fluf_io register ctx error"); + return ret_fluf; + } + } + ret_fluf = fluf_io_register_ctx_get_payload(&ctx->fluf_io.register_ctx, + &out_buff[offset], + out_buff_len - offset, + &copied_bytes); + offset += copied_bytes; + if (!ret_fluf) { + ctx->data_to_copy = false; + } else if (ret_fluf == FLUF_IO_NEED_NEXT_CALL) { + ctx->data_to_copy = true; + in_out_msg->payload = out_buff; + in_out_msg->payload_size = offset; + return SDM_IMPL_BLOCK_TRANSFER_NEEDED; + } else { + sdm_log(ERROR, "fluf_io register ctx error"); + return ret_fluf; + } + if (ret_sdm == SDM_LAST_RECORD) { + in_out_msg->payload = out_buff; + in_out_msg->payload_size = offset; + return 0; + } + } +} + +static int process_discover(sdm_process_ctx_t *ctx, + sdm_data_model_t *dm, + fluf_data_t *in_out_msg, + char *out_buff, + size_t out_buff_len) { + int ret_sdm = 0; + int ret_fluf = 0; + fluf_uri_path_t path; + const char *version; + const uint16_t *dim; + size_t offset = 0; + size_t copied_bytes; + + in_out_msg->content_format = FLUF_COAP_FORMAT_LINK_FORMAT; + in_out_msg->msg_code = FLUF_COAP_CODE_CONTENT; + + while (1) { + if (!ctx->data_to_copy) { + ret_sdm = sdm_get_discover_record(dm, &path, &version, &dim); + if (ret_sdm && ret_sdm != SDM_LAST_RECORD) { + in_out_msg->msg_code = map_sdm_err_to_coap_code(ret_sdm); + return ret_sdm; + } + // TODO: attr not supported yet + ret_fluf = + fluf_io_discover_ctx_new_entry(&ctx->fluf_io.discover_ctx, + &path, NULL, version, dim); + if (ret_fluf == FLUF_IO_WARNING_DEPTH) { + continue; + } else if (ret_fluf) { + sdm_log(ERROR, "fluf_io discover ctx error"); + in_out_msg->msg_code = FLUF_COAP_CODE_BAD_REQUEST; + return ret_fluf; + } + } + ret_fluf = fluf_io_discover_ctx_get_payload(&ctx->fluf_io.discover_ctx, + &out_buff[offset], + out_buff_len - offset, + &copied_bytes); + offset += copied_bytes; + if (!ret_fluf) { + ctx->data_to_copy = false; + } else if (ret_fluf == FLUF_IO_NEED_NEXT_CALL) { + ctx->data_to_copy = true; + in_out_msg->payload = out_buff; + in_out_msg->payload_size = offset; + return SDM_IMPL_BLOCK_TRANSFER_NEEDED; + } else { + sdm_log(ERROR, "fluf_io discover ctx error"); + in_out_msg->msg_code = FLUF_COAP_CODE_BAD_REQUEST; + return ret_fluf; + } + if (ret_sdm == SDM_LAST_RECORD) { + in_out_msg->payload = out_buff; + in_out_msg->payload_size = offset; + return 0; + } + } +} + +static int process_bootstrap_discover(sdm_process_ctx_t *ctx, + sdm_data_model_t *dm, + fluf_data_t *in_out_msg, + char *out_buff, + size_t out_buff_len) { + int ret_sdm = 0; + int ret_fluf = 0; + fluf_uri_path_t path; + const char *version; + const uint16_t *ssid; + const char *uri; + size_t offset = 0; + size_t copied_bytes; + + in_out_msg->content_format = FLUF_COAP_FORMAT_LINK_FORMAT; + in_out_msg->msg_code = FLUF_COAP_CODE_CONTENT; + + while (1) { + if (!ctx->data_to_copy) { + ret_sdm = sdm_get_bootstrap_discover_record(dm, &path, &version, + &ssid, &uri); + if (ret_sdm && ret_sdm != SDM_LAST_RECORD) { + in_out_msg->msg_code = map_sdm_err_to_coap_code(ret_sdm); + return ret_sdm; + } + ret_fluf = fluf_io_bootstrap_discover_ctx_new_entry( + &ctx->fluf_io.bootstrap_discover_ctx, &path, version, ssid, + uri); + if (ret_fluf) { + sdm_log(ERROR, "fluf_io bootstrap discover ctx error"); + in_out_msg->msg_code = FLUF_COAP_CODE_BAD_REQUEST; + return ret_fluf; + } + } + ret_fluf = fluf_io_bootstrap_discover_ctx_get_payload( + &ctx->fluf_io.bootstrap_discover_ctx, + &out_buff[offset], + out_buff_len - offset, + &copied_bytes); + offset += copied_bytes; + if (!ret_fluf) { + ctx->data_to_copy = false; + } else if (ret_fluf == FLUF_IO_NEED_NEXT_CALL) { + ctx->data_to_copy = true; + in_out_msg->payload = out_buff; + in_out_msg->payload_size = offset; + return SDM_IMPL_BLOCK_TRANSFER_NEEDED; + } else { + sdm_log(ERROR, "fluf_io bootstrap discover ctx error"); + in_out_msg->msg_code = FLUF_COAP_CODE_BAD_REQUEST; + return ret_fluf; + } + if (ret_sdm == SDM_LAST_RECORD) { + in_out_msg->payload = out_buff; + in_out_msg->payload_size = offset; + return 0; + } + } +} + +static int process_read(sdm_process_ctx_t *ctx, + sdm_data_model_t *dm, + fluf_data_t *in_out_msg, + char *out_buff, + size_t out_buff_len) { + int ret_sdm = 0; + int ret_fluf = 0; + fluf_io_out_entry_t record; + // HACK: fast drut + memset(&record, 0, sizeof(record)); + size_t offset = 0; + size_t copied_bytes; + + in_out_msg->content_format = + fluf_io_out_ctx_get_format(&ctx->fluf_io.out_ctx); + in_out_msg->msg_code = FLUF_COAP_CODE_CONTENT; + + while (1) { + if (!ctx->data_to_copy) { + ret_sdm = sdm_get_read_entry(dm, &record); + if (ret_sdm && ret_sdm != SDM_LAST_RECORD) { + in_out_msg->msg_code = map_sdm_err_to_coap_code(ret_sdm); + return ret_sdm; + } + ret_fluf = + fluf_io_out_ctx_new_entry(&ctx->fluf_io.out_ctx, &record); + if (ret_fluf) { + sdm_log(ERROR, "fluf_io out ctx error"); + in_out_msg->msg_code = FLUF_COAP_CODE_BAD_REQUEST; + return ret_fluf; + } + } + ret_fluf = fluf_io_out_ctx_get_payload(&ctx->fluf_io.out_ctx, + &out_buff[offset], + out_buff_len - offset, + &copied_bytes); + offset += copied_bytes; + if (!ret_fluf) { + ctx->data_to_copy = false; + } else if (ret_fluf == FLUF_IO_NEED_NEXT_CALL) { + ctx->data_to_copy = true; + in_out_msg->payload = out_buff; + in_out_msg->payload_size = offset; + return SDM_IMPL_BLOCK_TRANSFER_NEEDED; + } else { + sdm_log(ERROR, "fluf_io out ctx error"); + in_out_msg->msg_code = FLUF_COAP_CODE_BAD_REQUEST; + return ret_fluf; + } + if (ret_sdm == SDM_LAST_RECORD) { + in_out_msg->payload = out_buff; + in_out_msg->payload_size = offset; + return 0; + } + } +} + +static int process_execute(sdm_data_model_t *dm, fluf_data_t *in_out_msg) { + in_out_msg->msg_code = FLUF_COAP_CODE_CHANGED; + int ret_val = sdm_execute(dm, (const char *) in_out_msg->payload, + in_out_msg->payload_size); + in_out_msg->payload = NULL; + in_out_msg->payload_size = 0; + if (ret_val) { + in_out_msg->msg_code = map_sdm_err_to_coap_code(ret_val); + } + return ret_val; +} + +static int process_write(sdm_process_ctx_t *ctx, + sdm_data_model_t *dm, + fluf_data_t *in_out_msg) { + int ret_sdm; + int ret_fluf; + fluf_data_type_t type; + const fluf_res_value_t *value; + const fluf_uri_path_t *path; + + in_out_msg->content_format = FLUF_COAP_FORMAT_NOT_DEFINED; + if (in_out_msg->operation == FLUF_OP_DM_CREATE) { + in_out_msg->msg_code = FLUF_COAP_CODE_CREATED; + } else { + in_out_msg->msg_code = FLUF_COAP_CODE_CHANGED; + } + + bool payload_finished = + (in_out_msg->block.block_type == FLUF_OPTION_BLOCK_NOT_DEFINED) + || (in_out_msg->block.more_flag == false); + + ret_fluf = fluf_io_in_ctx_feed_payload(&ctx->fluf_io.in_ctx, + in_out_msg->payload, + in_out_msg->payload_size, + payload_finished); + in_out_msg->payload = NULL; + in_out_msg->payload_size = 0; + + if (ret_fluf) { + sdm_log(ERROR, "fluf_io in ctx error"); + in_out_msg->msg_code = FLUF_COAP_CODE_BAD_REQUEST; + return ret_fluf; + } + + while (1) { + type = FLUF_DATA_TYPE_ANY; + ret_fluf = fluf_io_in_ctx_get_entry(&ctx->fluf_io.in_ctx, &type, &value, + &path); + if (ret_fluf == FLUF_IO_WANT_TYPE_DISAMBIGUATION) { + ret_sdm = sdm_get_resource_type(dm, path, &type); + if (ret_sdm) { + in_out_msg->msg_code = map_sdm_err_to_coap_code(ret_sdm); + return ret_sdm; + } + ret_fluf = fluf_io_in_ctx_get_entry(&ctx->fluf_io.in_ctx, &type, + &value, &path); + } + if (!ret_fluf) { + fluf_io_out_entry_t record = { + .path = *path, + .type = type, + .value = *value + }; + ret_sdm = sdm_write_entry(dm, &record); + if (ret_sdm) { + in_out_msg->msg_code = map_sdm_err_to_coap_code(ret_sdm); + return ret_sdm; + } + } else if (ret_fluf == FLUF_IO_WANT_NEXT_PAYLOAD && !payload_finished) { + return SDM_IMPL_WANT_NEXT_MSG; + } else if (ret_fluf == FLUF_IO_EOF) { + return 0; + } else { + sdm_log(ERROR, "fluf_io in ctx error"); + in_out_msg->msg_code = FLUF_COAP_CODE_BAD_REQUEST; + return ret_fluf; + } + } +} + +static int process_operation(sdm_process_ctx_t *ctx, + sdm_data_model_t *dm, + fluf_data_t *in_out_msg, + bool is_bootstrap_server_call, + char *out_buff, + size_t out_buff_len) { + // No payload in incoming message, prepare for response. + if (in_out_msg->operation != FLUF_OP_DM_WRITE_REPLACE + && in_out_msg->operation != FLUF_OP_DM_WRITE_PARTIAL_UPDATE + && in_out_msg->operation != FLUF_OP_DM_CREATE + && in_out_msg->operation != FLUF_OP_DM_EXECUTE) { + in_out_msg->payload = NULL; + in_out_msg->payload_size = 0; + } + + switch (in_out_msg->operation) { + case FLUF_OP_DM_EXECUTE: + return process_execute(dm, in_out_msg); + case FLUF_OP_DM_DELETE: + in_out_msg->msg_code = FLUF_COAP_CODE_DELETED; + break; + case FLUF_OP_REGISTER: + case FLUF_OP_UPDATE: + return process_register(ctx, dm, in_out_msg, out_buff, out_buff_len); + case FLUF_OP_DM_DISCOVER: + if (is_bootstrap_server_call) { + return process_bootstrap_discover(ctx, dm, in_out_msg, out_buff, + out_buff_len); + } else { + return process_discover(ctx, dm, in_out_msg, out_buff, + out_buff_len); + } + case FLUF_OP_DM_READ: + return process_read(ctx, dm, in_out_msg, out_buff, out_buff_len); + case FLUF_OP_DM_WRITE_REPLACE: + case FLUF_OP_DM_WRITE_PARTIAL_UPDATE: + case FLUF_OP_DM_CREATE: + return process_write(ctx, dm, in_out_msg); + default: + break; + } + + return 0; +} + +static inline void set_response_operation(fluf_data_t *in_out_msg) { + if (in_out_msg->operation != FLUF_OP_REGISTER + && in_out_msg->operation != FLUF_OP_UPDATE) { + in_out_msg->operation = FLUF_OP_RESPONSE; + } +} + +static bool is_block_transfer_allowed(size_t buff_size) { + const size_t allowed_block_sized_values[] = { 16, 32, 64, 128, + 256, 512, 1024 }; + for (uint8_t i = 0; i < sizeof(allowed_block_sized_values); i++) { + if (buff_size == allowed_block_sized_values[i]) { + return true; + } + } + return false; +} + +int sdm_process(sdm_process_ctx_t *ctx, + sdm_data_model_t *dm, + fluf_data_t *in_out_msg, + bool is_bootstrap_server_call, + char *out_buff, + size_t out_buff_len) { + assert(ctx && dm && in_out_msg && out_buff && out_buff_len); + + int ret_val = 0; + fluf_op_t operation = in_out_msg->operation; + + if (!ctx->in_progress) { + ret_val = sdm_operation_begin(dm, operation, is_bootstrap_server_call, + &in_out_msg->uri); + if (ret_val) { + in_out_msg->msg_code = map_sdm_err_to_coap_code(ret_val); + goto finalize; + } + + ret_val = initialize_fluf_io_ctx(ctx, dm, in_out_msg, + is_bootstrap_server_call); + if (ret_val) { + in_out_msg->msg_code = FLUF_COAP_CODE_BAD_REQUEST; + sdm_log(ERROR, "fluf_io ctx initialization failed"); + goto finalize; + } + + ctx->data_to_copy = false; + ctx->in_progress = true; + ctx->block_number = 0; + ctx->op = operation; + } else { + if (ctx->op != operation) { + in_out_msg->msg_code = FLUF_COAP_CODE_BAD_REQUEST; + sdm_log(ERROR, "Incorrect sdm_impl usage"); + ret_val = SDM_ERR_LOGIC; + goto finalize; + } + + if (in_out_msg->block.block_type != FLUF_OPTION_BLOCK_NOT_DEFINED + && ctx->block_number != in_out_msg->block.number) { + sdm_log(ERROR, "Block transfer - packet lost"); + ret_val = SDM_ERR_INPUT_ARG; + in_out_msg->msg_code = FLUF_COAP_CODE_REQUEST_ENTITY_INCOMPLETE; + goto finalize; + } + } + + ret_val = process_operation(ctx, dm, in_out_msg, is_bootstrap_server_call, + out_buff, out_buff_len); + + if (in_out_msg->block.block_type == FLUF_OPTION_BLOCK_2 && !ret_val) { + in_out_msg->block.more_flag = false; + } else if (ret_val == SDM_IMPL_BLOCK_TRANSFER_NEEDED) { + if (!is_block_transfer_allowed(out_buff_len)) { + sdm_log(ERROR, "out_buff_len doesn't allow block transfers"); + ret_val = SDM_ERR_INPUT_ARG; + in_out_msg->msg_code = FLUF_COAP_CODE_BAD_REQUEST; + } + in_out_msg->block.size = (uint32_t) out_buff_len; + in_out_msg->block.block_type = FLUF_OPTION_BLOCK_2; + in_out_msg->block.more_flag = true; + ctx->block_number++; + set_response_operation(in_out_msg); + return ret_val; + } else if (ret_val == SDM_IMPL_WANT_NEXT_MSG) { + ctx->block_number++; + set_response_operation(in_out_msg); + return ret_val; + } + +finalize: + ctx->in_progress = false; + set_response_operation(in_out_msg); + if (ret_val) { + sdm_operation_end(dm); + return ret_val; + } + return sdm_operation_end(dm); +} + +int sdm_process_stop(sdm_process_ctx_t *ctx, sdm_data_model_t *dm) { + if (ctx->in_progress) { + ctx->in_progress = false; + return sdm_operation_end(dm); + } else { + sdm_log(ERROR, "No ongoing operation"); + return -1; + } +} diff --git a/src/anj/sdm/sdm_notification.c b/src/anj/sdm/sdm_notification.c new file mode 100644 index 00000000..e664e143 --- /dev/null +++ b/src/anj/sdm/sdm_notification.c @@ -0,0 +1,400 @@ +/* + * Copyright 2023-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 "sdm_core.h" + +#define NOTIFICATION_RECORDS_COUNT 10 + +typedef struct { + fluf_uri_path_t resource_path; + + fluf_attr_notification_t attributes_observe; + bool attributes_set_by_observe; + // attributes write by Write-Attributes operation + fluf_attr_notification_t attributes_write; + + fluf_io_out_entry_t record; + bool send_notification; + + uint64_t observe_number; + fluf_coap_token_t token; +} notification_records_t; + +static notification_records_t notification_records[NOTIFICATION_RECORDS_COUNT]; + +static notification_records_t *find_notification_or_maybe_return_empty_rec( + const fluf_uri_path_t *resource_path, bool return_empty) { + // try to find record with the same path + for (size_t i = 0; i < NOTIFICATION_RECORDS_COUNT; i++) { + if (fluf_uri_path_equal(resource_path, + ¬ification_records[i].resource_path)) { + return (notification_records_t *) ¬ification_records[i]; + } + } + // find first empty record + if (return_empty) { + for (size_t i = 0; i < NOTIFICATION_RECORDS_COUNT; i++) { + if (fluf_uri_path_length(¬ification_records[i].resource_path) + == 0) { + return (notification_records_t *) ¬ification_records[i]; + } + } + } + return NULL; +} + +static bool is_there_any_attribute(fluf_attr_notification_t *attr) { + return (attr->has_min_period || attr->has_max_period + || attr->has_greater_than || attr->has_less_than || attr->has_step + || attr->has_min_eval_period || attr->has_max_eval_period +#ifdef FLUF_WITH_LWM2M12 + || attr->has_edge || attr->has_con || attr->has_hqmax +#endif // FLUF_WITH_LWM2M12 + ); +} + +static int validate_attributes(fluf_data_t *in_out_msg) { + // for now we only support min_period and max_period + if (in_out_msg->attr.notification_attr.has_greater_than + || in_out_msg->attr.notification_attr.has_less_than + || in_out_msg->attr.notification_attr.has_step + || in_out_msg->attr.notification_attr.has_min_eval_period + || in_out_msg->attr.notification_attr.has_max_eval_period +#ifdef FLUF_WITH_LWM2M12 + || in_out_msg->attr.notification_attr.has_edge + || in_out_msg->attr.notification_attr.has_con + || in_out_msg->attr.notification_attr.has_hqmax +#endif // FLUF_WITH_LWM2M12 + ) { + in_out_msg->msg_code = FLUF_COAP_CODE_METHOD_NOT_ALLOWED; + return SDM_ERR_CURRENTLY_UNSUPPORTED; + } + return 0; +} + +static int handle_observe_operation(fluf_data_t *in_out_msg, + sdm_data_model_t *dm, + notification_records_t *notification_record, + char *out_buff, + size_t out_buff_len) { + int ret_val = 0; + fluf_io_out_ctx_t ctx; + fluf_io_out_entry_t record; + size_t buffer_usage; + + if ((ret_val = validate_attributes(in_out_msg))) { + return ret_val; + } + + if ((ret_val = _sdm_get_resource_value( + dm, &in_out_msg->uri, &record.value, + &record.type))) { // todo maybe change to sdm_operation_begin + in_out_msg->msg_code = FLUF_COAP_CODE_METHOD_NOT_ALLOWED; + return ret_val; + } + + if ((ret_val = fluf_io_out_ctx_init(&ctx, FLUF_OP_INF_OBSERVE, + &in_out_msg->uri, 1, + in_out_msg->content_format))) { + sdm_log(ERROR, "fluf_io ctx initialization failed"); + return ret_val; + } + + record.path = in_out_msg->uri; + record.timestamp = (double) anj_time_now() / 1000; + + if ((ret_val = fluf_io_out_ctx_new_entry(&ctx, &record)) + || (ret_val = fluf_io_out_ctx_get_payload( + &ctx, out_buff, out_buff_len, &buffer_usage))) { + return ret_val; + } + + notification_record->attributes_observe = + in_out_msg->attr.notification_attr; + notification_record->attributes_set_by_observe = + is_there_any_attribute(&in_out_msg->attr.notification_attr); + notification_record->resource_path = in_out_msg->uri; + notification_record->record = record; + notification_record->token = in_out_msg->coap.coap_udp.token; + notification_record->observe_number = + 1; // todo if we don't use FLUF_OP_INF_NON_CON_NOTIFY as a response + // to FLUF_OP_INF_OBSERVE then should be set to 0 + + in_out_msg->payload = (void *) out_buff; + in_out_msg->payload_size = buffer_usage; + in_out_msg->msg_code = FLUF_COAP_CODE_CONTENT; + in_out_msg->content_format = fluf_io_out_ctx_get_format(&ctx); + return ret_val; +} + +static int +handle_cancel_observe_operation(fluf_data_t *in_out_msg, + notification_records_t *notification_record) { + notification_record->resource_path.uri_len = 0; + memset(¬ification_record->attributes_observe, 0, + sizeof(notification_record->attributes_observe)); + notification_record->attributes_set_by_observe = false; + notification_record->send_notification = false; + + in_out_msg->msg_code = FLUF_COAP_CODE_CONTENT; + return 0; +} + +static int +handle_write_attribute_operation(fluf_data_t *in_out_msg, + notification_records_t *notification_record) { + int ret_val = 0; + + if ((ret_val = validate_attributes(in_out_msg))) { + return ret_val; + } + + notification_record->attributes_write = in_out_msg->attr.notification_attr; + + in_out_msg->msg_code = FLUF_COAP_CODE_CHANGED; + return ret_val; +} + +static bool res_values_equal(fluf_res_value_t *left, + fluf_res_value_t *right, + fluf_data_type_t type) { + switch (type) { + case FLUF_DATA_TYPE_NULL: + return true; + case FLUF_DATA_TYPE_BYTES: + case FLUF_DATA_TYPE_STRING: + return !memcmp(left->bytes_or_string.data, right->bytes_or_string.data, + left->bytes_or_string.full_length_hint); + case FLUF_DATA_TYPE_INT: + return left->int_value == right->int_value; + case FLUF_DATA_TYPE_DOUBLE: + return left->double_value == right->double_value; + case FLUF_DATA_TYPE_BOOL: + return left->bool_value == right->bool_value; + case FLUF_DATA_TYPE_OBJLNK: + return left->objlnk.oid == right->objlnk.oid + && left->objlnk.iid == right->objlnk.iid; + case FLUF_DATA_TYPE_UINT: + return left->uint_value == right->uint_value; + case FLUF_DATA_TYPE_TIME: + return left->time_value == right->time_value; + case FLUF_DATA_TYPE_FLAG_EXTERNAL: + // currently not supported + return false; + default: + assert(false); + } + return false; +} + +int sdm_notification(fluf_data_t *in_out_msg, + sdm_data_model_t *dm, + char *out_buff, + size_t out_buff_len) { + assert(dm && in_out_msg); + assert(in_out_msg->operation == FLUF_OP_DM_WRITE_ATTR + || in_out_msg->operation == FLUF_OP_INF_OBSERVE + || in_out_msg->operation == FLUF_OP_INF_CANCEL_OBSERVE); + + notification_records_t *notification_record; + int ret_val = 0; + _sdm_entity_ptrs_t ptrs; + fluf_op_t operation = in_out_msg->operation; + + in_out_msg->msg_code = FLUF_COAP_CODE_INTERNAL_SERVER_ERROR; + + if (in_out_msg->operation != FLUF_OP_INF_OBSERVE) { + in_out_msg->operation = FLUF_OP_RESPONSE; + } else { + in_out_msg->operation = FLUF_OP_INF_NON_CON_NOTIFY; // todo! ugh, hacky + } + + if ((ret_val = _sdm_get_entity_ptrs(dm, &in_out_msg->uri, &ptrs))) { + return ret_val; + } + + // for now we only support notification for resources and we don't support + // external data type + if (!fluf_uri_path_is(&in_out_msg->uri, FLUF_ID_RID) + || _sdm_is_multi_instance_resource(ptrs.res->res_spec->operation) + || ptrs.res->res_spec->type == FLUF_DATA_TYPE_FLAG_EXTERNAL) { + return SDM_ERR_CURRENTLY_UNSUPPORTED; + } + + if (in_out_msg->uri.ids[FLUF_ID_OID] == 0 + || in_out_msg->uri.ids[FLUF_ID_OID] == 21) { + in_out_msg->msg_code = FLUF_COAP_CODE_UNAUTHORIZED; + return SDM_ERR_LOGIC; + } + + if (in_out_msg->operation == FLUF_OP_INF_CANCEL_OBSERVE) { + if (!(notification_record = find_notification_or_maybe_return_empty_rec( + &in_out_msg->uri, false))) { + sdm_log(INFO, "Can't find observation related to given path"); + in_out_msg->msg_code = FLUF_COAP_CODE_NOT_FOUND; + return 0; + } + } else { + if (!(notification_record = find_notification_or_maybe_return_empty_rec( + &in_out_msg->uri, true))) { + sdm_log(ERROR, "No space for new observation"); + return SDM_ERR_MEMORY; + } + } + + switch (operation) { + case FLUF_OP_INF_OBSERVE: + ret_val = handle_observe_operation(in_out_msg, dm, notification_record, + out_buff, out_buff_len); + break; + case FLUF_OP_INF_CANCEL_OBSERVE: + ret_val = handle_cancel_observe_operation(in_out_msg, + notification_record); + break; + case FLUF_OP_DM_WRITE_ATTR: + ret_val = handle_write_attribute_operation(in_out_msg, + notification_record); + break; + default: + ret_val = SDM_ERR_INPUT_ARG; + } + + return ret_val; +} + +static int check_if_notification_should_be_send(sdm_data_model_t *dm) { + int ret_val = 0; + + for (size_t i = 0; i < NOTIFICATION_RECORDS_COUNT; i++) { + if (fluf_uri_path_length(¬ification_records[i].resource_path) != 0) { + int ret; + fluf_attr_notification_t *attributes = + notification_records[i].attributes_set_by_observe + ? ¬ification_records[i].attributes_observe + : ¬ification_records[i].attributes_write; + double elapsed_time = (double) anj_time_now() / 1000 + - notification_records[i].record.timestamp; + fluf_res_value_t value; + fluf_data_type_t type; + + if (attributes->has_min_period + && attributes->min_period > elapsed_time) { + continue; + } + + if (attributes->has_max_period && attributes->max_period != 0 + && attributes->max_period + >= (attributes->has_min_period + ? attributes->min_period + : 0) + && elapsed_time >= attributes->max_period) { + notification_records[i].send_notification = true; + continue; + } + + if ((ret = _sdm_get_resource_value( // todo maybe change to + // sdm_operation_begin + dm, ¬ification_records[i].resource_path, &value, + &type))) { + ret_val = ret; + continue; + } + + assert(type == notification_records[i].record.type); + if (!res_values_equal(¬ification_records[i].record.value, &value, + type)) { + notification_records[i].send_notification = true; + } + } + } + + return ret_val; +} + +static int prepare_notify_message(sdm_data_model_t *dm, + notification_records_t *notification_record, + fluf_data_t *out_msg, + char *out_buff, + size_t out_buff_len, + uint16_t format) { + fluf_io_out_ctx_t out_ctx; + fluf_io_out_entry_t record; + size_t buffer_usage; + int ret_val = 0; + + if ((ret_val = _sdm_get_resource_value( // todo maybe change to + // sdm_operation_begin + dm, ¬ification_record->resource_path, &record.value, + &record.type))) { + return ret_val; + } + + if ((ret_val = fluf_io_out_ctx_init(&out_ctx, FLUF_OP_INF_NON_CON_NOTIFY, + ¬ification_record->resource_path, 1, + format))) { + sdm_log(ERROR, "fluf_io ctx initialization failed"); + return ret_val; + } + + record.timestamp = (double) anj_time_now() / 1000; + record.path = notification_record->resource_path; + + if ((ret_val = fluf_io_out_ctx_new_entry(&out_ctx, &record)) + || (ret_val = fluf_io_out_ctx_get_payload( + &out_ctx, out_buff, out_buff_len, &buffer_usage))) { + return ret_val; + } + + notification_record->record = record; + notification_record->send_notification = false; + + out_msg->operation = FLUF_OP_INF_NON_CON_NOTIFY; + out_msg->content_format = format; + out_msg->observe_number = notification_record->observe_number++; + out_msg->payload = (void *) out_buff; + out_msg->payload_size = buffer_usage; + out_msg->coap.coap_udp.token = notification_record->token; + + return ret_val; +} + +int sdm_notification_process(fluf_data_t *out_msg, + sdm_data_model_t *dm, + char *out_buff, + size_t out_buff_len, + uint16_t format) { + assert(dm && out_msg && out_buff); + int ret_val = 0; + + if ((ret_val = check_if_notification_should_be_send(dm))) { + sdm_log(WARN, "Failed to check all observations"); + } + + for (size_t i = 0; i < NOTIFICATION_RECORDS_COUNT; i++) { + if (notification_records[i].send_notification) { + if ((ret_val = prepare_notify_message(dm, ¬ification_records[i], + out_msg, out_buff, + out_buff_len, format))) { + sdm_log(WARN, "Preparing notify message has failed"); + } else { + // just one notify message per sdm_notification_process call + break; + } + } + } + + return ret_val; +} diff --git a/src/anj/sdm/sdm_read.c b/src/anj/sdm/sdm_read.c new file mode 100644 index 00000000..36545350 --- /dev/null +++ b/src/anj/sdm/sdm_read.c @@ -0,0 +1,429 @@ +/* + * Copyright 2023-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 + +#include "sdm_core.h" + +static bool is_readable_resource(sdm_res_operation_t op, bool is_bootstrap) { + return op == SDM_RES_R || op == SDM_RES_RM || op == SDM_RES_RW + || op == SDM_RES_RWM || (is_bootstrap && op == SDM_RES_BS_RW); +} + +static size_t get_readable_res_count_from_resource(sdm_res_t *res, + bool is_bootstrap) { + if (!is_readable_resource(res->res_spec->operation, is_bootstrap)) { + return 0; + } + if (!_sdm_is_multi_instance_resource(res->res_spec->operation)) { + return 1; + } + return res->value.res_inst.inst_count; +} + +static size_t get_readable_res_count_from_instance(sdm_obj_inst_t *inst, + bool is_bootstrap) { + size_t count = 0; + + for (uint16_t idx = 0; idx < inst->res_count; idx++) { + count += get_readable_res_count_from_resource(&inst->resources[idx], + is_bootstrap); + } + return count; +} + +static size_t get_readable_res_count_from_object(sdm_obj_t *obj, + bool is_bootstrap) { + size_t count = 0; + + for (uint16_t idx = 0; idx < obj->inst_count; idx++) { + count += get_readable_res_count_from_instance(obj->insts[idx], + is_bootstrap); + } + return count; +} + +static int get_readable_res_count_and_set_start_level(sdm_data_model_t *dm) { + _sdm_read_ctx_t *read_ctx = &dm->op_ctx.read_ctx; + _sdm_entity_ptrs_t *entity_ptrs = &dm->entity_ptrs; + if (entity_ptrs->res_inst) { + read_ctx->level = FLUF_ID_RIID; + read_ctx->total_op_count = + is_readable_resource(entity_ptrs->res->res_spec->operation, + dm->boostrap_operation) + ? 1 + : 0; + } else if (entity_ptrs->res) { + read_ctx->level = FLUF_ID_RID; + read_ctx->total_op_count = + get_readable_res_count_from_resource(entity_ptrs->res, + dm->boostrap_operation); + } else if (entity_ptrs->inst) { + read_ctx->level = FLUF_ID_IID; + read_ctx->total_op_count = + get_readable_res_count_from_instance(entity_ptrs->inst, + dm->boostrap_operation); + } else { + read_ctx->level = FLUF_ID_OID; + read_ctx->total_op_count = + get_readable_res_count_from_object(entity_ptrs->obj, + dm->boostrap_operation); + } + if (!read_ctx->total_op_count) { + sdm_log(ERROR, "No readable resources"); + return SDM_ERR_NOT_FOUND; + } + + dm->op_count = read_ctx->total_op_count; + return 0; +} + +static int get_read_value(fluf_io_out_entry_t *out_record, + sdm_obj_t *obj, + sdm_obj_inst_t *obj_inst, + sdm_res_t *res, + sdm_res_inst_t *res_inst) { + out_record->type = res->res_spec->type; + out_record->path = + res_inst ? FLUF_MAKE_RESOURCE_INSTANCE_PATH(obj->oid, obj_inst->iid, + res->res_spec->rid, + res_inst->riid) + : FLUF_MAKE_RESOURCE_PATH(obj->oid, obj_inst->iid, + res->res_spec->rid); + if (res->res_handlers && res->res_handlers->res_read) { + return res->res_handlers->res_read(obj, obj_inst, res, res_inst, + &out_record->value); + } + out_record->value = + res_inst ? res_inst->res_value.value : res->value.res_value.value; + return 0; +} + +static void increment_idx_starting_from_res(_sdm_read_ctx_t *read_ctx, + uint16_t res_count) { + read_ctx->res_idx++; + if (read_ctx->res_idx == res_count) { + read_ctx->res_idx = 0; + read_ctx->inst_idx++; + } +} + +static void increment_idx_starting_from_res_inst(_sdm_read_ctx_t *read_ctx, + uint16_t res_count, + uint16_t res_inst_count) { + read_ctx->res_inst_idx++; + if (read_ctx->res_inst_idx == res_inst_count) { + read_ctx->res_inst_idx = 0; + increment_idx_starting_from_res(read_ctx, res_count); + } +} + +static void get_readable_resource(sdm_data_model_t *dm) { + _sdm_read_ctx_t *read_ctx = &dm->op_ctx.read_ctx; + _sdm_entity_ptrs_t *entity_ptrs = &dm->entity_ptrs; + sdm_obj_t *obj = entity_ptrs->obj; + sdm_res_t *res; + bool found = false; + while (!found) { + if (read_ctx->level == FLUF_ID_OID) { + assert(read_ctx->inst_idx < obj->inst_count); + entity_ptrs->inst = obj->insts[read_ctx->inst_idx]; + } + assert(read_ctx->res_idx < entity_ptrs->inst->res_count); + res = &entity_ptrs->inst->resources[read_ctx->res_idx]; + if (is_readable_resource(res->res_spec->operation, + dm->boostrap_operation)) { + if (_sdm_is_multi_instance_resource(res->res_spec->operation) + && res->value.res_inst.inst_count) { + assert(read_ctx->res_inst_idx < res->value.res_inst.inst_count); + entity_ptrs->res_inst = + res->value.res_inst.insts[read_ctx->res_inst_idx]; + increment_idx_starting_from_res_inst( + read_ctx, entity_ptrs->inst->res_count, + res->value.res_inst.inst_count); + found = true; + } else { + if (!_sdm_is_multi_instance_resource( + res->res_spec->operation)) { + found = true; + entity_ptrs->res_inst = NULL; + } + increment_idx_starting_from_res(read_ctx, + entity_ptrs->inst->res_count); + } + } else { + increment_idx_starting_from_res(read_ctx, + entity_ptrs->inst->res_count); + } + } + entity_ptrs->res = res; +} + +int sdm_get_read_entry(sdm_data_model_t *dm, fluf_io_out_entry_t *out_record) { + assert(dm && out_record); + _sdm_read_ctx_t *read_ctx = &dm->op_ctx.read_ctx; + _sdm_entity_ptrs_t *entity_ptrs = &dm->entity_ptrs; + + if (dm->operation != FLUF_OP_DM_READ) { + sdm_log(ERROR, "Incorrect operation"); + dm->result = SDM_ERR_LOGIC; + return dm->result; + } + _SDM_ONGOING_OP_ERROR_CHECK(dm); + _SDM_ONGOING_OP_COUNT_ERROR_CHECK(dm); + + if (read_ctx->level == FLUF_ID_OID || read_ctx->level == FLUF_ID_IID) { + get_readable_resource(dm); + } + // there is nothing to do on FLUF_ID_RIID level + if (read_ctx->level == FLUF_ID_RID) { + if (_sdm_is_multi_instance_resource( + entity_ptrs->res->res_spec->operation)) { + assert(read_ctx->res_inst_idx + < entity_ptrs->res->value.res_inst.inst_count); + entity_ptrs->res_inst = entity_ptrs->res->value.res_inst + .insts[read_ctx->res_inst_idx++]; + } + // there is nothing to do on FLUF_ID_RID level for single-instance case + } + + dm->result = get_read_value(out_record, entity_ptrs->obj, entity_ptrs->inst, + entity_ptrs->res, entity_ptrs->res_inst); + if (dm->result) { + return dm->result; + } + + dm->op_count--; + return dm->op_count > 0 ? 0 : SDM_LAST_RECORD; +} + +int sdm_get_readable_res_count(sdm_data_model_t *dm, size_t *out_res_count) { + assert(dm && out_res_count); + if (dm->operation != FLUF_OP_DM_READ) { + sdm_log(ERROR, "Incorrect operation"); + dm->result = SDM_ERR_LOGIC; + return dm->result; + } + _SDM_ONGOING_OP_ERROR_CHECK(dm); + *out_res_count = dm->op_ctx.read_ctx.total_op_count; + return 0; +} + +int sdm_get_composite_readable_res_count(sdm_data_model_t *dm, + const fluf_uri_path_t *path, + size_t *out_res_count) { + assert(dm && path && out_res_count); + if (dm->operation != FLUF_OP_DM_READ_COMP) { + dm->result = SDM_ERR_LOGIC; + return dm->result; + } + _SDM_ONGOING_OP_ERROR_CHECK(dm); + + _sdm_entity_ptrs_t ptrs; + sdm_obj_t *obj; + dm->result = + _sdm_get_obj_ptr_call_operation_begin(dm, path->ids[FLUF_ID_OID], + &obj); + if (dm->result) { + return dm->result; + } + dm->result = _sdm_get_obj_ptrs(obj, path, &ptrs); + if (dm->result) { + return dm->result; + } + + size_t count = 0; + + if (ptrs.res_inst) { + count = is_readable_resource(ptrs.res->res_spec->operation, false) ? 1 + : 0; + } else if (ptrs.res) { + count = get_readable_res_count_from_resource(ptrs.res, false); + } else if (ptrs.inst) { + count = get_readable_res_count_from_instance(ptrs.inst, false); + } else { + count = get_readable_res_count_from_object(ptrs.obj, false); + } + + *out_res_count = count; + return 0; +} + +int sdm_get_composite_read_entry(sdm_data_model_t *dm, + const fluf_uri_path_t *path, + fluf_io_out_entry_t *out_record) { + assert(dm && out_record && path); + + if (dm->operation != FLUF_OP_DM_READ_COMP) { + sdm_log(ERROR, "Incorrect operation"); + dm->result = SDM_ERR_LOGIC; + return dm->result; + } + _SDM_ONGOING_OP_ERROR_CHECK(dm); + + _sdm_read_ctx_t *read_ctx = &dm->op_ctx.read_ctx; + _sdm_entity_ptrs_t *entity_ptrs = &dm->entity_ptrs; + // new path + if (!fluf_uri_path_equal(path, &read_ctx->path) && dm->op_count == 0) { + read_ctx->path = *path; + + sdm_obj_t *obj; + dm->result = _sdm_get_obj_ptr_call_operation_begin( + dm, path->ids[FLUF_ID_OID], &obj); + if (dm->result) { + return dm->result; + } + dm->result = _sdm_get_obj_ptrs(obj, path, &dm->entity_ptrs); + if (dm->result) { + return dm->result; + } + + dm->result = get_readable_res_count_and_set_start_level(dm); + if (dm->result) { + return dm->result; + } + read_ctx->inst_idx = 0; + read_ctx->res_idx = 0; + read_ctx->res_inst_idx = 0; + } + + _SDM_ONGOING_OP_COUNT_ERROR_CHECK(dm); + + if (read_ctx->level == FLUF_ID_OID || read_ctx->level == FLUF_ID_IID) { + get_readable_resource(dm); + } + // there is nothing to do on FLUF_ID_RIID level + if (read_ctx->level == FLUF_ID_RID) { + if (_sdm_is_multi_instance_resource( + entity_ptrs->res->res_spec->operation)) { + assert(read_ctx->res_inst_idx + < entity_ptrs->res->value.res_inst.inst_count); + entity_ptrs->res_inst = entity_ptrs->res->value.res_inst + .insts[read_ctx->res_inst_idx++]; + } + // there is nothing to do on FLUF_ID_RID level for single-instance case + } + + dm->result = get_read_value(out_record, entity_ptrs->obj, entity_ptrs->inst, + entity_ptrs->res, entity_ptrs->res_inst); + if (dm->result) { + return dm->result; + } + + dm->op_count--; + return dm->op_count > 0 ? 0 : SDM_LAST_RECORD; +} + +int _sdm_get_resource_value(sdm_data_model_t *dm, + const fluf_uri_path_t *path, + fluf_res_value_t *out_value, + fluf_data_type_t *out_type) { + assert(dm && path && out_value); + if (!fluf_uri_path_has(path, FLUF_ID_RID)) { + goto path_error; + } + _sdm_entity_ptrs_t ptrs; + int ret = _sdm_get_entity_ptrs(dm, path, &ptrs); + if (ret) { + return ret; + } + if (!is_readable_resource(ptrs.res->res_spec->operation, true)) { + goto path_error; + } + + bool is_multi_instance = + _sdm_is_multi_instance_resource(ptrs.res->res_spec->operation); + if (is_multi_instance != fluf_uri_path_has(path, FLUF_ID_RIID)) { + goto path_error; + } + + if (out_type) { + *out_type = ptrs.res->res_spec->type; + } + + if (ptrs.res->res_handlers && ptrs.res->res_handlers->res_read) { + return ptrs.res->res_handlers->res_read(ptrs.obj, ptrs.inst, ptrs.res, + ptrs.res_inst, out_value); + } + *out_value = is_multi_instance ? ptrs.res_inst->res_value.value + : ptrs.res->value.res_value.value; + + return 0; + +path_error: + sdm_log(ERROR, "Incorect path"); + return SDM_ERR_NOT_FOUND; +} + +int sdm_get_resource_type(sdm_data_model_t *dm, + const fluf_uri_path_t *path, + fluf_data_type_t *out_type) { + assert(dm && path && out_type); + if (!fluf_uri_path_has(path, FLUF_ID_RID)) { + sdm_log(ERROR, "Incorect path"); + return SDM_ERR_INPUT_ARG; + } + _sdm_entity_ptrs_t ptrs; + int ret = _sdm_get_entity_ptrs(dm, path, &ptrs); + if (ret) { + return ret; + } + *out_type = ptrs.res->res_spec->type; + return 0; +} + +int _sdm_begin_read_op(sdm_data_model_t *dm, const fluf_uri_path_t *base_path) { + assert(dm && base_path && fluf_uri_path_has(base_path, FLUF_ID_OID)); + + if (dm->boostrap_operation) { + if (base_path->ids[FLUF_ID_OID] != FLUF_OBJ_ID_SERVER + && base_path->ids[FLUF_ID_OID] != FLUF_OBJ_ID_ACCESS_CONTROL) { + sdm_log(ERROR, "Bootstrap server can't access this object"); + dm->result = SDM_ERR_METHOD_NOT_ALLOWED; + return dm->result; + } + if (fluf_uri_path_has(base_path, FLUF_ID_RID)) { + sdm_log(ERROR, "Bootstrap read can't target resource"); + dm->result = SDM_ERR_METHOD_NOT_ALLOWED; + return dm->result; + } + } + + sdm_obj_t *obj; + dm->result = _sdm_get_obj_ptr_call_operation_begin( + dm, base_path->ids[FLUF_ID_OID], &obj); + if (dm->result) { + return dm->result; + } + dm->result = _sdm_get_obj_ptrs(obj, base_path, &dm->entity_ptrs); + if (dm->result) { + return dm->result; + } + dm->result = get_readable_res_count_and_set_start_level(dm); + if (dm->result) { + return dm->result; + } + + _sdm_read_ctx_t *read_ctx = &dm->op_ctx.read_ctx; + read_ctx->inst_idx = 0; + read_ctx->res_idx = 0; + read_ctx->res_inst_idx = 0; + return 0; +} diff --git a/src/anj/sdm/sdm_register.c b/src/anj/sdm/sdm_register.c new file mode 100644 index 00000000..765ba148 --- /dev/null +++ b/src/anj/sdm/sdm_register.c @@ -0,0 +1,100 @@ +/* + * Copyright 2023-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 + +#include "sdm_core.h" + +int _sdm_begin_register_op(sdm_data_model_t *dm) { + assert(dm); + + _sdm_reg_ctx_t *reg_ctx = &dm->op_ctx.reg_ctx; + dm->op_count = 0; + for (uint16_t idx = 0; idx < dm->objs_count; idx++) { + sdm_obj_t *obj = dm->objs[idx]; + if (obj->oid != FLUF_OBJ_ID_SECURITY + && obj->oid != FLUF_OBJ_ID_OSCORE) { + obj->in_transaction = true; + if (obj->obj_handlers && obj->obj_handlers->operation_begin) { + dm->result = + obj->obj_handlers->operation_begin(obj, dm->operation); + if (dm->result) { + return dm->result; + } + } + dm->op_count = dm->op_count + 1 + obj->inst_count; + } + } + reg_ctx->level = FLUF_ID_OID; + reg_ctx->obj_idx = 0; + reg_ctx->inst_idx = 0; + return 0; +} + +int sdm_get_register_record(sdm_data_model_t *dm, + fluf_uri_path_t *out_path, + const char **out_version) { + assert(dm && out_path && out_version); + + _sdm_reg_ctx_t *reg_ctx = &dm->op_ctx.reg_ctx; + assert(reg_ctx->obj_idx < dm->objs_count); + + if (dm->operation != FLUF_OP_REGISTER && dm->operation != FLUF_OP_UPDATE) { + sdm_log(ERROR, "Incorrect operation"); + dm->result = SDM_ERR_LOGIC; + return dm->result; + } + + _SDM_ONGOING_OP_ERROR_CHECK(dm); + _SDM_ONGOING_OP_COUNT_ERROR_CHECK(dm); + + if (reg_ctx->level == FLUF_ID_OID) { + if (dm->objs[reg_ctx->obj_idx]->oid == FLUF_OBJ_ID_SECURITY + || dm->objs[reg_ctx->obj_idx]->oid == FLUF_OBJ_ID_OSCORE) { + reg_ctx->obj_idx++; + } + + sdm_obj_t *obj = dm->objs[reg_ctx->obj_idx]; + *out_path = FLUF_MAKE_OBJECT_PATH(obj->oid); + *out_version = obj->version; + + if (!obj->inst_count) { + reg_ctx->obj_idx++; + } else { + reg_ctx->level = FLUF_ID_IID; + reg_ctx->inst_idx = 0; + } + } else { + sdm_obj_t *obj = dm->objs[reg_ctx->obj_idx]; + assert(reg_ctx->inst_idx < obj->inst_count); + + *out_path = FLUF_MAKE_INSTANCE_PATH(obj->oid, + obj->insts[reg_ctx->inst_idx]->iid); + *out_version = NULL; + reg_ctx->inst_idx++; + if (reg_ctx->inst_idx == obj->inst_count) { + reg_ctx->level = FLUF_ID_OID; + reg_ctx->obj_idx++; + } + } + + dm->op_count--; + return dm->op_count > 0 ? 0 : SDM_LAST_RECORD; +} diff --git a/src/anj/sdm/sdm_send.c b/src/anj/sdm/sdm_send.c new file mode 100644 index 00000000..f2b9aa2c --- /dev/null +++ b/src/anj/sdm/sdm_send.c @@ -0,0 +1,150 @@ +/* + * Copyright 2023-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 + +static int get_item_count(sdm_data_model_t *dm, + const fluf_uri_path_t *paths, + const size_t path_cnt, + size_t *item_cnt) { + assert(item_cnt); + + *item_cnt = 0; + + for (size_t i = 0; i < path_cnt; i++) { + size_t res_count; + int ret = + sdm_get_composite_readable_res_count(dm, &paths[i], &res_count); + if (!ret) { + *item_cnt += res_count; + } else { + return ret; + } + } + + return 0; +} + +int sdm_send_create_msg_from_dm(sdm_data_model_t *dm, + uint16_t format, + uint8_t *out_buff, + size_t *inout_size, + const fluf_uri_path_t *paths, + const size_t path_cnt) { + assert(dm && out_buff && paths && inout_size && path_cnt); + assert(format == FLUF_COAP_FORMAT_SENML_CBOR); + for (size_t i = 0; i < path_cnt; i++) { + assert(fluf_uri_path_has(&paths[i], FLUF_ID_RID)); + } + + int ret; + size_t in_size = *inout_size; + fluf_io_out_ctx_t out_ctx; + double timestamp = (double) anj_time_now() / 1000; + + *inout_size = 0; + + if ((ret = sdm_operation_begin(dm, FLUF_OP_DM_READ_COMP, false, NULL))) { + return ret; + } + + size_t item_cnt; + if (get_item_count(dm, paths, path_cnt, &item_cnt)) { + sdm_operation_end(dm); + return SDM_ERR_INPUT_ARG; + } + + if (fluf_io_out_ctx_init(&out_ctx, FLUF_OP_INF_SEND, NULL, item_cnt, + format)) { + sdm_operation_end(dm); + return SDM_ERR_INPUT_ARG; + } + + for (size_t i = 0; i < path_cnt; i++) { + int read_entry_ret; + + do { + fluf_io_out_entry_t record = { 0 }; + size_t record_len; + + read_entry_ret = + sdm_get_composite_read_entry(dm, &paths[i], &record); + if (read_entry_ret && read_entry_ret != SDM_LAST_RECORD) { + sdm_operation_end(dm); + return read_entry_ret; + } + record.timestamp = timestamp; + + if ((ret = fluf_io_out_ctx_new_entry(&out_ctx, &record)) + || (ret = fluf_io_out_ctx_get_payload( + &out_ctx, out_buff + *inout_size, + in_size - *inout_size, &record_len))) { + sdm_operation_end(dm); + if (ret == FLUF_IO_NEED_NEXT_CALL) { + return SDM_ERR_MEMORY; + } else { + return SDM_ERR_INPUT_ARG; + } + } + + *inout_size += record_len; + } while (read_entry_ret != SDM_LAST_RECORD); + } + sdm_operation_end(dm); + + return 0; +} + +int sdm_send_create_msg_from_list_of_records(uint16_t format, + uint8_t *out_buff, + size_t *inout_size, + const fluf_io_out_entry_t *records, + const size_t record_cnt) { + assert(out_buff && inout_size && records && record_cnt); + assert(format == FLUF_COAP_FORMAT_SENML_CBOR); + for (size_t i = 0; i < record_cnt; i++) { + assert(fluf_uri_path_has(&records[i].path, FLUF_ID_RID)); + } + + size_t in_size = *inout_size; + *inout_size = 0; + + fluf_io_out_ctx_t out_ctx; + + if (fluf_io_out_ctx_init(&out_ctx, FLUF_OP_INF_SEND, NULL, record_cnt, + format)) { + return SDM_ERR_INPUT_ARG; + } + + for (size_t i = 0; i < record_cnt; i++) { + size_t record_len; + int ret; + + if ((ret = fluf_io_out_ctx_new_entry(&out_ctx, &records[i])) + || (ret = fluf_io_out_ctx_get_payload( + &out_ctx, out_buff + *inout_size, + in_size - *inout_size, &record_len))) { + if (ret == FLUF_IO_NEED_NEXT_CALL) { + return SDM_ERR_MEMORY; + } else { + return SDM_ERR_INPUT_ARG; + } + } + *inout_size += record_len; + } + + return 0; +} diff --git a/src/anj/sdm/sdm_write.c b/src/anj/sdm/sdm_write.c new file mode 100644 index 00000000..8a204b18 --- /dev/null +++ b/src/anj/sdm/sdm_write.c @@ -0,0 +1,309 @@ +/* + * Copyright 2023-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 + +#include "sdm_core.h" + +static int update_res_val(sdm_data_model_t *dm, fluf_res_value_t *value) { + _sdm_entity_ptrs_t *entity_ptrs = &dm->entity_ptrs; + if (entity_ptrs->res->res_handlers + && entity_ptrs->res->res_handlers->res_write) { + return entity_ptrs->res->res_handlers->res_write(entity_ptrs->obj, + entity_ptrs->inst, + entity_ptrs->res, + entity_ptrs->res_inst, + value); + } + + sdm_res_value_t *res_val; + if (_sdm_is_multi_instance_resource( + entity_ptrs->res->res_spec->operation)) { + res_val = &entity_ptrs->res_inst->res_value; + } else { + res_val = &entity_ptrs->res->value.res_value; + } + + switch (entity_ptrs->res->res_spec->type) { + case FLUF_DATA_TYPE_INT: + case FLUF_DATA_TYPE_DOUBLE: + case FLUF_DATA_TYPE_BOOL: + case FLUF_DATA_TYPE_OBJLNK: + case FLUF_DATA_TYPE_UINT: + case FLUF_DATA_TYPE_TIME: + res_val->value = *value; + break; + case FLUF_DATA_TYPE_STRING: + case FLUF_DATA_TYPE_BYTES: + // check if this is last chunk + if (value->bytes_or_string.chunk_length + value->bytes_or_string.offset + == value->bytes_or_string.full_length_hint) { + res_val->value.bytes_or_string.full_length_hint = + value->bytes_or_string.full_length_hint; + res_val->value.bytes_or_string.chunk_length = + value->bytes_or_string.full_length_hint; + } + if (value->bytes_or_string.chunk_length + value->bytes_or_string.offset + > res_val->resource_buffer_size) { + sdm_log(ERROR, "Resource buffer to small"); + return SDM_ERR_MEMORY; + } + char *write_ptr = (char *) res_val->value.bytes_or_string.data; + memcpy(&write_ptr[value->bytes_or_string.offset], + value->bytes_or_string.data, + value->bytes_or_string.chunk_length); + break; + default: + break; + } + + return 0; +} + +static int resource_type_check(sdm_data_model_t *dm, + fluf_io_out_entry_t *record) { + _sdm_entity_ptrs_t *entity_ptrs = &dm->entity_ptrs; + if (entity_ptrs->res->res_spec->type != record->type) { + // Bootstrap Server can try to modify + // FLUF_DATA_TYPE_EXTERNAL_STRING/BYTES. + if (dm->boostrap_operation + && ((record->type == FLUF_DATA_TYPE_STRING + && entity_ptrs->res->res_spec->type + == FLUF_DATA_TYPE_EXTERNAL_STRING) + || (record->type == FLUF_DATA_TYPE_BYTES + && entity_ptrs->res->res_spec->type + == FLUF_DATA_TYPE_EXTERNAL_BYTES))) { + return 0; + } + return SDM_ERR_BAD_REQUEST; + } + return 0; +} + +static bool is_writable_resource(sdm_res_operation_t op, bool is_bootstrap) { + return op == SDM_RES_W || op == SDM_RES_RW || op == SDM_RES_WM + || op == SDM_RES_RWM || (is_bootstrap && op == SDM_RES_BS_RW); +} + +static int begin_write_replace_operation(sdm_data_model_t *dm) { + fluf_uri_path_t *path = &dm->op_ctx.write_ctx.path; + sdm_obj_t *obj; + dm->result = _sdm_get_obj_ptr_call_operation_begin( + dm, path->ids[FLUF_ID_OID], &obj); + if (dm->result) { + return dm->result; + } + dm->result = _sdm_get_obj_ptrs(obj, path, &dm->entity_ptrs); + if (dm->result) { + return dm->result; + } + + _sdm_entity_ptrs_t *entity_ptrs = &dm->entity_ptrs; + sdm_res_t *res = entity_ptrs->res; + if (fluf_uri_path_is(path, FLUF_ID_IID)) { + if (!obj->obj_handlers || !obj->obj_handlers->inst_reset) { + sdm_log(ERROR, "inst_reset handler not defined"); + dm->result = SDM_ERR_INTERNAL; + return dm->result; + } + dm->result = obj->obj_handlers->inst_reset(obj, entity_ptrs->inst); + if (dm->result) { + sdm_log(ERROR, "inst_reset failed"); + return dm->result; + } + } else if (fluf_uri_path_is(path, FLUF_ID_RID) + && _sdm_is_multi_instance_resource(res->res_spec->operation)) { + // remove all res_insts + while (res->value.res_inst.inst_count) { + entity_ptrs->res_inst = + res->value.res_inst + .insts[res->value.res_inst.inst_count - 1]; + dm->result = _sdm_delete_res_instance(dm); + if (dm->result) { + return dm->result; + } + } + } + + return 0; +} + +static int handle_res_instances(sdm_data_model_t *dm, + fluf_io_out_entry_t *record) { + _sdm_entity_ptrs_t *entity_ptrs = &dm->entity_ptrs; + sdm_res_t *res = entity_ptrs->res; + + // found res_inst or create new + for (uint16_t idx = 0; idx < res->value.res_inst.inst_count; idx++) { + if (res->value.res_inst.insts[idx]->riid + == record->path.ids[FLUF_ID_RIID]) { + entity_ptrs->res_inst = res->value.res_inst.insts[idx]; + return 0; + } + } + + if (res->value.res_inst.inst_count == res->value.res_inst.max_inst_count) { + sdm_log(ERROR, "No space for new resource instance"); + return SDM_ERR_MEMORY; + } + if (!res->res_handlers || !res->res_handlers->res_inst_create) { + sdm_log(ERROR, "res_inst_create handler not defined"); + return SDM_ERR_INTERNAL; + } + + entity_ptrs->res_inst = NULL; + int ret = entity_ptrs->res->res_handlers->res_inst_create( + entity_ptrs->obj, + entity_ptrs->inst, + res, + &entity_ptrs->res_inst, + record->path.ids[FLUF_ID_RIID]); + if (ret || !entity_ptrs->res_inst) { + sdm_log(ERROR, "res_inst_create failed"); + if (!ret) { + ret = SDM_ERR_INTERNAL; + } + sdm_log(ERROR, "res_inst_create failed"); + return ret; + } + entity_ptrs->res_inst->riid = record->path.ids[FLUF_ID_RIID]; + // udpate res_inst array + uint16_t idx_to_write = UINT16_MAX; + for (uint16_t idx = 0; idx < res->value.res_inst.inst_count; idx++) { + if (res->value.res_inst.insts[idx]->riid + > record->path.ids[FLUF_ID_RIID]) { + idx_to_write = idx; + break; + } + } + if (idx_to_write == UINT16_MAX) { + res->value.res_inst.insts[res->value.res_inst.inst_count] = + entity_ptrs->res_inst; + } else { + for (uint16_t idx = res->value.res_inst.inst_count; idx > idx_to_write; + idx--) { + res->value.res_inst.insts[idx] = res->value.res_inst.insts[idx - 1]; + } + res->value.res_inst.insts[idx_to_write] = entity_ptrs->res_inst; + } + res->value.res_inst.inst_count++; + return 0; +} + +static int verify_resource_before_writing(sdm_data_model_t *dm, + fluf_io_out_entry_t *record) { + if (!is_writable_resource(dm->entity_ptrs.res->res_spec->operation, + dm->boostrap_operation)) { + sdm_log(ERROR, "Resource is not writable"); + return SDM_ERR_BAD_REQUEST; + } else if (resource_type_check(dm, record)) { + sdm_log(ERROR, "Invalid record type"); + return SDM_ERR_BAD_REQUEST; + } else if (_sdm_is_multi_instance_resource( + dm->entity_ptrs.res->res_spec->operation) + != fluf_uri_path_has(&record->path, FLUF_ID_RIID)) { + sdm_log(ERROR, "Writing to invalid path"); + return SDM_ERR_METHOD_NOT_ALLOWED; + } + return 0; +} + +int sdm_write_entry(sdm_data_model_t *dm, fluf_io_out_entry_t *record) { + assert(dm && record); + + if (dm->operation != FLUF_OP_DM_CREATE + && dm->operation != FLUF_OP_DM_WRITE_REPLACE + && dm->operation != FLUF_OP_DM_WRITE_PARTIAL_UPDATE) { + sdm_log(ERROR, "Incorrect operation"); + dm->result = SDM_ERR_LOGIC; + return dm->result; + } + _SDM_ONGOING_OP_ERROR_CHECK(dm); + + if (!fluf_uri_path_has(&record->path, FLUF_ID_RID)) { + sdm_log(ERROR, "Invalid path"); + dm->result = SDM_ERR_BAD_REQUEST; + return dm->result; + } + if (fluf_uri_path_outside_base(&record->path, &dm->op_ctx.write_ctx.path)) { + sdm_log(ERROR, "Write record outside of request path"); + dm->result = SDM_ERR_BAD_REQUEST; + return dm->result; + } + + if (dm->operation == FLUF_OP_DM_CREATE + && !dm->op_ctx.write_ctx.instance_created) { + dm->op_ctx.write_ctx.instance_created = true; + // HACK: We can create instance now because we didn't know its ID + // before. + dm->result = + _sdm_create_object_instance(dm, record->path.ids[FLUF_ID_IID]); + if (dm->result) { + return dm->result; + } + } + + // lack of resource instance is not the error + dm->result = _sdm_get_obj_ptrs( + dm->entity_ptrs.obj, + &FLUF_MAKE_RESOURCE_PATH(record->path.ids[FLUF_ID_OID], + record->path.ids[FLUF_ID_IID], + record->path.ids[FLUF_ID_RID]), + &dm->entity_ptrs); + if (dm->result) { + return dm->result; + } + + dm->result = verify_resource_before_writing(dm, record); + if (dm->result) { + return dm->result; + } + + if (_sdm_is_multi_instance_resource( + dm->entity_ptrs.res->res_spec->operation)) { + dm->result = handle_res_instances(dm, record); + if (dm->result) { + return dm->result; + } + } + + dm->result = update_res_val(dm, &record->value); + return dm->result; +} + +int _sdm_begin_write_op(sdm_data_model_t *dm, + const fluf_uri_path_t *base_path) { + assert(dm && base_path && fluf_uri_path_has(base_path, FLUF_ID_IID)); + dm->is_transactional = true; + dm->op_ctx.write_ctx.path = *base_path; + + if (dm->operation == FLUF_OP_DM_WRITE_REPLACE) { + return begin_write_replace_operation(dm); + } else { + sdm_obj_t *obj; + dm->result = _sdm_get_obj_ptr_call_operation_begin( + dm, base_path->ids[FLUF_ID_OID], &obj); + if (dm->result) { + return dm->result; + } + dm->result = _sdm_get_obj_ptrs(obj, base_path, &dm->entity_ptrs); + return dm->result; + } +} diff --git a/src/anjay_lite/anjay_lite.c b/src/anjay_lite/anjay_lite.c new file mode 100644 index 00000000..5d28b349 --- /dev/null +++ b/src/anjay_lite/anjay_lite.c @@ -0,0 +1,55 @@ +/* + * Copyright 2023-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 "anjay_lite_objs.h" +#include "anjay_lite_register.h" +#include "anjay_lite_servers.h" + +int anjay_lite_init(anjay_lite_t *anjay_lite) { + fluf_init(0xffff); + + sdm_obj_t *obj = + anjay_lite_server_obj_setup(anjay_lite->server_conf.ssid, + anjay_lite->server_conf.lifetime, + anjay_lite->server_conf.binding); + if (!obj || sdm_add_obj(&anjay_lite->dm, obj)) { + return -1; + } + obj = anjay_lite_security_obj_setup(anjay_lite->server_conf.ssid, + anjay_lite->server_conf.hostname, + anjay_lite->server_conf.security_mode); + if (!obj || sdm_add_obj(&anjay_lite->dm, obj)) { + return -1; + } + + anjay_lite_conn_conf_t server_conf; + server_conf.udp.hostname = anjay_lite->server_conf.hostname; + server_conf.udp.port = (int) anjay_lite->server_conf.port; + server_conf.udp.version = ANJAY_NET_IP_VER_V4; + + return anjay_lite_register_add_server(&server_conf, + anjay_lite->server_conf.binding, + anjay_lite->endpoint_name, + anjay_lite->server_conf.lifetime); +} + +void anjay_lite_process(anjay_lite_t *anjay_lite) { + anjay_lite_servers_process(anjay_lite); + anjay_lite_register_process(anjay_lite); +} + +void anjay_lite_send(uint8_t *payload, size_t size) { + anjay_lite_send_process(payload, size); +} diff --git a/src/anjay_lite/anjay_lite_objs.h b/src/anjay_lite/anjay_lite_objs.h new file mode 100644 index 00000000..9a9cd30f --- /dev/null +++ b/src/anjay_lite/anjay_lite_objs.h @@ -0,0 +1,31 @@ +/* + * Copyright 2023-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_LITE_OBJS_H +#define ANJAY_LITE_OBJS_H + +#include + +#include + +#include + +sdm_obj_t *anjay_lite_security_obj_setup(uint16_t ssid, + char *uri, + anjay_security_mode_t sec_mode); + +sdm_obj_t *anjay_lite_server_obj_setup(uint16_t ssid, + uint32_t lifetime, + fluf_binding_type_t binding); + +uint32_t anjay_lite_server_obj_get_lifetime(void); + +bool anjay_lite_server_obj_update_trigger_active(void); + +#endif // ANJAY_LITE_OBJS_H diff --git a/src/anjay_lite/anjay_lite_register.c b/src/anjay_lite/anjay_lite_register.c new file mode 100644 index 00000000..2fdf95de --- /dev/null +++ b/src/anjay_lite/anjay_lite_register.c @@ -0,0 +1,170 @@ +/* + * Copyright 2023-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 "anjay_lite_objs.h" +#include "anjay_lite_register.h" +#include "anjay_lite_servers.h" + +typedef struct { + bool is_active; + bool is_connected; + bool waiting_for_response; + uint16_t id; + fluf_attr_register_t register_attr; + char location_path[FLUL_MAX_ALLOWED_LOCATION_PATHS_NUMBER] + [ANJAY_LITE_SERVERS_REGISTER_PATH_STR_LEN]; + size_t location_count; + uint64_t last_update_timestamp; + uint32_t lifetime; + bool force_update; + char lifetime_str[12]; +} server_register_t; + +static server_register_t servers[ANJAY_LITE_ALLOWED_SERVERS_NUMBER]; + +static char *bindig_UDP = "U"; + +static server_register_t *get_server_by_id(uint16_t server_id) { + for (int i = 0; i < ANJAY_LITE_ALLOWED_SERVERS_NUMBER; i++) { + if (servers[i].id == server_id) { + return &servers[i]; + } + } + return NULL; +} + +static void +register_callback(fluf_data_t *response, bool is_error, uint16_t server_id) { + server_register_t *server = get_server_by_id(server_id); + + server->waiting_for_response = false; + if (!is_error) { + if (response->msg_code == FLUF_COAP_CODE_CREATED) { + // copy location path + for (size_t i = 0; i < response->location_path.location_count; + i++) { + memcpy(server->location_path[i], + response->location_path.location[i], + response->location_path.location_len[i]); + } + server->location_count = response->location_path.location_count; + anjay_lite_servers_set_state(server_id, ANJAY_SERVERS_REGISTER); + server->last_update_timestamp = anj_time_now(); + } + } else { + anjay_lite_servers_set_state(server_id, ANJAY_SERVERS_ERROR); + } +} + +static void send_register_msg(anjay_lite_t *anjay_lite, + server_register_t *server) { + fluf_data_t request; + memset(&request, 0, sizeof(request)); + request.operation = FLUF_OP_REGISTER; + _anjay_lite_servers_get_register_payload(anjay_lite, &request); + request.attr.register_attr = server->register_attr; + + if (!anjay_lite_servers_exchange_request(server->id, &request, + register_callback)) { + server->waiting_for_response = true; + } +} + +static void +update_callback(fluf_data_t *response, bool is_error, uint16_t server_id) { + server_register_t *server = get_server_by_id(server_id); + + server->waiting_for_response = false; + if (!is_error) { + if (response->msg_code == FLUF_COAP_CODE_CHANGED) { + server->last_update_timestamp = anj_time_now(); + } else { + anjay_lite_servers_set_state(server_id, ANJAY_SERVERS_ERROR); + } + } +} + +static void maybe_send_update_msg(server_register_t *server) { + if ((anj_time_now() - server->last_update_timestamp) / 1000U + > (uint64_t) (server->lifetime / 2) + || server->force_update) { + server->force_update = false; + fluf_data_t request; + memset(&request, 0, sizeof(request)); + request.operation = FLUF_OP_UPDATE; + request.location_path.location_count = server->location_count; + for (size_t i = 0; i < server->location_count; i++) { + request.location_path.location[i] = server->location_path[i]; + request.location_path.location_len[i] = + strlen(server->location_path[i]); + } + + if (!anjay_lite_servers_exchange_request(server->id, &request, + update_callback)) { + server->waiting_for_response = true; + } + } +} + +int anjay_lite_register_add_server(anjay_lite_conn_conf_t *server_conf, + fluf_binding_type_t binding, + char *endpoint, + uint32_t lifetime) { + for (int i = 0; i < ANJAY_LITE_ALLOWED_SERVERS_NUMBER; i++) { + if (!servers[i].is_active) { + int res = anjay_lite_servers_add_server(server_conf, binding); + if (res >= 0) { + server_register_t *server = &servers[i]; + server->is_active = true; + server->id = (uint16_t) res; + server->lifetime = lifetime; + + server->register_attr.has_lwm2m_ver = true; + server->register_attr.lwm2m_ver = FLUF_LWM2M_VERSION_STR; + server->register_attr.has_binding = true; + if (binding == FLUF_BINDING_UDP + || binding == FLUF_BINDING_DTLS_PSK) { + server->register_attr.binding = bindig_UDP; + } + server->register_attr.has_endpoint = true; + server->register_attr.endpoint = endpoint; + server->register_attr.has_lifetime = true; + server->register_attr.lifetime = lifetime; + return 0; + } + } + } + return -1; +} + +void anjay_lite_register_process(anjay_lite_t *anjay_lite) { + for (int i = 0; i < ANJAY_LITE_ALLOWED_SERVERS_NUMBER; i++) { + if (servers[i].is_active && !servers[i].waiting_for_response) { + // check status + anjay_servers_state_t state = + anjay_lite_servers_get_state(servers[i].id); + if (state == ANJAY_SERVERS_ONLINE) { + send_register_msg(anjay_lite, &servers[i]); + } else if (state == ANJAY_SERVERS_REGISTER) { + servers[i].lifetime = anjay_lite_server_obj_get_lifetime(); + servers[i].force_update = + anjay_lite_server_obj_update_trigger_active(); + maybe_send_update_msg(&servers[i]); + } + } + } +} diff --git a/src/anjay_lite/anjay_lite_register.h b/src/anjay_lite/anjay_lite_register.h new file mode 100644 index 00000000..fd23c4ff --- /dev/null +++ b/src/anjay_lite/anjay_lite_register.h @@ -0,0 +1,26 @@ +/* + * Copyright 2023-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_LITE_REGISTER_H +#define ANJAY_LITE_REGISTER_H + +#include +#include + +#include +#include +#include + +int anjay_lite_register_add_server(anjay_lite_conn_conf_t *server_conf, + fluf_binding_type_t binding, + char *endpoint, + uint32_t lifetime); +void anjay_lite_register_process(anjay_lite_t *anjay_lite); + +#endif // ANJAY_LITE_REGISTER_H diff --git a/src/anjay_lite/anjay_lite_security_obj.c b/src/anjay_lite/anjay_lite_security_obj.c new file mode 100644 index 00000000..2e9381c1 --- /dev/null +++ b/src/anjay_lite/anjay_lite_security_obj.c @@ -0,0 +1,133 @@ +/* + * Copyright 2023-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 "anjay_lite_objs.h" + +#define SECURITY_OBJ_RID_URI 0 +#define SECURITY_OBJ_RID_BOOTSTRAP_SERVER 1 +#define SECURITY_OBJ_RID_SEC_MODE 2 +#define SECURITY_OBJ_RID_PUBLIC_KEY 3 +#define SECURITY_OBJ_RID_SERVER_KEY 4 +#define SECURITY_OBJ_RID_SECRET_KEY 5 +#define SECURITY_OBJ_RID_SSID 10 + +#define SECURITY_OBJ_RID_URI_IDX 0 +#define SECURITY_OBJ_RID_BOOTSTRAP_SERVER_IDX 1 +#define SECURITY_OBJ_RID_SEC_MODE_IDX 2 +#define SECURITY_OBJ_RID_PUBLIC_KEY_IDX 3 +#define SECURITY_OBJ_RID_SERVER_KEY_IDX 4 +#define SECURITY_OBJ_RID_SECRET_KEY_IDX 5 +#define SECURITY_OBJ_RID_SSID_IDX 6 + +static const sdm_res_spec_t res_spec_uri = { + .rid = SECURITY_OBJ_RID_URI, + .operation = SDM_RES_R, + .type = FLUF_DATA_TYPE_STRING +}; +static const sdm_res_spec_t res_spec_bootstrap_server = { + .rid = SECURITY_OBJ_RID_BOOTSTRAP_SERVER, + .operation = SDM_RES_R, + .type = FLUF_DATA_TYPE_BOOL +}; +static const sdm_res_spec_t res_spec_sec_mode = { + .rid = SECURITY_OBJ_RID_SEC_MODE, + .operation = SDM_RES_R, + .type = FLUF_DATA_TYPE_INT +}; +static const sdm_res_spec_t res_spec_public_key = { + .rid = SECURITY_OBJ_RID_PUBLIC_KEY, + .operation = SDM_RES_R, + .type = FLUF_DATA_TYPE_STRING +}; +static const sdm_res_spec_t res_spec_server_key = { + .rid = SECURITY_OBJ_RID_SERVER_KEY, + .operation = SDM_RES_R, + .type = FLUF_DATA_TYPE_STRING +}; +static const sdm_res_spec_t res_spec_secret_key = { + .rid = SECURITY_OBJ_RID_SECRET_KEY, + .operation = SDM_RES_R, + .type = FLUF_DATA_TYPE_STRING +}; +static const sdm_res_spec_t res_spec_ssid = { + .rid = SECURITY_OBJ_RID_SSID, + .operation = SDM_RES_R, + .type = FLUF_DATA_TYPE_INT +}; + +static sdm_res_t security_obj_resources[] = { + [SECURITY_OBJ_RID_URI_IDX] = { + .res_spec = &res_spec_uri + }, + [SECURITY_OBJ_RID_BOOTSTRAP_SERVER_IDX] = { + .res_spec = &res_spec_bootstrap_server, + .value.res_value.value.bool_value = false + }, + [SECURITY_OBJ_RID_SEC_MODE_IDX] = { + .res_spec = &res_spec_sec_mode + }, + [SECURITY_OBJ_RID_PUBLIC_KEY_IDX] = { + .res_spec = &res_spec_public_key + }, + [SECURITY_OBJ_RID_SERVER_KEY_IDX] = { + .res_spec = &res_spec_server_key + }, + [SECURITY_OBJ_RID_SECRET_KEY_IDX] = { + .res_spec = &res_spec_secret_key + }, + [SECURITY_OBJ_RID_SSID_IDX] = { + .res_spec = &res_spec_ssid + } +}; + +static sdm_obj_inst_t security_obj_instance = { + .iid = 0, + .res_count = AVS_ARRAY_SIZE(security_obj_resources), + .resources = security_obj_resources +}; + +static sdm_obj_inst_t *security_obj_instances[1] = { &security_obj_instance }; + +static sdm_obj_t security_obj = { + .oid = FLUF_OBJ_ID_SECURITY, + .insts = security_obj_instances, + .inst_count = 1, + .max_inst_count = 1 +}; + +sdm_obj_t *anjay_lite_security_obj_setup(uint16_t ssid, + char *uri, + anjay_security_mode_t sec_mode) { + if (ssid == 0 || ssid == UINT16_MAX || !uri + || sec_mode != ANJAY_SECURITY_NOSEC) { + return NULL; + } + + security_obj_resources[SECURITY_OBJ_RID_SSID_IDX] + .value.res_value.value.int_value = (int64_t) ssid; + security_obj_resources[SECURITY_OBJ_RID_URI_IDX] + .value.res_value.value.bytes_or_string.data = uri; + security_obj_resources[SECURITY_OBJ_RID_URI_IDX] + .value.res_value.value.bytes_or_string.chunk_length = strlen(uri); + security_obj_resources[SECURITY_OBJ_RID_SEC_MODE_IDX] + .value.res_value.value.int_value = (int64_t) sec_mode; + + return &security_obj; +} diff --git a/src/anjay_lite/anjay_lite_server_obj.c b/src/anjay_lite/anjay_lite_server_obj.c new file mode 100644 index 00000000..5e40db97 --- /dev/null +++ b/src/anjay_lite/anjay_lite_server_obj.c @@ -0,0 +1,181 @@ +/* + * Copyright 2023-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 "anjay_lite_objs.h" + +#define SERVER_OBJ_RID_SSID 0 +#define SERVER_OBJ_RID_LIFETIME 1 +#define SERVER_OBJ_RID_NOTIFICATION_STORING 6 +#define SERVER_OBJ_RID_BINDING 7 +#define SERVER_OBJ_RID_UPDATE_TRIGGER 9 + +#define SERVER_OBJ_RID_SSID_IDX 0 +#define SERVER_OBJ_RID_LIFETIME_IDX 1 +#define SERVER_OBJ_RID_NOTIFICATION_STORING_IDX 2 +#define SERVER_OBJ_RID_BINDING_IDX 3 +#define SERVER_OBJ_RID_UPDATE_TRIGGER_IDX 4 + +typedef struct { + sdm_obj_inst_t obj_inst; + char binding_mode_buff[2]; + bool update_trigger; +} sdm_server_obj_inst_t; + +static const sdm_res_spec_t res_spec_ssid = { + .rid = SERVER_OBJ_RID_SSID, + .operation = SDM_RES_R, + .type = FLUF_DATA_TYPE_INT +}; +static const sdm_res_spec_t res_spec_lifetime = { + .rid = SERVER_OBJ_RID_LIFETIME, + .operation = SDM_RES_RW, + .type = FLUF_DATA_TYPE_INT +}; +static const sdm_res_spec_t res_spec_notification_storing = { + .rid = SERVER_OBJ_RID_NOTIFICATION_STORING, + .operation = SDM_RES_RW, + .type = FLUF_DATA_TYPE_BOOL +}; +static const sdm_res_spec_t res_spec_binding = { + .rid = SERVER_OBJ_RID_BINDING, + .operation = SDM_RES_RW, + .type = FLUF_DATA_TYPE_STRING +}; +static const sdm_res_spec_t res_spec_update_trigger = { + .rid = SERVER_OBJ_RID_UPDATE_TRIGGER, + .operation = SDM_RES_E +}; + +static int binding_write(sdm_obj_t *obj, + sdm_obj_inst_t *obj_inst, + sdm_res_t *res, + sdm_res_inst_t *res_inst, + const fluf_res_value_t *value) { + (void) (obj); + (void) (res); + (void) (res_inst); + + const char *data = (const char *) value->bytes_or_string.data; + size_t data_len = value->bytes_or_string.chunk_length; + + // only UDP binding is now supported + if (data_len != 1 || data[0] != 'U') { + return SDM_ERR_METHOD_NOT_ALLOWED; + } + sdm_server_obj_inst_t *inst = (sdm_server_obj_inst_t *) obj_inst; + inst->binding_mode_buff[0] = data[0]; + return 0; +} + +static int update_trigger_callback(sdm_obj_t *obj, + sdm_obj_inst_t *obj_inst, + sdm_res_t *res, + const char *execute_arg, + size_t execute_arg_len) { + (void) (obj); + (void) (res); + (void) (execute_arg); + (void) (execute_arg_len); + + sdm_server_obj_inst_t *inst = (sdm_server_obj_inst_t *) obj_inst; + inst->update_trigger = true; + return 0; +} + +static const sdm_res_handlers_t res_handlers = { + .res_write = binding_write, + .res_execute = update_trigger_callback +}; + +static sdm_res_t server_obj_resources[] = { + [SERVER_OBJ_RID_SSID_IDX] = { + .res_spec = &res_spec_ssid + }, + [SERVER_OBJ_RID_LIFETIME_IDX] = { + .res_spec = &res_spec_lifetime + }, + [SERVER_OBJ_RID_NOTIFICATION_STORING_IDX] = { + .res_spec = &res_spec_notification_storing, + .value.res_value.value.bool_value = true + }, + [SERVER_OBJ_RID_BINDING_IDX] = { + .res_spec = &res_spec_binding, + .res_handlers = &res_handlers + }, + [SERVER_OBJ_RID_UPDATE_TRIGGER_IDX] = { + .res_spec = &res_spec_update_trigger, + .res_handlers = &res_handlers + } +}; + +static sdm_server_obj_inst_t server_obj_instance = { + .obj_inst = { + .iid = 0, + .res_count = AVS_ARRAY_SIZE(server_obj_resources), + .resources = server_obj_resources + } +}; + +static sdm_obj_inst_t *server_obj_instances[1] = { ( + sdm_obj_inst_t *) &server_obj_instance }; + +static sdm_obj_t server_obj = { + .oid = FLUF_OBJ_ID_SERVER, + .insts = server_obj_instances, + .inst_count = 1, + .max_inst_count = 1 +}; + +sdm_obj_t *anjay_lite_server_obj_setup(uint16_t ssid, + uint32_t lifetime, + fluf_binding_type_t binding) { + if (ssid == 0 || ssid == UINT16_MAX || lifetime == 0 + || binding != FLUF_BINDING_UDP) { + return NULL; + } + + server_obj_resources[SERVER_OBJ_RID_SSID_IDX] + .value.res_value.value.int_value = (int64_t) ssid; + server_obj_resources[SERVER_OBJ_RID_LIFETIME_IDX] + .value.res_value.value.int_value = (int64_t) lifetime; + server_obj_resources[SERVER_OBJ_RID_BINDING_IDX] + .value.res_value.value.bytes_or_string.chunk_length = 1; + server_obj_resources[SERVER_OBJ_RID_BINDING_IDX] + .value.res_value.value.bytes_or_string.full_length_hint = 1; + server_obj_resources[SERVER_OBJ_RID_BINDING_IDX] + .value.res_value.value.bytes_or_string.data = + server_obj_instance.binding_mode_buff; + server_obj_instance.binding_mode_buff[0] = 'U'; + + return &server_obj; +} + +uint32_t anjay_lite_server_obj_get_lifetime(void) { + return (uint32_t) server_obj_resources[SERVER_OBJ_RID_LIFETIME_IDX] + .value.res_value.value.int_value; +} + +bool anjay_lite_server_obj_update_trigger_active(void) { + if (server_obj_instance.update_trigger == true) { + server_obj_instance.update_trigger = false; + return true; + } + return false; +} diff --git a/src/anjay_lite/anjay_lite_servers.c b/src/anjay_lite/anjay_lite_servers.c new file mode 100644 index 00000000..2ac9b849 --- /dev/null +++ b/src/anjay_lite/anjay_lite_servers.c @@ -0,0 +1,465 @@ +/* + * Copyright 2023-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 "anjay_lite_servers.h" + +#define SERVERS_NUMBER ANJAY_LITE_ALLOWED_SERVERS_NUMBER + +typedef struct { + anjay_servers_state_t state; + anjay_lite_conn_conf_t conf; + fluf_binding_type_t binding; + uint64_t last_operation_timestamp; + anjay_net_conn_ref_t conn_ref; + void *user_data; + bool active_exchange; + anjay_servers_request_response_t *response_callback; + fluf_coap_msg_t coap; + bool awaiting_exchange_send_res; + bool awaiting_dm_op_send_res; + size_t expected_write_size; +} server_t; + +static server_t servers[SERVERS_NUMBER]; +static uint16_t servers_counter; +static uint8_t msg_buff[ANJAY_LITE_MSG_BUFF_SIZE]; +static char payload_buff[ANJAY_LITE_PAYLOAD_BUFF_SIZE]; + +static inline bool is_ok(anjay_net_op_res_t res) { + return res == ANJAY_NET_OP_RES_OK; +} + +static inline bool is_again(anjay_net_op_res_t res) { + return res == ANJAY_NET_OP_RES_AGAIN; +} + +static inline bool is_err(anjay_net_op_res_t res) { + return res == ANJAY_NET_OP_RES_ERR; +} + +static anjay_net_op_res_t do_open_udp(server_t *server) { + anjay_net_op_ctx_t ctx = { + .op = ANJAY_NET_OP_OPEN_UDP, + .args.open_udp = { + .hostname = server->conf.udp.hostname, + .port = (uint16_t) server->conf.udp.port, + .version = ANJAY_NET_IP_VER_V4 + } + }; + anjay_net_op_res_t res = anjay_net_op_handler(&ctx); + if (is_ok(res)) { + server->conn_ref = ctx.conn_ref; + } + return res; +} + +static anjay_net_op_res_t do_open_udp_res(server_t *server) { + anjay_net_op_ctx_t ctx = { + .op = ANJAY_NET_OP_OPEN_UDP_RES, + .conn_ref = server->conn_ref + }; + + return anjay_net_op_handler(&ctx); +} + +static anjay_net_op_res_t +do_send(server_t *server, size_t length, const uint8_t *buf) { + anjay_net_op_ctx_t ctx = { + .op = ANJAY_NET_OP_SEND, + .conn_ref = server->conn_ref, + .args.send = { + .buf = buf, + .length = length + } + }; + + return anjay_net_op_handler(&ctx); +} + +static anjay_net_op_res_t do_send_res(server_t *server, + size_t *out_write_length) { + anjay_net_op_ctx_t ctx = { + .op = ANJAY_NET_OP_SEND_RES, + .conn_ref = server->conn_ref + }; + + anjay_net_op_res_t res = anjay_net_op_handler(&ctx); + *out_write_length = ctx.args.send_res.out_write_length; + return res; +} + +static anjay_net_op_res_t do_try_recv(server_t *server, + size_t length, + uint8_t *out_read_buf, + size_t *out_read_length) { + anjay_net_op_ctx_t ctx = { + .op = ANJAY_NET_OP_TRY_RECV, + .conn_ref = server->conn_ref, + .args.try_recv = { + .length = length, + .out_read_buf = out_read_buf + } + }; + + anjay_net_op_res_t res = anjay_net_op_handler(&ctx); + *out_read_length = ctx.args.try_recv.out_read_length; + return res; +} + +static anjay_net_op_res_t do_close(server_t *server) { + anjay_net_op_ctx_t ctx = { + .op = ANJAY_NET_OP_CLOSE, + .conn_ref = server->conn_ref + }; + return anjay_net_op_handler(&ctx); +} + +static anjay_net_op_res_t do_close_res(server_t *server) { + anjay_net_op_ctx_t ctx = { + .op = ANJAY_NET_OP_CLOSE_RES, + .conn_ref = server->conn_ref + }; + return anjay_net_op_handler(&ctx); +} + +static anjay_net_op_res_t do_cleanup(server_t *server) { + anjay_net_op_ctx_t ctx = { + .op = ANJAY_NET_OP_CLEANUP, + .conn_ref = server->conn_ref + }; + return anjay_net_op_handler(&ctx); +} + +static void open_connection(server_t *server) { + anjay_net_op_res_t res = ANJAY_NET_OP_RES_ERR; + if (server->state == ANJAY_SERVERS_INIT) { + server->state = ANJAY_SERVERS_OFFLINE; + res = do_open_udp(server); + } else if (anj_time_now() - server->last_operation_timestamp + > ANJAY_LITE_RECONNECTION_TIMEOUT_MS) { + + server->last_operation_timestamp = anj_time_now(); + res = do_open_udp(server); + } + + if (is_ok(res)) { + server->state = ANJAY_SERVERS_OPEN_IN_PROGRESS; + } +} + +static void await_open_result(server_t *server) { + anjay_net_op_res_t res = do_open_udp_res(server); + if (is_again(res)) { + return; + } + if (is_ok(res)) { + server->state = ANJAY_SERVERS_ONLINE; + } else { + do_cleanup(server); + server->state = ANJAY_SERVERS_INIT; + } +} + +static void close_connection(server_t *server) { + if (anj_time_now() - server->last_operation_timestamp + > ANJAY_LITE_RECONNECTION_TIMEOUT_MS) { + server->last_operation_timestamp = anj_time_now(); + if (is_ok(do_close(server))) { + server->state = ANJAY_SERVERS_CLOSE_IN_PROGRESS; + } + } +} + +static void await_close_result(server_t *server) { + anjay_net_op_res_t res = do_close_res(server); + if (is_again(res)) { + return; + } + do_cleanup(server); + server->state = ANJAY_SERVERS_INIT; +} + +static void +get_new_msg(anjay_lite_t *anjay_lite, server_t *server, uint16_t server_id) { + fluf_data_t data; + size_t msg_size; + + while (1) { + anjay_net_op_res_t res = do_try_recv(server, ANJAY_LITE_MSG_BUFF_SIZE, + msg_buff, &msg_size); + if (is_again(res)) { + break; // no msg found + } else if (is_err(res)) { + server->state = ANJAY_SERVERS_ERROR; + break; + } + + memset(&data, 0, sizeof(data)); + if (!fluf_msg_decode(msg_buff, msg_size, server->binding, &data)) { + // ignore all request before registrationd + // TODO: ADD BLOCK OPTIONS + if (data.operation == FLUF_OP_RESPONSE && server->active_exchange) { + if (server->active_exchange) { + // match the repsonse with the request + switch (server->binding) { + case FLUF_BINDING_UDP: + case FLUF_BINDING_DTLS_PSK: + if (memcmp(server->coap.coap_udp.token.bytes, + data.coap.coap_udp.token.bytes, + data.coap.coap_udp.token.size)) { + // token no match + return; + } + break; + default: + return; + } + server->response_callback(&data, false, server_id); + server->active_exchange = false; + } + } + // if DM Interface + size_t response_msg_size = 0; + if (server->state == ANJAY_SERVERS_REGISTER) { + if ((data.operation >= FLUF_OP_DM_READ + && data.operation <= FLUF_OP_DM_DELETE) + || data.operation == FLUF_OP_INF_OBSERVE + || data.operation == FLUF_OP_INF_CANCEL_OBSERVE) { + if (data.operation == FLUF_OP_INF_OBSERVE + || data.operation == FLUF_OP_INF_CANCEL_OBSERVE + || data.operation == FLUF_OP_DM_WRITE_ATTR) { + sdm_notification(&data, &anjay_lite->dm, payload_buff, + ANJAY_LITE_PAYLOAD_BUFF_SIZE); + } else { + sdm_process(&anjay_lite->dm_impl, &anjay_lite->dm, + &data, false, payload_buff, + ANJAY_LITE_PAYLOAD_BUFF_SIZE); + } + if (!fluf_msg_prepare(&data, msg_buff, + ANJAY_LITE_MSG_BUFF_SIZE, + &response_msg_size)) { + if (!is_ok(do_send(server, response_msg_size, + msg_buff))) { + server->state = ANJAY_SERVERS_ERROR; + return; + } else { + server->expected_write_size = response_msg_size; + server->awaiting_dm_op_send_res = true; + } + } + } + } + } + } +} + +static void await_exchange_send_res(server_t *server, uint16_t server_id) { + size_t write_length; + anjay_net_op_res_t res = do_send_res(server, &write_length); + if (is_again(res)) { + return; + } + server->awaiting_exchange_send_res = false; + if (is_err(res) || write_length != server->expected_write_size) { + server->active_exchange = false; + server->response_callback(NULL, true, server_id); + } +} + +static void await_dm_op_send_res(server_t *server) { + size_t write_length; + anjay_net_op_res_t res = do_send_res(server, &write_length); + if (is_again(res)) { + return; + } + server->awaiting_dm_op_send_res = false; + if (is_err(res) || write_length != server->expected_write_size) { + server->state = ANJAY_SERVERS_ERROR; + } +} + +void anjay_lite_servers_exchange_delete(uint16_t server_id) { + if (server_id < SERVERS_NUMBER) { + servers[server_id].active_exchange = false; + } +} + +void _anjay_lite_servers_get_register_payload(anjay_lite_t *anjay_lite, + fluf_data_t *msg) { + sdm_process(&anjay_lite->dm_impl, &anjay_lite->dm, msg, false, payload_buff, + ANJAY_LITE_PAYLOAD_BUFF_SIZE); +} + +int anjay_lite_servers_exchange_request( + uint16_t server_id, + fluf_data_t *request, + anjay_servers_request_response_t *response_callback) { + + if (!response_callback || server_id >= SERVERS_NUMBER + || servers[server_id].active_exchange) { + return -1; + } + server_t *server = &servers[server_id]; + server->response_callback = response_callback; + + if (server->state != ANJAY_SERVERS_ONLINE + && server->state != ANJAY_SERVERS_REGISTER) { + return -1; + } + + size_t request_msg_size = 0; + request->binding = server->binding; + if (!fluf_msg_prepare(request, msg_buff, ANJAY_LITE_MSG_BUFF_SIZE, + &request_msg_size)) { + if (is_ok(do_send(server, request_msg_size, msg_buff))) { + memcpy(&servers->coap, &request->coap, sizeof(fluf_coap_msg_t)); + server->active_exchange = true; + server->last_operation_timestamp = anj_time_now(); + server->expected_write_size = request_msg_size; + server->awaiting_exchange_send_res = true; + return 0; + } + } + + return -1; +} + +static void notification_process(anjay_lite_t *anjay_lite, server_t *server) { + fluf_data_t data; + size_t response_msg_size = 0; + + memset(&data, 0, sizeof(fluf_data_t)); + data.binding = server->binding; + + sdm_notification_process(&data, &anjay_lite->dm, payload_buff, + ANJAY_LITE_PAYLOAD_BUFF_SIZE, + FLUF_COAP_FORMAT_SENML_CBOR); + if (data.operation != FLUF_OP_INF_NON_CON_NOTIFY + || fluf_msg_prepare(&data, msg_buff, ANJAY_LITE_MSG_BUFF_SIZE, + &response_msg_size)) { + return; + } + + if (!is_ok(do_send(server, response_msg_size, msg_buff))) { + server->state = ANJAY_SERVERS_ERROR; + return; + } else { + server->expected_write_size = response_msg_size; + server->awaiting_dm_op_send_res = true; + } +} + +void anjay_lite_servers_process(anjay_lite_t *anjay_lite) { + for (uint16_t i = 0; i < SERVERS_NUMBER; i++) { + if (servers[i].state == ANJAY_SERVERS_OFFLINE + || servers[i].state == ANJAY_SERVERS_INIT) { + open_connection(&servers[i]); + } else if (servers[i].state == ANJAY_SERVERS_OPEN_IN_PROGRESS) { + await_open_result(&servers[i]); + } else if (servers[i].state == ANJAY_SERVERS_ONLINE + || servers[i].state == ANJAY_SERVERS_REGISTER) { + if (servers[i].awaiting_exchange_send_res) { + await_exchange_send_res(&servers[i], i); + } else if (servers[i].awaiting_dm_op_send_res) { + await_dm_op_send_res(&servers[i]); + } else { + get_new_msg(anjay_lite, &servers[i], i); + notification_process(anjay_lite, &servers[i]); + } + } else if (servers[i].state == ANJAY_SERVERS_ERROR) { + close_connection(&servers[i]); + } else if (servers[i].state == ANJAY_SERVERS_CLOSE_IN_PROGRESS) { + await_close_result(&servers[i]); + } + // check timeouts + if (servers[i].active_exchange + && (anj_time_now() - servers[i].last_operation_timestamp + > ANJAY_LITE_RESPONSE_TIMEOUT_MS)) { + servers[i].active_exchange = false; + servers[i].response_callback(NULL, true, i); + } + } +} + +int anjay_lite_servers_add_server(anjay_lite_conn_conf_t *server_conf, + fluf_binding_type_t binding) { + if (servers_counter == SERVERS_NUMBER || !server_conf) { + return -1; + } + + servers[servers_counter].binding = binding; + memcpy(&(servers[servers_counter].conf), server_conf, + sizeof(anjay_lite_conn_conf_t)); + servers[servers_counter].state = ANJAY_SERVERS_OFFLINE; + + int ret = (int) servers_counter; + servers_counter++; + return ret; +} + +anjay_servers_state_t anjay_lite_servers_get_state(uint16_t server_id) { + if (server_id < SERVERS_NUMBER) { + return servers[server_id].state; + } + return ANJAY_SERVERS_INVALID; +} + +int anjay_lite_servers_set_state(uint16_t server_id, + anjay_servers_state_t new_state) { + if (server_id >= SERVERS_NUMBER) { + return -1; + } + + anjay_servers_state_t *state = &(servers[server_id].state); + + // allowed transitions + if (*state == ANJAY_SERVERS_ONLINE && new_state == ANJAY_SERVERS_REGISTER) { + *state = ANJAY_SERVERS_REGISTER; + return 0; + } else if (new_state == ANJAY_SERVERS_ERROR) { + *state = ANJAY_SERVERS_ERROR; + return 0; + } + + return -1; +} + +void anjay_lite_send_process(uint8_t *payload, size_t size) { + for (int i = 0; i < SERVERS_NUMBER; i++) { + if (servers[i].state == ANJAY_SERVERS_REGISTER) { + fluf_data_t data; + memset(&data, 0, sizeof(fluf_data_t)); + uint8_t msg_buf[512]; + size_t msg_buf_size; + + data.binding = FLUF_BINDING_UDP; + data.operation = FLUF_OP_INF_SEND; + data.content_format = FLUF_COAP_FORMAT_SENML_CBOR; + data.payload = payload; + data.payload_size = size; + + if (fluf_msg_prepare(&data, msg_buf, sizeof(msg_buf), + &msg_buf_size)) { + return; + } + do_send(&servers[i], msg_buf_size, msg_buf); + } + } +} diff --git a/src/anjay_lite/anjay_lite_servers.h b/src/anjay_lite/anjay_lite_servers.h new file mode 100644 index 00000000..97755a64 --- /dev/null +++ b/src/anjay_lite/anjay_lite_servers.h @@ -0,0 +1,59 @@ +/* + * Copyright 2023-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_LITE_SERVERS_H +#define ANJAY_LITE_SERVERS_H + +#include +#include + +#include +#include + +#include + +typedef enum { + ANJAY_SERVERS_INACTIVE, + ANJAY_SERVERS_INIT, + ANJAY_SERVERS_OFFLINE, + ANJAY_SERVERS_OPEN_IN_PROGRESS, + ANJAY_SERVERS_ONLINE, + // TODO: add bootstrap + ANJAY_SERVERS_REGISTER, + ANJAY_SERVERS_ERROR, + ANJAY_SERVERS_CLOSE_IN_PROGRESS, + ANJAY_SERVERS_INVALID +} anjay_servers_state_t; + +typedef void anjay_servers_request_response_t(fluf_data_t *response, + bool is_timeout, + uint16_t server_id); + +void anjay_lite_servers_process(anjay_lite_t *anjay_lite); + +int anjay_lite_servers_add_server(anjay_lite_conn_conf_t *server_conf, + fluf_binding_type_t binding); + +int anjay_lite_servers_exchange_request( + uint16_t server_id, + fluf_data_t *request, + anjay_servers_request_response_t *response_callback); + +void anjay_lite_servers_exchange_delete(uint16_t server_id); + +anjay_servers_state_t anjay_lite_servers_get_state(uint16_t server_id); +int anjay_lite_servers_set_state(uint16_t server_id, + anjay_servers_state_t new_state); + +void _anjay_lite_servers_get_register_payload(anjay_lite_t *anjay_lite, + fluf_data_t *msg); + +void anjay_lite_send_process(uint8_t *payload, size_t size); + +#endif // ANJAY_LITE_SERVERS_H diff --git a/src/fluf/fluf_attributes.c b/src/fluf/fluf_attributes.c new file mode 100644 index 00000000..3fb2967b --- /dev/null +++ b/src/fluf/fluf_attributes.c @@ -0,0 +1,175 @@ +/* + * Copyright 2023-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 "fluf_attributes.h" + +#define _RET_IF_ERROR(Val) \ + if (Val) { \ + return Val; \ + } + +static int get_attr(const fluf_coap_options_t *opts, + const char *attr, + uint32_t *uint_value, + double *double_value, + bool *variable_flag) { + assert(!!uint_value != !!double_value); + + size_t it = 0; + uint8_t attr_buff[FLUF_ATTR_OPTION_MAX_SIZE]; + + while (1) { + size_t attr_option_size = 0; + memset(attr_buff, 0, sizeof(attr_buff)); + int res = _fluf_coap_options_get_data_iterate( + opts, _FLUF_COAP_OPTION_URI_QUERY, &it, &attr_option_size, + attr_buff, sizeof(attr_buff)); + + if (res == _FLUF_COAP_OPTION_MISSING) { + return 0; + } else if (res) { + return FLUF_ERR_ATTR_BUFF; + } + + if (!strncmp(attr, (const char *) attr_buff, strlen(attr))) { + // format pmin={value} + size_t value_offset = strlen(attr) + 1; + if (value_offset > attr_option_size) { + return FLUF_ERR_MALFORMED_MESSAGE; + } + *variable_flag = true; + if (uint_value) { + res = fluf_string_to_uint32_value( + (const char *) (attr_buff + value_offset), + attr_option_size - value_offset, + (uint32_t *) uint_value); + } else if (double_value) { + res = fluf_string_to_simple_double_value( + (const char *) (attr_buff + value_offset), + attr_option_size - value_offset, + (double *) double_value); + } + if (res) { + return FLUF_ERR_ATTR_BUFF; + } + } + } +} + +static int add_str_attr(fluf_coap_options_t *opts, + const char *attr_name, + const char *attr_value, + bool value_present) { + if (!value_present) { + return 0; + } + + char atrr_buff[FLUF_ATTR_OPTION_MAX_SIZE] = { 0 }; + + size_t name_len = strlen(attr_name); + if (name_len >= FLUF_ATTR_OPTION_MAX_SIZE) { + return FLUF_ERR_ATTR_BUFF; + } + memcpy(atrr_buff, attr_name, name_len); + + // not empty string + if (attr_value) { + size_t value_len = strlen(attr_value); + atrr_buff[name_len] = '='; + + if (name_len + value_len + 1 >= FLUF_ATTR_OPTION_MAX_SIZE) { + return FLUF_ERR_ATTR_BUFF; + } + memcpy(&atrr_buff[name_len + 1], attr_value, value_len); + } + + return _fluf_coap_options_add_string(opts, _FLUF_COAP_OPTION_URI_QUERY, + atrr_buff); +} + +int fluf_attr_discover_decode(const fluf_coap_options_t *opts, + fluf_attr_discover_t *attr) { + memset(attr, 0, sizeof(fluf_attr_discover_t)); + + return get_attr(opts, "depth", &attr->depth, NULL, &attr->has_depth); +} + +int fluf_attr_notification_attr_decode(const fluf_coap_options_t *opts, + fluf_attr_notification_t *attr) { + memset(attr, 0, sizeof(fluf_attr_notification_t)); + + int res = get_attr(opts, "pmin", &attr->min_period, NULL, + &attr->has_min_period); + _RET_IF_ERROR(res); + res = get_attr(opts, "pmax", &attr->max_period, NULL, + &attr->has_max_period); + _RET_IF_ERROR(res); + res = get_attr(opts, "gt", NULL, &attr->greater_than, + &attr->has_greater_than); + _RET_IF_ERROR(res); + res = get_attr(opts, "lt", NULL, &attr->less_than, &attr->has_less_than); + _RET_IF_ERROR(res); + res = get_attr(opts, "st", NULL, &attr->step, &attr->has_step); + _RET_IF_ERROR(res); + res = get_attr(opts, "epmin", &attr->min_eval_period, NULL, + &attr->has_min_eval_period); + _RET_IF_ERROR(res); + res = get_attr(opts, "epmax", &attr->max_eval_period, NULL, + &attr->has_max_eval_period); +#ifdef FLUF_WITH_LWM2M12 + _RET_IF_ERROR(res); + res = get_attr(opts, "edge", &attr->edge, NULL, &attr->has_edge); + _RET_IF_ERROR(res); + res = get_attr(opts, "con", &attr->con, NULL, &attr->has_con); + _RET_IF_ERROR(res); + res = get_attr(opts, "hqmax", &attr->hqmax, NULL, &attr->has_hqmax); +#endif // FLUF_WITH_LWM2M12 + + return res; +} + +int fluf_attr_register_prepare(fluf_coap_options_t *opts, + const fluf_attr_register_t *attr) { + int res = add_str_attr(opts, "ep", attr->endpoint, attr->has_endpoint); + _RET_IF_ERROR(res); + if (attr->has_lifetime) { + char lifetime_buff[FLUF_U32_STR_MAX_LEN + 1] = { 0 }; + fluf_uint32_to_string_value(attr->lifetime, lifetime_buff); + res = add_str_attr(opts, "lt", lifetime_buff, attr->has_lifetime); + _RET_IF_ERROR(res); + } + res = add_str_attr(opts, "lwm2m", attr->lwm2m_ver, attr->has_lwm2m_ver); + _RET_IF_ERROR(res); + res = add_str_attr(opts, "b", attr->binding, attr->has_binding); + _RET_IF_ERROR(res); + res = add_str_attr(opts, "sms", attr->sms_number, attr->has_sms_number); + _RET_IF_ERROR(res); + res = add_str_attr(opts, "Q", NULL, attr->has_Q); + + return res; +} + +int fluf_attr_bootstrap_prepare(fluf_coap_options_t *opts, + const fluf_attr_bootstrap_t *attr) { + int res = add_str_attr(opts, "ep", attr->endpoint, attr->has_endpoint); + _RET_IF_ERROR(res); + if (attr->has_pct) { + char pct_buff[FLUF_U32_STR_MAX_LEN + 1] = { 0 }; + fluf_uint16_to_string_value(attr->pct, pct_buff); + res = add_str_attr(opts, "pct", pct_buff, attr->has_pct); + } + + return res; +} diff --git a/src/fluf/fluf_attributes.h b/src/fluf/fluf_attributes.h new file mode 100644 index 00000000..8475035b --- /dev/null +++ b/src/fluf/fluf_attributes.h @@ -0,0 +1,34 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#ifndef FLUF_ATTRIBUTES_H +#define FLUF_ATTRIBUTES_H + +#include + +#include "fluf_options.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int fluf_attr_notification_attr_decode(const fluf_coap_options_t *opts, + fluf_attr_notification_t *attr); +int fluf_attr_discover_decode(const fluf_coap_options_t *opts, + fluf_attr_discover_t *attr); +int fluf_attr_register_prepare(fluf_coap_options_t *opts, + const fluf_attr_register_t *attr); +int fluf_attr_bootstrap_prepare(fluf_coap_options_t *opts, + const fluf_attr_bootstrap_t *attr); + +#ifdef __cplusplus +} +#endif + +#endif // FLUF_ATTRIBUTES_H diff --git a/src/fluf/fluf_block.c b/src/fluf/fluf_block.c new file mode 100644 index 00000000..355d94bb --- /dev/null +++ b/src/fluf/fluf_block.c @@ -0,0 +1,146 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#include "fluf_block.h" +#include "fluf_options.h" + +#define _FLUF_BLOCK_OPTION_MAX_SIZE 3 + +#define _FLUF_BLOCK_OPTION_M_MASK 0x08 +#define _FLUF_BLOCK_OPTION_M_SHIFT 3 +#define _FLUF_BLOCK_OPTION_SZX_MASK 0x07 +#define _FLUF_BLOCK_OPTION_SZX_CALC_CONST 3 +#define _FLUF_BLOCK_OPTION_NUM_MASK 0xF0 +#define _FLUF_BLOCK_OPTION_NUM_SHIFT 4 +#define _FLUF_BLOCK_OPTION_BYTE_SHIFT 8 + +#define _FLUF_BLOCK_1_BYTE_NUM_MAX_VALUE 15 +#define _FLUF_BLOCK_2_BYTE_NUM_MAX_VALUE 4095 +#define _FLUF_BLOCK_NUM_MAX_VALUE 0x000FFFFF + +int _fluf_block_decode(fluf_coap_options_t *opts, fluf_block_t *block) { + bool check_block2_opt = false; + uint8_t block_buff[_FLUF_BLOCK_OPTION_MAX_SIZE]; + size_t block_option_size = 0; + + memset(block, 0, sizeof(fluf_block_t)); + + int res = _fluf_coap_options_get_data_iterate( + opts, _FLUF_COAP_OPTION_BLOCK1, NULL, &block_option_size, + block_buff, _FLUF_BLOCK_OPTION_MAX_SIZE); + if (res == _FLUF_COAP_OPTION_MISSING) { + res = _fluf_coap_options_get_data_iterate( + opts, _FLUF_COAP_OPTION_BLOCK2, NULL, &block_option_size, + block_buff, _FLUF_BLOCK_OPTION_MAX_SIZE); + check_block2_opt = true; + } + if (res == _FLUF_COAP_OPTION_MISSING) { + return 0; + } else if (res) { + return res; + } else if (!block_option_size) { + // dont't allow empty block option + return FLUF_ERR_MALFORMED_MESSAGE; + } else if (!res && block_option_size <= _FLUF_BLOCK_OPTION_MAX_SIZE) { + block->block_type = + check_block2_opt ? FLUF_OPTION_BLOCK_2 : FLUF_OPTION_BLOCK_1; + block->more_flag = !!(block_buff[block_option_size - 1] + & _FLUF_BLOCK_OPTION_M_MASK); + + size_t SZX = (block_buff[block_option_size - 1] + & _FLUF_BLOCK_OPTION_SZX_MASK); + block->size = + 2U << (SZX + _FLUF_BLOCK_OPTION_SZX_CALC_CONST); // block size = + // 2**(SZX + 4) + + if (block_option_size == 1) { + block->number = (block_buff[0] & _FLUF_BLOCK_OPTION_NUM_MASK) + >> _FLUF_BLOCK_OPTION_NUM_SHIFT; + } else if (block_option_size == 2) { + // network big-endian order + block->number = + (uint32_t) ((block_buff[0] << _FLUF_BLOCK_OPTION_NUM_SHIFT) + + ((block_buff[1] & _FLUF_BLOCK_OPTION_NUM_MASK) + >> _FLUF_BLOCK_OPTION_NUM_SHIFT)); + } else { + block->number = + (uint32_t) ((block_buff[0] + << (_FLUF_BLOCK_OPTION_BYTE_SHIFT + + _FLUF_BLOCK_OPTION_NUM_SHIFT)) + + (block_buff[1] + << _FLUF_BLOCK_OPTION_NUM_SHIFT) + + ((block_buff[2] & _FLUF_BLOCK_OPTION_NUM_MASK) + >> _FLUF_BLOCK_OPTION_NUM_SHIFT)); + } + + return 0; + } else { + return FLUF_ERR_MALFORMED_MESSAGE; + } +} + +int _fluf_block_prepare(fluf_coap_options_t *opts, fluf_block_t *block) { + uint16_t opt_number; + + if (block->block_type == FLUF_OPTION_BLOCK_1) { + opt_number = _FLUF_COAP_OPTION_BLOCK1; + } else if (block->block_type == FLUF_OPTION_BLOCK_2) { + opt_number = _FLUF_COAP_OPTION_BLOCK2; + } else { + return FLUF_ERR_INPUT_ARG; + } + + // prepare SZX parameter + uint8_t SZX = 0xFF; + const unsigned int allowed_block_sized_values[] = { 16, 32, 64, 128, + 256, 512, 1024 }; + for (uint8_t i = 0; i < sizeof(allowed_block_sized_values); i++) { + if (block->size == allowed_block_sized_values[i]) { + SZX = i; + break; + } + } + if (SZX == 0xFF) { + // incorrect block_size_option + return FLUF_ERR_INPUT_ARG; + } + + // determine block option size + size_t block_opt_size = 1; + if (block->number > _FLUF_BLOCK_1_BYTE_NUM_MAX_VALUE) { + block_opt_size++; + } + if (block->number > _FLUF_BLOCK_2_BYTE_NUM_MAX_VALUE) { + block_opt_size++; + } + if (block->number > _FLUF_BLOCK_NUM_MAX_VALUE) { + // block number out of the range + return FLUF_ERR_INPUT_ARG; + } + + uint8_t buff[_FLUF_BLOCK_OPTION_MAX_SIZE]; + + buff[block_opt_size - 1] = + (uint8_t) ((((uint8_t) block->more_flag + << _FLUF_BLOCK_OPTION_M_SHIFT) + & _FLUF_BLOCK_OPTION_M_MASK) + + SZX + + ((block->number << _FLUF_BLOCK_OPTION_NUM_SHIFT) + & _FLUF_BLOCK_OPTION_NUM_MASK)); + if (block_opt_size == 2) { + buff[0] = (uint8_t) (block->number >> _FLUF_BLOCK_OPTION_NUM_SHIFT); + } else if (block_opt_size == 3) { + buff[1] = (uint8_t) (block->number >> _FLUF_BLOCK_OPTION_NUM_SHIFT); + buff[0] = + (uint8_t) (block->number >> (_FLUF_BLOCK_OPTION_NUM_SHIFT + + _FLUF_BLOCK_OPTION_BYTE_SHIFT)); + } + + return _fluf_coap_options_add_data(opts, opt_number, buff, block_opt_size); +} diff --git a/src/fluf/fluf_block.h b/src/fluf/fluf_block.h new file mode 100644 index 00000000..e4ee5b32 --- /dev/null +++ b/src/fluf/fluf_block.h @@ -0,0 +1,29 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#ifndef FLUF_BLOCK_H +#define FLUF_BLOCK_H + +#include + +#include "fluf_options.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int _fluf_block_decode(fluf_coap_options_t *opts, fluf_block_t *block); + +int _fluf_block_prepare(fluf_coap_options_t *opts, fluf_block_t *block); + +#ifdef __cplusplus +} +#endif + +#endif // FLUF_BLOCK_H diff --git a/src/fluf/fluf_cbor_decoder_ll.c b/src/fluf/fluf_cbor_decoder_ll.c new file mode 100644 index 00000000..d8d5cfa2 --- /dev/null +++ b/src/fluf/fluf_cbor_decoder_ll.c @@ -0,0 +1,1506 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#if defined(FLUF_WITH_SENML_CBOR) || defined(FLUF_WITH_LWM2M_CBOR) \ + || defined(FLUF_WITH_CBOR) + +# ifdef FLUF_WITH_CBOR_STRING_TIME +# include +# endif // FLUF_WITH_CBOR_STRING_TIME + +# if defined(FLUF_WITH_CBOR_HALF_FLOAT) \ + || defined(FLUF_WITH_CBOR_DECIMAL_FRACTIONS) +# include +# endif /* defined(FLUF_WITH_CBOR_HALF_FLOAT) || \ + defined(FLUF_WITH_CBOR_DECIMAL_FRACTIONS) */ + +# include + +# include + +# include "fluf_internal.h" + +static int fill_prebuffer(fluf_cbor_ll_decoder_t *ctx, uint8_t min_size) { + assert(min_size <= sizeof(ctx->prebuffer)); + if (ctx->prebuffer_size - ctx->prebuffer_offset >= min_size) { + return 0; + } + if (ctx->prebuffer_offset) { + ctx->prebuffer_size -= ctx->prebuffer_offset; + if (ctx->prebuffer_size) { + memmove(ctx->prebuffer, + ctx->prebuffer + ctx->prebuffer_offset, + ctx->prebuffer_size); + } + ctx->prebuffer_offset = 0; + } + if (ctx->prebuffer_size < sizeof(ctx->prebuffer)) { + uint8_t bytes_to_copy = + (uint8_t) AVS_MIN(sizeof(ctx->prebuffer) - ctx->prebuffer_size, + (size_t) (ctx->input_end - ctx->input)); + if (bytes_to_copy) { + memcpy(ctx->prebuffer + ctx->prebuffer_size, + ctx->input, + bytes_to_copy); + ctx->input += bytes_to_copy; + ctx->prebuffer_size += bytes_to_copy; + } + } + if (ctx->prebuffer_size < min_size && !ctx->input_last) { + return FLUF_IO_WANT_NEXT_PAYLOAD; + } + return 0; +} + +# if FLUF_MAX_CBOR_NEST_STACK_SIZE > 0 +static bool is_indefinite(fluf_cbor_ll_nested_state_t *state) { + return state->all_items == FLUF_CBOR_LL_DECODER_ITEMS_INDEFINITE; +} +# endif // FLUF_MAX_CBOR_NEST_STACK_SIZE > 0 + +typedef enum { +# ifdef FLUF_WITH_CBOR_STRING_TIME + CBOR_DECODER_TAG_STRING_TIME = 0, +# endif // FLUF_WITH_CBOR_STRING_TIME + CBOR_DECODER_TAG_EPOCH_BASED_TIME = 1, +# ifdef FLUF_WITH_CBOR_DECIMAL_FRACTIONS + CBOR_DECODER_TAG_DECIMAL_FRACTION = 4, +# endif // FLUF_WITH_CBOR_DECIMAL_FRACTIONS +} cbor_decoder_tag_t; + +static inline int get_major_type(const uint8_t initial_byte) { + return initial_byte >> 5; +} + +static inline int get_additional_info(const uint8_t initial_byte) { + return initial_byte & 0x1f; +} + +static uint8_t parse_ext_length_size(const fluf_cbor_ll_decoder_t *ctx) { + switch (get_additional_info(ctx->current_item.initial_byte)) { + case CBOR_EXT_LENGTH_1BYTE: + return 1; + case CBOR_EXT_LENGTH_2BYTE: + return 2; + case CBOR_EXT_LENGTH_4BYTE: + return 4; + case CBOR_EXT_LENGTH_8BYTE: + return 8; + default: + return 0; + } +} + +static void +handle_header_for_float_or_simple_value(fluf_cbor_ll_decoder_t *ctx) { + assert(get_major_type(ctx->current_item.initial_byte) + == CBOR_MAJOR_TYPE_FLOAT_OR_SIMPLE_VALUE); + + /* See "2.3. Floating-Point Numbers and Values with No Content" */ + switch (get_additional_info(ctx->current_item.initial_byte)) { + case CBOR_VALUE_BOOL_FALSE: + case CBOR_VALUE_BOOL_TRUE: + ctx->current_item.value_type = FLUF_CBOR_LL_VALUE_BOOL; + break; + case CBOR_VALUE_NULL: + ctx->current_item.value_type = FLUF_CBOR_LL_VALUE_NULL; + break; +# ifdef FLUF_WITH_CBOR_HALF_FLOAT + case CBOR_VALUE_FLOAT_16: +# endif // FLUF_WITH_CBOR_HALF_FLOAT + case CBOR_VALUE_FLOAT_32: + ctx->current_item.value_type = FLUF_CBOR_LL_VALUE_FLOAT; + break; + case CBOR_VALUE_FLOAT_64: + ctx->current_item.value_type = FLUF_CBOR_LL_VALUE_DOUBLE; + break; + case CBOR_VALUE_UNDEFINED: + case CBOR_VALUE_IN_NEXT_BYTE: + default: + /* As per "Table 2: Simple Values", range 32..255 is unassigned, so + * we may call it an error. */ + ctx->state = FLUF_CBOR_LL_DECODER_STATE_ERROR; + } +} + +static void ignore_tag(fluf_cbor_ll_decoder_t *ctx) { + assert(get_major_type(ctx->current_item.initial_byte) + == CBOR_MAJOR_TYPE_TAG); +# ifdef FLUF_WITH_CBOR_STRING_TIME + assert(get_additional_info(ctx->current_item.initial_byte) + != CBOR_DECODER_TAG_STRING_TIME); +# endif // FLUF_WITH_CBOR_STRING_TIME + assert(get_additional_info(ctx->current_item.initial_byte) + != CBOR_DECODER_TAG_EPOCH_BASED_TIME); +# ifdef FLUF_WITH_CBOR_DECIMAL_FRACTIONS + assert(get_additional_info(ctx->current_item.initial_byte) + != CBOR_DECODER_TAG_DECIMAL_FRACTION); +# endif // FLUF_WITH_CBOR_DECIMAL_FRACTIONS + uint8_t ext_len_size = parse_ext_length_size(ctx); + if (ext_len_size) { + if (ctx->prebuffer_offset + ext_len_size > ctx->prebuffer_size) { + assert(ctx->input_last); + ctx->state = FLUF_CBOR_LL_DECODER_STATE_ERROR; + } else { + ctx->prebuffer_offset += ext_len_size; + } + } +} + +# if FLUF_MAX_CBOR_NEST_STACK_SIZE > 0 +static int nested_state_push(fluf_cbor_ll_decoder_t *ctx); +static void nested_state_pop(fluf_cbor_ll_decoder_t *ctx); +static fluf_cbor_ll_nested_state_t * +nested_state_top(fluf_cbor_ll_decoder_t *ctx) { + assert(ctx->nest_stack_size); + return &ctx->nest_stack[ctx->nest_stack_size - 1]; +} +# endif // FLUF_MAX_CBOR_NEST_STACK_SIZE > 0 + +static int preprocess_next_value(fluf_cbor_ll_decoder_t *ctx) { + while (ctx->state == FLUF_CBOR_LL_DECODER_STATE_OK) { + // We might need to skip the tag, which might be up to 8 bytes + int result = fill_prebuffer(ctx, 9); + if (result) { + return result; + } + assert(ctx->prebuffer_offset <= ctx->prebuffer_size); + if (ctx->prebuffer_offset == ctx->prebuffer_size) { + // EOF + if (ctx->after_tag) { + /* All tags must be followed with data, otherwise the CBOR + * payload is malformed */ + ctx->state = FLUF_CBOR_LL_DECODER_STATE_ERROR; + } else { + ctx->state = FLUF_CBOR_LL_DECODER_STATE_FINISHED; + } + return 0; + } + + uint8_t byte = ctx->prebuffer[ctx->prebuffer_offset++]; + if (byte == CBOR_INDEFINITE_STRUCTURE_BREAK) { + /* end of the indefinite map, array or byte/text string */ +# if FLUF_MAX_CBOR_NEST_STACK_SIZE > 0 + if (ctx->nest_stack_size + && (nested_state_top(ctx)->type != FLUF_CBOR_LL_VALUE_MAP + || !nested_state_top(ctx)->items_parsed.odd) + && is_indefinite(nested_state_top(ctx))) { + nested_state_pop(ctx); + } else +# endif // FLUF_MAX_CBOR_NEST_STACK_SIZE > 0 + { + ctx->state = FLUF_CBOR_LL_DECODER_STATE_ERROR; + } + continue; + } + ctx->current_item.initial_byte = byte; + + if (get_major_type(ctx->current_item.initial_byte) + == CBOR_MAJOR_TYPE_UINT) { + ctx->current_item.value_type = FLUF_CBOR_LL_VALUE_UINT; + } else if (get_major_type(ctx->current_item.initial_byte) + == CBOR_MAJOR_TYPE_NEGATIVE_INT) { + ctx->current_item.value_type = FLUF_CBOR_LL_VALUE_NEGATIVE_INT; + } else if (get_major_type(ctx->current_item.initial_byte) + == CBOR_MAJOR_TYPE_BYTE_STRING) { + ctx->current_item.value_type = FLUF_CBOR_LL_VALUE_BYTE_STRING; + } else if (get_major_type(ctx->current_item.initial_byte) + == CBOR_MAJOR_TYPE_TEXT_STRING) { + ctx->current_item.value_type = FLUF_CBOR_LL_VALUE_TEXT_STRING; + } else if (get_major_type(ctx->current_item.initial_byte) + == CBOR_MAJOR_TYPE_ARRAY) { + ctx->current_item.value_type = FLUF_CBOR_LL_VALUE_ARRAY; + } else if (get_major_type(ctx->current_item.initial_byte) + == CBOR_MAJOR_TYPE_MAP) { + ctx->current_item.value_type = FLUF_CBOR_LL_VALUE_MAP; + } else if (get_major_type(ctx->current_item.initial_byte) + == CBOR_MAJOR_TYPE_FLOAT_OR_SIMPLE_VALUE) { + handle_header_for_float_or_simple_value(ctx); + } else { + // This if ladder is supposed to be exhaustive + assert(get_major_type(ctx->current_item.initial_byte) + == CBOR_MAJOR_TYPE_TAG); + switch (get_additional_info(ctx->current_item.initial_byte)) { +# ifdef FLUF_WITH_CBOR_STRING_TIME + case CBOR_DECODER_TAG_STRING_TIME: +# endif // FLUF_WITH_CBOR_STRING_TIME + case CBOR_DECODER_TAG_EPOCH_BASED_TIME: + if (ctx->subparser_type != FLUF_CBOR_LL_SUBPARSER_NONE) { + ctx->state = FLUF_CBOR_LL_DECODER_STATE_ERROR; + return 0; + } + ctx->current_item.value_type = FLUF_CBOR_LL_VALUE_TIMESTAMP; + break; +# ifdef FLUF_WITH_CBOR_DECIMAL_FRACTIONS + case CBOR_DECODER_TAG_DECIMAL_FRACTION: + /** + * From section "2.4. Optional Tagging of Items": + * > Decoders do not need to understand tags, and thus tags may + * > be of little value in applications where the implementation + * > creating a particular CBOR data item and the implementation + * > decoding that stream know the semantic meaning of each item + * > in the data flow. + * > + * > [...] + * > + * > Understanding the semantic tags is optional for a decoder; + * > it can just jump over the initial bytes of the tag and + * > interpret the tagged data item itself. + * + * Also: + * > The initial bytes of the tag follow the rules for positive + * > integers (major type 0). + * + * However, SenML specification, "6. CBOR Representation + * (application/senml+cbor)" says: + * + * > The CBOR [RFC7049] representation is equivalent to the JSON + * > representation, with the following changes: + * > + * > o For JSON Numbers, the CBOR representation can use + * > integers, floating-point numbers, or decimal fractions + * > (CBOR Tag 4); + * + * so, we are basically forced to support tag 4. + * + * The idea, of course, is to pack decoded decimal fraction into + * double and just hope for the best -- there is no dedicated + * type in LwM2M for decimal fractions. + */ + if (ctx->subparser_type != FLUF_CBOR_LL_SUBPARSER_NONE) { + ctx->state = FLUF_CBOR_LL_DECODER_STATE_ERROR; + return 0; + } + ctx->current_item.value_type = FLUF_CBOR_LL_VALUE_DOUBLE; + break; +# endif // FLUF_WITH_CBOR_DECIMAL_FRACTIONS + default: + ignore_tag(ctx); + ctx->after_tag = true; + continue; + } + } + ctx->needs_preprocessing = false; + break; + } + + if (ctx->state == FLUF_CBOR_LL_DECODER_STATE_ERROR) { + return 0; + } + +# if FLUF_MAX_CBOR_NEST_STACK_SIZE > 0 + while (ctx->nest_stack_size) { + fluf_cbor_ll_nested_state_t *top = nested_state_top(ctx); + if (is_indefinite(top)) { + top->items_parsed.odd = !top->items_parsed.odd; + return 0; + } + if ((size_t) top->all_items - top->items_parsed.total) { + top->items_parsed.total++; + return 0; + } + nested_state_pop(ctx); + } +# endif // FLUF_MAX_CBOR_NEST_STACK_SIZE > 0 + return 0; +} + +static int ensure_value_or_error_available(fluf_cbor_ll_decoder_t *ctx) { + if (ctx->state != FLUF_CBOR_LL_DECODER_STATE_OK + || !ctx->needs_preprocessing) { + return 0; + } + return preprocess_next_value(ctx); +} + +static int parse_uint(fluf_cbor_ll_decoder_t *ctx, uint64_t *out_value) { + uint8_t ext_len_size = parse_ext_length_size(ctx); + if (!ext_len_size) { + *out_value = + (uint64_t) get_additional_info(ctx->current_item.initial_byte); + if (*out_value >= CBOR_EXT_LENGTH_1BYTE) { + // Invalid short primitive value + ctx->state = FLUF_CBOR_LL_DECODER_STATE_ERROR; + return FLUF_IO_ERR_FORMAT; + } + return 0; + } + + int result = fill_prebuffer(ctx, ext_len_size); + if (result) { + return result; + } + union { + uint8_t u8; + uint16_t u16; + uint32_t u32; + uint64_t u64; + } value; + if (ctx->prebuffer_offset + ext_len_size > ctx->prebuffer_size) { + assert(ctx->input_last); + ctx->state = FLUF_CBOR_LL_DECODER_STATE_ERROR; + return FLUF_IO_ERR_FORMAT; + } + memcpy(&value, ctx->prebuffer + ctx->prebuffer_offset, ext_len_size); + ctx->prebuffer_offset += ext_len_size; + switch (ext_len_size) { + case 1: + *out_value = value.u8; + return 0; + case 2: + *out_value = avs_convert_be16(value.u16); + return 0; + case 4: + *out_value = avs_convert_be32(value.u32); + return 0; + case 8: + *out_value = avs_convert_be64(value.u64); + return 0; + default: + AVS_UNREACHABLE("unsupported extended length size"); + return FLUF_IO_ERR_LOGIC; + } +} + +static int parse_size(fluf_cbor_ll_decoder_t *ctx, size_t *out_value) { + uint64_t u64; + int result = parse_uint(ctx, &u64); + if (result) { + return result; + } + if (u64 > SIZE_MAX) { + return FLUF_IO_ERR_FORMAT; + } + *out_value = (size_t) u64; + return 0; +} + +# if FLUF_MAX_CBOR_NEST_STACK_SIZE > 0 +static int parse_ptrdiff(fluf_cbor_ll_decoder_t *ctx, ptrdiff_t *out_value) { + size_t size; + int result = parse_size(ctx, &size); + if (result) { + return result; + } + if (size > SIZE_MAX / 2) { + return FLUF_IO_ERR_FORMAT; + } + *out_value = (ptrdiff_t) size; + return 0; +} + +static int nested_state_push(fluf_cbor_ll_decoder_t *ctx) { + assert(ctx->state == FLUF_CBOR_LL_DECODER_STATE_OK); + assert(ctx->current_item.value_type == FLUF_CBOR_LL_VALUE_ARRAY + || ctx->current_item.value_type == FLUF_CBOR_LL_VALUE_MAP + || ((ctx->current_item.value_type == FLUF_CBOR_LL_VALUE_BYTE_STRING + || ctx->current_item.value_type + == FLUF_CBOR_LL_VALUE_TEXT_STRING) + && get_additional_info(ctx->current_item.initial_byte) + == CBOR_EXT_LENGTH_INDEFINITE)); + + fluf_cbor_ll_nested_state_t state = { + .type = ctx->current_item.value_type + }; + + int result = FLUF_IO_ERR_LOGIC; + if (ctx->nest_stack_size == AVS_ARRAY_SIZE(ctx->nest_stack)) { + result = FLUF_IO_ERR_FORMAT; + goto error; + } + + switch (state.type) { + case FLUF_CBOR_LL_VALUE_ARRAY: + if (get_additional_info(ctx->current_item.initial_byte) + == CBOR_EXT_LENGTH_INDEFINITE) { + /* indefinite array */ + state.all_items = FLUF_CBOR_LL_DECODER_ITEMS_INDEFINITE; + } else if ((result = parse_ptrdiff(ctx, &state.all_items))) { + goto error; + } + break; + case FLUF_CBOR_LL_VALUE_MAP: + if (get_additional_info(ctx->current_item.initial_byte) + == CBOR_EXT_LENGTH_INDEFINITE) { + /* indefinite map */ + state.all_items = FLUF_CBOR_LL_DECODER_ITEMS_INDEFINITE; + } else if ((result = parse_ptrdiff(ctx, &state.all_items))) { + goto error; + } else if (state.all_items > PTRDIFF_MAX / 2) { + result = FLUF_IO_ERR_FORMAT; + goto error; + } else { + /** + * A map contains (key, value) pairs, which, in effect doubles the + * number of expected entries. + */ + state.all_items *= 2; + } + break; +# ifdef FLUF_WITH_CBOR_INDEFINITE_BYTES + case FLUF_CBOR_LL_VALUE_BYTE_STRING: + case FLUF_CBOR_LL_VALUE_TEXT_STRING: + state.all_items = FLUF_CBOR_LL_DECODER_ITEMS_INDEFINITE; + break; +# endif // FLUF_WITH_CBOR_INDEFINITE_BYTES + default: + AVS_UNREACHABLE("this switch statement must be exhaustive"); + goto error; + } + + ctx->nest_stack_size++; + *nested_state_top(ctx) = state; + return 0; +error: + if (result < 0) { + ctx->state = FLUF_CBOR_LL_DECODER_STATE_ERROR; + } + return result; +} + +static void nested_state_pop(fluf_cbor_ll_decoder_t *ctx) { + assert(is_indefinite(nested_state_top(ctx)) + || ((size_t) nested_state_top(ctx)->all_items + - nested_state_top(ctx)->items_parsed.total) + == 0); + + ctx->nest_stack_size--; +} +# endif // FLUF_MAX_CBOR_NEST_STACK_SIZE > 0 + +static int decode_uint(fluf_cbor_ll_decoder_t *ctx, uint64_t *out_value) { + if (ctx->state != FLUF_CBOR_LL_DECODER_STATE_OK + || (ctx->subparser_type != FLUF_CBOR_LL_SUBPARSER_NONE + && ctx->subparser_type + != FLUF_CBOR_LL_SUBPARSER_EPOCH_BASED_TIME)) { + return FLUF_IO_ERR_LOGIC; + } + if (ctx->current_item.value_type != FLUF_CBOR_LL_VALUE_UINT) { + return FLUF_IO_ERR_FORMAT; + } + assert(!ctx->needs_preprocessing); + int retval = parse_uint(ctx, out_value); + if (retval <= 0) { + ctx->needs_preprocessing = true; + ctx->after_tag = false; + } + return retval; +} + +static int decode_negative_int(fluf_cbor_ll_decoder_t *ctx, + int64_t *out_value) { + if (ctx->state != FLUF_CBOR_LL_DECODER_STATE_OK + || (ctx->subparser_type != FLUF_CBOR_LL_SUBPARSER_NONE + && ctx->subparser_type + != FLUF_CBOR_LL_SUBPARSER_EPOCH_BASED_TIME)) { + return FLUF_IO_ERR_LOGIC; + } + if (ctx->current_item.value_type != FLUF_CBOR_LL_VALUE_NEGATIVE_INT) { + return FLUF_IO_ERR_FORMAT; + } + assert(!ctx->needs_preprocessing); + uint64_t u64; + int result = parse_uint(ctx, &u64); + if (result) { + return result; + } + /* equivalent to if (u64 >= -INT64_MIN) */ + if (u64 >= (uint64_t) INT64_MAX + 1) { + ctx->state = FLUF_CBOR_LL_DECODER_STATE_ERROR; + return FLUF_IO_ERR_FORMAT; + } + *out_value = -(int64_t) u64 - INT64_C(1); + ctx->needs_preprocessing = true; + ctx->after_tag = false; + return 0; +} + +# ifdef FLUF_WITH_CBOR_HALF_FLOAT +static float decode_half_float(uint16_t half) { + /* Code adapted from https://tools.ietf.org/html/rfc7049#appendix-D */ + const int exponent = (half >> 10) & 0x1f; + const int mantissa = half & 0x3ff; + float value; + if (exponent == 0) { + value = ldexpf((float) mantissa, -24); + } else if (exponent != 31) { + value = ldexpf((float) (mantissa + 1024), exponent - 25); + } else if (mantissa == 0) { + value = INFINITY; + } else { + value = NAN; + } + return (half & 0x8000) ? -value : value; +} +# endif // FLUF_WITH_CBOR_HALF_FLOAT + +static int decode_float(fluf_cbor_ll_decoder_t *ctx, float *out_value) { + if (ctx->state != FLUF_CBOR_LL_DECODER_STATE_OK + || (ctx->subparser_type != FLUF_CBOR_LL_SUBPARSER_NONE + && ctx->subparser_type + != FLUF_CBOR_LL_SUBPARSER_EPOCH_BASED_TIME)) { + return FLUF_IO_ERR_LOGIC; + } + if (ctx->current_item.value_type != FLUF_CBOR_LL_VALUE_FLOAT) { + return FLUF_IO_ERR_FORMAT; + } + assert(!ctx->needs_preprocessing); + int result; +# ifdef FLUF_WITH_CBOR_HALF_FLOAT + if (get_additional_info(ctx->current_item.initial_byte) + == CBOR_VALUE_FLOAT_16) { + uint16_t value; + if ((result = fill_prebuffer(ctx, sizeof(value)))) { + return result; + } + if (ctx->prebuffer_offset + sizeof(value) > ctx->prebuffer_size) { + result = FLUF_IO_ERR_FORMAT; + } else { + memcpy(&value, + ctx->prebuffer + ctx->prebuffer_offset, + sizeof(value)); + ctx->prebuffer_offset += sizeof(value); + *out_value = decode_half_float(avs_convert_be16(value)); + } + } else +# endif // FLUF_WITH_CBOR_HALF_FLOAT + { + assert(get_additional_info(ctx->current_item.initial_byte) + == CBOR_VALUE_FLOAT_32); + uint32_t value; + if ((result = fill_prebuffer(ctx, sizeof(value)))) { + return result; + } + if (ctx->prebuffer_offset + sizeof(value) > ctx->prebuffer_size) { + result = FLUF_IO_ERR_FORMAT; + } else { + memcpy(&value, + ctx->prebuffer + ctx->prebuffer_offset, + sizeof(value)); + ctx->prebuffer_offset += sizeof(value); + *out_value = avs_ntohf(value); + } + } + if (result) { + assert(result < 0); + ctx->state = FLUF_CBOR_LL_DECODER_STATE_ERROR; + } else { + ctx->needs_preprocessing = true; + ctx->after_tag = false; + } + return result; +} + +# ifdef FLUF_WITH_CBOR_DECIMAL_FRACTIONS +static int reinterpret_fraction_component_as_double(fluf_cbor_ll_decoder_t *ctx, + double *out_value) { + if (ctx->state != FLUF_CBOR_LL_DECODER_STATE_OK) { + return FLUF_IO_ERR_FORMAT; + } + assert(!ctx->needs_preprocessing); + if (ctx->current_item.value_type != FLUF_CBOR_LL_VALUE_UINT + && ctx->current_item.value_type + != FLUF_CBOR_LL_VALUE_NEGATIVE_INT) { + return FLUF_IO_ERR_FORMAT; + } + uint64_t value; + int result = parse_uint(ctx, &value); + if (result <= 0) { + ctx->needs_preprocessing = true; + ctx->after_tag = false; + } + if (result) { + return result; + } + *out_value = (double) value; + if (ctx->current_item.value_type == FLUF_CBOR_LL_VALUE_NEGATIVE_INT) { + *out_value = -*out_value - 1.0; + } + return 0; +} + +static int ensure_fraction_component_available(fluf_cbor_ll_decoder_t *ctx, + double *out_value) { + if (!isnan(*out_value)) { + return 0; + } + int result = ensure_value_or_error_available(ctx); + if (result + || ctx->nest_stack_size + != ctx->subparser.decimal_fraction.array_level + || (result = reinterpret_fraction_component_as_double(ctx, + out_value))) { + return result ? result : FLUF_IO_ERR_FORMAT; + } + assert(!isnan(*out_value)); + return 0; +} + +static int decode_decimal_fraction(fluf_cbor_ll_decoder_t *ctx, + double *out_value) { + int result = 0; + /** + * RFC7049 "2.4.3. Decimal Fractions and Bigfloats": + * + * > A decimal fraction or a bigfloat is represented as a tagged array + * > that contains exactly two integer numbers: an exponent e and a + * > mantissa m. Decimal fractions (tag 4) use base-10 exponents; the + * > value of a decimal fraction data item is m*(10**e). + */ + if (ctx->subparser_type == FLUF_CBOR_LL_SUBPARSER_NONE) { + size_t current_level; + if ((result = + fluf_cbor_ll_decoder_nesting_level(ctx, ¤t_level))) { + return result; + } + assert(get_major_type(ctx->current_item.initial_byte) + == CBOR_MAJOR_TYPE_TAG + || ctx->state != FLUF_CBOR_LL_DECODER_STATE_OK); + ctx->subparser.decimal_fraction.array_level = current_level + 1; + ctx->subparser.decimal_fraction.entered_array = false; + ctx->subparser.decimal_fraction.exponent = NAN; + ctx->subparser.decimal_fraction.mantissa = NAN; + ctx->subparser_type = FLUF_CBOR_LL_SUBPARSER_DECIMAL_FRACTION; + ctx->needs_preprocessing = true; + ctx->after_tag = true; + } else if (ctx->subparser_type != FLUF_CBOR_LL_SUBPARSER_DECIMAL_FRACTION) { + return FLUF_IO_ERR_FORMAT; + } + if (!ctx->subparser.decimal_fraction.entered_array) { + if ((result = ensure_value_or_error_available(ctx)) + || ctx->state != FLUF_CBOR_LL_DECODER_STATE_OK + || ctx->current_item.value_type != FLUF_CBOR_LL_VALUE_ARRAY + || (result = nested_state_push(ctx))) { + return result ? result : FLUF_IO_ERR_FORMAT; + } + ctx->needs_preprocessing = true; + ctx->after_tag = false; + ctx->subparser.decimal_fraction.entered_array = true; + } + if ((result = ensure_fraction_component_available( + ctx, &ctx->subparser.decimal_fraction.exponent)) + || (result = ensure_fraction_component_available( + ctx, &ctx->subparser.decimal_fraction.mantissa))) { + return result; + } + if ((result = ensure_value_or_error_available(ctx)) + || ctx->state == FLUF_CBOR_LL_DECODER_STATE_ERROR + || (ctx->state == FLUF_CBOR_LL_DECODER_STATE_OK + && ctx->nest_stack_size + == ctx->subparser.decimal_fraction.array_level)) { + return result ? result : FLUF_IO_ERR_FORMAT; + } + *out_value = ctx->subparser.decimal_fraction.mantissa + * pow(10.0, ctx->subparser.decimal_fraction.exponent); + ctx->subparser_type = FLUF_CBOR_LL_SUBPARSER_NONE; + return 0; +} +# endif // FLUF_WITH_CBOR_DECIMAL_FRACTIONS + +static int decode_double(fluf_cbor_ll_decoder_t *ctx, double *out_value) { + if (ctx->state != FLUF_CBOR_LL_DECODER_STATE_OK) { + return FLUF_IO_ERR_LOGIC; + } + assert(!ctx->needs_preprocessing); + int result; + +# ifdef FLUF_WITH_CBOR_DECIMAL_FRACTIONS + /** + * NOTE: This if is safe, because decimal fraction tag (4) does not + * conflict with any kind of floating-point-type value. Also we wouldn't + * land in this function for non-floating-point types (as ensured by the + * if above). + */ + if (ctx->subparser_type == FLUF_CBOR_LL_SUBPARSER_DECIMAL_FRACTION + || (ctx->subparser_type == FLUF_CBOR_LL_SUBPARSER_NONE + && ctx->current_item.value_type == FLUF_CBOR_LL_VALUE_DOUBLE + && get_additional_info(ctx->current_item.initial_byte) + == CBOR_DECODER_TAG_DECIMAL_FRACTION)) { + assert(ctx->subparser_type == FLUF_CBOR_LL_SUBPARSER_DECIMAL_FRACTION + || get_major_type(ctx->current_item.initial_byte) + == CBOR_MAJOR_TYPE_TAG); + result = decode_decimal_fraction(ctx, out_value); + } else +# endif // FLUF_WITH_CBOR_DECIMAL_FRACTIONS + if (ctx->current_item.value_type != FLUF_CBOR_LL_VALUE_DOUBLE) { + return FLUF_IO_ERR_FORMAT; + } else { + uint64_t value; + if (!(result = fill_prebuffer(ctx, sizeof(value)))) { + if (ctx->prebuffer_offset + sizeof(value) > ctx->prebuffer_size) { + ctx->state = FLUF_CBOR_LL_DECODER_STATE_ERROR; + result = FLUF_IO_ERR_FORMAT; + } else { + memcpy(&value, + ctx->prebuffer + ctx->prebuffer_offset, + sizeof(value)); + ctx->prebuffer_offset += sizeof(value); + *out_value = avs_ntohd(value); + } + } + if (result <= 0) { + ctx->needs_preprocessing = true; + ctx->after_tag = false; + } + } + return result; +} + +static int decode_simple_number(fluf_cbor_ll_decoder_t *ctx, + fluf_cbor_ll_number_t *out_value) { + int result = ensure_value_or_error_available(ctx); + if (result) { + return result; + } + if (ctx->state == FLUF_CBOR_LL_DECODER_STATE_FINISHED) { + return FLUF_IO_ERR_LOGIC; + } + if (ctx->state != FLUF_CBOR_LL_DECODER_STATE_OK) { + return FLUF_IO_ERR_FORMAT; + } + out_value->type = ctx->current_item.value_type; + switch (out_value->type) { + case FLUF_CBOR_LL_VALUE_UINT: + return decode_uint(ctx, &out_value->value.u64); + case FLUF_CBOR_LL_VALUE_NEGATIVE_INT: + return decode_negative_int(ctx, &out_value->value.i64); + case FLUF_CBOR_LL_VALUE_FLOAT: + return decode_float(ctx, &out_value->value.f32); + case FLUF_CBOR_LL_VALUE_DOUBLE: + return decode_double(ctx, &out_value->value.f64); + default: + return FLUF_IO_ERR_FORMAT; + } +} + +static int cbor_get_bytes_size(fluf_cbor_ll_decoder_t *ctx, + size_t *out_bytes_size) { + if (ctx->state != FLUF_CBOR_LL_DECODER_STATE_OK + || (ctx->subparser_type != FLUF_CBOR_LL_SUBPARSER_NONE + && ctx->subparser_type != FLUF_CBOR_LL_SUBPARSER_BYTES +# ifdef FLUF_WITH_CBOR_STRING_TIME + && ctx->subparser_type != FLUF_CBOR_LL_SUBPARSER_STRING_TIME +# endif // FLUF_WITH_CBOR_STRING_TIME + ) + || (ctx->current_item.value_type != FLUF_CBOR_LL_VALUE_BYTE_STRING + && ctx->current_item.value_type + != FLUF_CBOR_LL_VALUE_TEXT_STRING)) { + return FLUF_IO_ERR_FORMAT; + } + return parse_size(ctx, out_bytes_size); +} + +static int initialize_bytes_subparser(fluf_cbor_ll_decoder_t *ctx) { + int result = ensure_value_or_error_available(ctx); + if (result) { + return result; + } + + if (ctx->state == FLUF_CBOR_LL_DECODER_STATE_FINISHED) { + return FLUF_IO_ERR_LOGIC; + } + if (ctx->state != FLUF_CBOR_LL_DECODER_STATE_OK + || (ctx->current_item.value_type != FLUF_CBOR_LL_VALUE_BYTE_STRING + && ctx->current_item.value_type + != FLUF_CBOR_LL_VALUE_TEXT_STRING)) { + return FLUF_IO_ERR_FORMAT; + } + + size_t bytes_available = 0; + if (get_additional_info(ctx->current_item.initial_byte) + == CBOR_EXT_LENGTH_INDEFINITE) { +# ifdef FLUF_WITH_CBOR_INDEFINITE_BYTES + if ((result = nested_state_push(ctx))) { + return result; + } else { + ctx->needs_preprocessing = true; + ctx->after_tag = false; + } +# else // FLUF_WITH_CBOR_INDEFINITE_BYTES + return FLUF_IO_ERR_FORMAT; +# endif // FLUF_WITH_CBOR_INDEFINITE_BYTES + } else if ((result = cbor_get_bytes_size(ctx, &bytes_available))) { + if (result < 0) { + ctx->state = FLUF_CBOR_LL_DECODER_STATE_ERROR; + } + return result; + } + + ctx->subparser.bytes_or_string_time.bytes_available = bytes_available; +# ifdef FLUF_WITH_CBOR_INDEFINITE_BYTES + ctx->subparser.bytes_or_string_time.initial_nesting_level = + ctx->nest_stack_size; + ctx->subparser.bytes_or_string_time.indefinite = + (get_additional_info(ctx->current_item.initial_byte) + == CBOR_EXT_LENGTH_INDEFINITE); +# endif // FLUF_WITH_CBOR_INDEFINITE_BYTES + return 0; +} + +# ifdef FLUF_WITH_CBOR_STRING_TIME +static int64_t year_to_days(uint16_t year, bool *out_is_leap) { + // NOTE: Gregorian calendar rules are used proleptically here, which means + // that dates before 1583 will not align with historical documents. Negative + // dates handling might also be confusing (i.e. year == -1 means 2 BC). + // + // These rules are, however, consistent with the ISO 8601 convention that + // ASN.1 GeneralizedTime type references, not to mention that X.509 + // certificates are generally not expected to contain dates before 1583 ;) + + static const int64_t LEAP_YEARS_IN_CYCLE = 97; + static const int64_t LEAP_YEARS_UNTIL_1970 = 478; + + *out_is_leap = ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0); + + uint8_t cycles = (uint8_t) (year / 400); + uint16_t years_since_cycle_start = year % 400; + + int leap_years_since_cycle_start = (*out_is_leap ? 0 : 1) + + years_since_cycle_start / 4 + - years_since_cycle_start / 100; + int64_t leap_years_since_1970 = cycles * LEAP_YEARS_IN_CYCLE + + leap_years_since_cycle_start + - LEAP_YEARS_UNTIL_1970; + return (year - 1970) * 365 + leap_years_since_1970; +} + +static int month_to_days(uint8_t month, bool is_leap) { + static const uint16_t MONTH_LENGTHS[] = { + 31, 28 /* or 29 */, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 + }; + int days = (is_leap && month > 2) ? 1 : 0; + for (int i = 0; i < month - 1; ++i) { + days += MONTH_LENGTHS[i]; + } + return days; +} + +static int64_t +convert_date_midnight_utc(uint16_t year, uint8_t month, uint8_t day) { + bool is_leap; + int64_t result = year_to_days(year, &is_leap); + result += month_to_days(month, is_leap); + result += day - 1; + return result * 86400; +} + +static int parse_time_string(fluf_cbor_ll_number_t *out_value, + const char *time_string) { + if (!isdigit(time_string[0]) || !isdigit(time_string[1]) + || !isdigit(time_string[2]) || !isdigit(time_string[3]) + || time_string[4] != '-') { + return FLUF_IO_ERR_FORMAT; + } + uint16_t year = + (uint16_t) ((time_string[0] - '0') * 1000 + + (time_string[1] - '0') * 100 + + (time_string[2] - '0') * 10 + (time_string[3] - '0')); + if (!isdigit(time_string[5]) || !isdigit(time_string[6]) + || time_string[7] != '-') { + return FLUF_IO_ERR_FORMAT; + } + uint8_t month = + (uint8_t) ((time_string[5] - '0') * 10 + (time_string[6] - '0')); + if (month < 1 || month > 12 || !isdigit(time_string[8]) + || !isdigit(time_string[9]) + || (time_string[10] != 'T' && time_string[10] != 't')) { + return FLUF_IO_ERR_FORMAT; + } + uint8_t day = + (uint8_t) ((time_string[8] - '0') * 10 + (time_string[9] - '0')); + if (day < 1 || day > 31 || !isdigit(time_string[11]) + || !isdigit(time_string[12]) || time_string[13] != ':') { + return FLUF_IO_ERR_FORMAT; + } + int64_t timestamp = convert_date_midnight_utc(year, month, day); + uint8_t hour = + (uint8_t) ((time_string[11] - '0') * 10 + (time_string[12] - '0')); + if (hour > 23 || !isdigit(time_string[14]) || !isdigit(time_string[15]) + || time_string[16] != ':') { + return FLUF_IO_ERR_FORMAT; + } + timestamp += (int64_t) hour * 3600; + uint8_t minute = + (uint8_t) ((time_string[14] - '0') * 10 + (time_string[15] - '0')); + if (minute > 59 || !isdigit(time_string[17]) || !isdigit(time_string[18])) { + return FLUF_IO_ERR_FORMAT; + } + timestamp += (int64_t) minute * 60; + uint8_t second = + (uint8_t) ((time_string[17] - '0') * 10 + (time_string[18] - '0')); + if (second > 60) { + return FLUF_IO_ERR_FORMAT; + } + timestamp += second; + uint32_t nanosecond = 0; + size_t index = 19; + size_t ns_digits = 0; + if (time_string[index] == '.') { + ++index; + while (ns_digits < 9 && isdigit(time_string[index])) { + nanosecond = + (nanosecond * 10) + (uint32_t) (time_string[index] - '0'); + ++index; + ++ns_digits; + } + while (ns_digits++ < 9) { + nanosecond *= 10; + } + } + int32_t tzoffset_seconds_east = 0; + if (time_string[index] == 'Z' || time_string[index] == 'z') { + ++index; + } else { + if ((time_string[index] != '+' && time_string[index] != '-') + || !isdigit(time_string[index + 1]) + || !isdigit(time_string[index + 2]) + || time_string[index + 3] != ':' + || !isdigit(time_string[index + 4]) + || !isdigit(time_string[index + 5])) { + return FLUF_IO_ERR_FORMAT; + } + uint8_t tzoffset_hours = (uint8_t) ((time_string[index + 1] - '0') * 10 + + (time_string[index + 2] - '0')); + uint8_t tzoffset_minutes = + (uint8_t) ((time_string[index + 4] - '0') * 10 + + (time_string[index + 5] - '0')); + if (tzoffset_minutes > 59) { + return FLUF_IO_ERR_FORMAT; + } + tzoffset_seconds_east = (int32_t) tzoffset_hours * 3600 + + (int32_t) tzoffset_minutes * 60; + if (time_string[index] == '-') { + tzoffset_seconds_east = -tzoffset_seconds_east; + } + index += 6; + } + if (time_string[index]) { + return FLUF_IO_ERR_FORMAT; + } + timestamp -= tzoffset_seconds_east; + if (nanosecond) { + out_value->type = FLUF_CBOR_LL_VALUE_DOUBLE; + out_value->value.f64 = (double) timestamp + (double) nanosecond / 1.0e9; + } else if (timestamp >= 0) { + out_value->type = FLUF_CBOR_LL_VALUE_UINT; + out_value->value.u64 = (uint64_t) timestamp; + } else { + out_value->type = FLUF_CBOR_LL_VALUE_NEGATIVE_INT; + out_value->value.i64 = timestamp; + } + return 0; +} +# endif // FLUF_WITH_CBOR_STRING_TIME + +static int decode_timestamp(fluf_cbor_ll_decoder_t *ctx, + fluf_cbor_ll_number_t *out_value) { + if (ctx->state != FLUF_CBOR_LL_DECODER_STATE_OK) { + return FLUF_IO_ERR_LOGIC; + } + assert(!ctx->needs_preprocessing); + + if (ctx->subparser_type == FLUF_CBOR_LL_SUBPARSER_NONE) { +# ifdef FLUF_WITH_CBOR_STRING_TIME + if (get_additional_info(ctx->current_item.initial_byte) + == CBOR_DECODER_TAG_STRING_TIME) { + memset(&ctx->subparser.bytes_or_string_time, 0, + sizeof(ctx->subparser.bytes_or_string_time)); + ctx->subparser_type = FLUF_CBOR_LL_SUBPARSER_STRING_TIME; + ctx->needs_preprocessing = true; + ctx->after_tag = true; + } else +# endif // FLUF_WITH_CBOR_STRING_TIME + { + assert(get_additional_info(ctx->current_item.initial_byte) + == CBOR_DECODER_TAG_EPOCH_BASED_TIME); + ctx->subparser_type = FLUF_CBOR_LL_SUBPARSER_EPOCH_BASED_TIME; + ctx->needs_preprocessing = true; + ctx->after_tag = true; + } + } + + int result; + switch (ctx->subparser_type) { +# ifdef FLUF_WITH_CBOR_STRING_TIME + case FLUF_CBOR_LL_SUBPARSER_STRING_TIME: { + if (!ctx->subparser.bytes_or_string_time.string_time.initialized) { + if ((result = initialize_bytes_subparser(ctx))) { + return result; + } + if (get_major_type(ctx->current_item.initial_byte) + != CBOR_MAJOR_TYPE_TEXT_STRING) { + ctx->state = FLUF_CBOR_LL_DECODER_STATE_ERROR; + return FLUF_IO_ERR_FORMAT; + } + ctx->subparser.bytes_or_string_time.string_time.initialized = true; + } + bool message_finished = false; + while (!message_finished) { + const void *buf; + size_t buf_size; + if ((result = fluf_cbor_ll_decoder_bytes_get_some( + &ctx->subparser.bytes_or_string_time, &buf, &buf_size, + &message_finished))) { + return result; + } + if (buf_size) { + if (ctx->subparser.bytes_or_string_time.string_time.bytes_read + + buf_size + >= sizeof(ctx->subparser.bytes_or_string_time + .string_time.buffer)) { + ctx->state = FLUF_CBOR_LL_DECODER_STATE_ERROR; + return FLUF_IO_ERR_FORMAT; + } + memcpy(ctx->subparser.bytes_or_string_time.string_time.buffer + + ctx->subparser.bytes_or_string_time.string_time + .bytes_read, + buf, + buf_size); + ctx->subparser.bytes_or_string_time.string_time.bytes_read += + buf_size; + } + } + // after message_finished, fluf_cbor_ll_decoder_bytes_get_some() will + // reset subparser_type to FLUF_CBOR_LL_SUBPARSER_NONE + assert(ctx->subparser_type == FLUF_CBOR_LL_SUBPARSER_NONE); + assert(ctx->subparser.bytes_or_string_time.string_time.bytes_read + < sizeof(ctx->subparser.bytes_or_string_time.string_time + .buffer)); + ctx->subparser.bytes_or_string_time.string_time + .buffer[ctx->subparser.bytes_or_string_time.string_time + .bytes_read] = '\0'; + if ((result = parse_time_string( + out_value, + ctx->subparser.bytes_or_string_time.string_time.buffer))) { + ctx->state = FLUF_CBOR_LL_DECODER_STATE_ERROR; + } + return result; + } +# endif // FLUF_WITH_CBOR_STRING_TIME + case FLUF_CBOR_LL_SUBPARSER_EPOCH_BASED_TIME: { + if (!(result = decode_simple_number(ctx, out_value))) { + ctx->subparser_type = FLUF_CBOR_LL_SUBPARSER_NONE; + } + return result; + } + default: { + AVS_UNREACHABLE("Invalid subparser type"); + return FLUF_IO_ERR_LOGIC; + } + } +} + +# ifdef FLUF_WITH_CBOR_INDEFINITE_BYTES +static int try_preprocess_next_bytes_chunk(fluf_cbor_ll_decoder_t *ctx, + bool *out_message_finished) { +# ifdef FLUF_WITH_CBOR_STRING_TIME + assert(ctx->subparser_type == FLUF_CBOR_LL_SUBPARSER_BYTES + || ctx->subparser_type == FLUF_CBOR_LL_SUBPARSER_STRING_TIME); +# else // FLUF_WITH_CBOR_STRING_TIME + assert(ctx->subparser_type == FLUF_CBOR_LL_SUBPARSER_BYTES); +# endif // FLUF_WITH_CBOR_STRING_TIME + assert(ctx->subparser.bytes_or_string_time.indefinite); + assert(!ctx->subparser.bytes_or_string_time.bytes_available); + int result = ensure_value_or_error_available(ctx); + if (result) { + return result; + } + if (ctx->subparser.bytes_or_string_time.initial_nesting_level + == ctx->nest_stack_size) { + if ((result = cbor_get_bytes_size( + ctx, &ctx->subparser.bytes_or_string_time.bytes_available)) + < 0) { + ctx->state = FLUF_CBOR_LL_DECODER_STATE_ERROR; + } + *out_message_finished = false; + return result; + } else { + *out_message_finished = true; + return 0; + } +} +# endif // FLUF_WITH_CBOR_INDEFINITE_BYTES + +static int bytes_get_some_impl(fluf_cbor_ll_decoder_bytes_ctx_t *bytes_ctx, + const void **out_buf, + size_t *out_buf_size, + bool *out_message_finished) { + assert(bytes_ctx); + fluf_cbor_ll_decoder_t *ctx = + AVS_CONTAINER_OF(bytes_ctx, fluf_cbor_ll_decoder_t, subparser); + if (ctx->subparser_type != FLUF_CBOR_LL_SUBPARSER_BYTES +# ifdef FLUF_WITH_CBOR_STRING_TIME + && ctx->subparser_type != FLUF_CBOR_LL_SUBPARSER_STRING_TIME +# endif // FLUF_WITH_CBOR_STRING_TIME + ) { + return FLUF_IO_ERR_LOGIC; + } + + *out_message_finished = false; +# ifdef FLUF_WITH_CBOR_INDEFINITE_BYTES + int result = 0; + if (ctx->state == FLUF_CBOR_LL_DECODER_STATE_OK && bytes_ctx->indefinite + && !ctx->subparser.bytes_or_string_time.bytes_available + && (result = try_preprocess_next_bytes_chunk( + ctx, out_message_finished))) { + return result; + } + + if (*out_message_finished) { + *out_buf = NULL; + *out_buf_size = 0; + } else +# endif // FLUF_WITH_CBOR_INDEFINITE_BYTES + { + if (ctx->prebuffer_size > ctx->prebuffer_offset) { + size_t prebuffered_bytes = + ctx->prebuffer_size - ctx->prebuffer_offset; + assert(ctx->input >= ctx->input_begin); + size_t can_rewind_by = (size_t) (ctx->input - ctx->input_begin); + if (can_rewind_by < prebuffered_bytes) { + // Can't "unbuffer everything" - next payload already provided + // return the prebuffer + *out_buf = ctx->prebuffer + ctx->prebuffer_offset; + *out_buf_size = + AVS_MIN(prebuffered_bytes, bytes_ctx->bytes_available); + ctx->prebuffer_offset += (uint8_t) *out_buf_size; + goto finish; + } else { + // Rewind already prebuffered bytes and then continue + ctx->prebuffer_size = ctx->prebuffer_offset; + ctx->input -= prebuffered_bytes; + } + } + assert(ctx->prebuffer_offset == ctx->prebuffer_size); + *out_buf = ctx->input; + assert(ctx->input_end >= ctx->input); + *out_buf_size = AVS_MIN((size_t) (ctx->input_end - ctx->input), + bytes_ctx->bytes_available); + ctx->input += *out_buf_size; + finish: + bytes_ctx->bytes_available -= *out_buf_size; + if (!bytes_ctx->bytes_available) { +# ifdef FLUF_WITH_CBOR_INDEFINITE_BYTES + *out_message_finished = + !ctx->subparser.bytes_or_string_time.indefinite; +# else // FLUF_WITH_CBOR_INDEFINITE_BYTES + *out_message_finished = true; +# endif // FLUF_WITH_CBOR_INDEFINITE_BYTES + ctx->needs_preprocessing = true; + ctx->after_tag = false; + } else { + *out_message_finished = false; + if (!*out_buf_size) { + return FLUF_IO_WANT_NEXT_PAYLOAD; + } + } + } + if (*out_message_finished) { + ctx->subparser_type = FLUF_CBOR_LL_SUBPARSER_NONE; + } + return 0; +} + +void fluf_cbor_ll_decoder_init(fluf_cbor_ll_decoder_t *ctx) { + memset(ctx, 0, sizeof(*ctx)); + ctx->state = FLUF_CBOR_LL_DECODER_STATE_OK; + ctx->needs_preprocessing = true; + ctx->after_tag = false; +} + +int fluf_cbor_ll_decoder_feed_payload(fluf_cbor_ll_decoder_t *ctx, + const void *buff, + size_t buff_len, + bool payload_finished) { + if (ctx->input != ctx->input_end || ctx->input_last) { + return FLUF_IO_ERR_LOGIC; + } + ctx->input_begin = (const uint8_t *) buff; + ctx->input = ctx->input_begin; + ctx->input_end = ctx->input_begin; + if (buff_len) { + // NOTE: if buff_len is 0, then buff == NULL is technically legal + // but UBSan complains about adding a zero offset to a null pointer + ctx->input_end += buff_len; + } + ctx->input_last = payload_finished; + return 0; +} + +int fluf_cbor_ll_decoder_errno(fluf_cbor_ll_decoder_t *ctx) { + int result = ensure_value_or_error_available(ctx); + if (result) { + return result; + } + switch (ctx->state) { + case FLUF_CBOR_LL_DECODER_STATE_OK: + return 0; + case FLUF_CBOR_LL_DECODER_STATE_FINISHED: + return FLUF_IO_EOF; + case FLUF_CBOR_LL_DECODER_STATE_ERROR: + return FLUF_IO_ERR_FORMAT; + } + AVS_UNREACHABLE("invalid fluf_cbor_ll_decoder_state_t value"); + return FLUF_IO_ERR_LOGIC; +} + +int fluf_cbor_ll_decoder_current_value_type( + fluf_cbor_ll_decoder_t *ctx, fluf_cbor_ll_value_type_t *out_type) { + int result = ensure_value_or_error_available(ctx); + if (result) { + return result; + } + if (ctx->state == FLUF_CBOR_LL_DECODER_STATE_FINISHED) { + return FLUF_IO_ERR_LOGIC; + } + if (ctx->state == FLUF_CBOR_LL_DECODER_STATE_OK) { + switch (ctx->subparser_type) { + case FLUF_CBOR_LL_SUBPARSER_NONE: + case FLUF_CBOR_LL_SUBPARSER_BYTES: + *out_type = ctx->current_item.value_type; + return 0; + case FLUF_CBOR_LL_SUBPARSER_EPOCH_BASED_TIME: +# ifdef FLUF_WITH_CBOR_STRING_TIME + case FLUF_CBOR_LL_SUBPARSER_STRING_TIME: +# endif // FLUF_WITH_CBOR_STRING_TIME + *out_type = FLUF_CBOR_LL_VALUE_TIMESTAMP; + return 0; +# ifdef FLUF_WITH_CBOR_DECIMAL_FRACTIONS + case FLUF_CBOR_LL_SUBPARSER_DECIMAL_FRACTION: + *out_type = FLUF_CBOR_LL_VALUE_DOUBLE; + return 0; +# endif // FLUF_WITH_CBOR_DECIMAL_FRACTIONS + } + } + return FLUF_IO_ERR_FORMAT; +} + +int fluf_cbor_ll_decoder_null(fluf_cbor_ll_decoder_t *ctx) { + int result = ensure_value_or_error_available(ctx); + if (result) { + return result; + } + if (ctx->state == FLUF_CBOR_LL_DECODER_STATE_FINISHED) { + return FLUF_IO_ERR_LOGIC; + } + if (ctx->state != FLUF_CBOR_LL_DECODER_STATE_OK + || ctx->subparser_type != FLUF_CBOR_LL_SUBPARSER_NONE + || ctx->current_item.value_type != FLUF_CBOR_LL_VALUE_NULL) { + return FLUF_IO_ERR_FORMAT; + } + ctx->needs_preprocessing = true; + ctx->after_tag = false; + return 0; +} + +int fluf_cbor_ll_decoder_bool(fluf_cbor_ll_decoder_t *ctx, bool *out_value) { + int result = ensure_value_or_error_available(ctx); + if (result) { + return result; + } + if (ctx->state == FLUF_CBOR_LL_DECODER_STATE_FINISHED) { + return FLUF_IO_ERR_LOGIC; + } + if (ctx->state != FLUF_CBOR_LL_DECODER_STATE_OK + || ctx->subparser_type != FLUF_CBOR_LL_SUBPARSER_NONE + || ctx->current_item.value_type != FLUF_CBOR_LL_VALUE_BOOL) { + return FLUF_IO_ERR_FORMAT; + } + switch (get_additional_info(ctx->current_item.initial_byte)) { + case CBOR_VALUE_BOOL_FALSE: + *out_value = false; + break; + case CBOR_VALUE_BOOL_TRUE: + *out_value = true; + break; + default: + AVS_UNREACHABLE("expected boolean, but got something else instead"); + return FLUF_IO_ERR_LOGIC; + } + ctx->needs_preprocessing = true; + ctx->after_tag = false; + return 0; +} + +int fluf_cbor_ll_decoder_number(fluf_cbor_ll_decoder_t *ctx, + fluf_cbor_ll_number_t *out_value) { + int result = ensure_value_or_error_available(ctx); + if (result) { + return result; + } + if (ctx->state == FLUF_CBOR_LL_DECODER_STATE_FINISHED) { + return FLUF_IO_ERR_LOGIC; + } + if (ctx->state != FLUF_CBOR_LL_DECODER_STATE_OK) { + return FLUF_IO_ERR_FORMAT; + } + out_value->type = (fluf_cbor_ll_value_type_t) -1; + switch (ctx->subparser_type) { + case FLUF_CBOR_LL_SUBPARSER_NONE: + if (ctx->current_item.value_type != FLUF_CBOR_LL_VALUE_TIMESTAMP) { + return decode_simple_number(ctx, out_value); + } + // fall through + case FLUF_CBOR_LL_SUBPARSER_EPOCH_BASED_TIME: +# ifdef FLUF_WITH_CBOR_STRING_TIME + case FLUF_CBOR_LL_SUBPARSER_STRING_TIME: +# endif // FLUF_WITH_CBOR_STRING_TIME + return decode_timestamp(ctx, out_value); +# ifdef FLUF_WITH_CBOR_DECIMAL_FRACTIONS + case FLUF_CBOR_LL_SUBPARSER_DECIMAL_FRACTION: + out_value->type = FLUF_CBOR_LL_VALUE_DOUBLE; + return decode_decimal_fraction(ctx, &out_value->value.f64); +# endif // FLUF_WITH_CBOR_DECIMAL_FRACTIONS + case FLUF_CBOR_LL_SUBPARSER_BYTES:; + } + return FLUF_IO_ERR_LOGIC; +} + +int fluf_cbor_ll_decoder_bytes(fluf_cbor_ll_decoder_t *ctx, + fluf_cbor_ll_decoder_bytes_ctx_t **out_bytes_ctx, + ptrdiff_t *out_total_size) { + *out_bytes_ctx = NULL; + if (ctx->subparser_type != FLUF_CBOR_LL_SUBPARSER_NONE) { + return FLUF_IO_ERR_FORMAT; + } + int result = initialize_bytes_subparser(ctx); + if (!result) { + ctx->subparser_type = FLUF_CBOR_LL_SUBPARSER_BYTES; + *out_bytes_ctx = &ctx->subparser.bytes_or_string_time; + if (out_total_size) { + if ( +# ifdef FLUF_WITH_CBOR_INDEFINITE_BYTES + ctx->subparser.bytes_or_string_time.indefinite || +# endif // FLUF_WITH_CBOR_INDEFINITE_BYTES + ctx->subparser.bytes_or_string_time.bytes_available + > SIZE_MAX / 2) { + *out_total_size = FLUF_CBOR_LL_DECODER_ITEMS_INDEFINITE; + } else { + *out_total_size = (ptrdiff_t) ctx->subparser + .bytes_or_string_time.bytes_available; + } + } + } + return result; +} + +int fluf_cbor_ll_decoder_bytes_get_some( + fluf_cbor_ll_decoder_bytes_ctx_t *bytes_ctx, + const void **out_buf, + size_t *out_buf_size, + bool *out_message_finished) { + int result; + do { + result = bytes_get_some_impl(bytes_ctx, out_buf, out_buf_size, + out_message_finished); + // Empty blocks may happen in an indefinite length bytes block - + // don't return them to the user as they are useless. + } while (!result && !*out_buf_size && !*out_message_finished); + return result; +} + +# if FLUF_MAX_CBOR_NEST_STACK_SIZE > 0 +int fluf_cbor_ll_decoder_enter_array(fluf_cbor_ll_decoder_t *ctx, + ptrdiff_t *out_size) { + int result = ensure_value_or_error_available(ctx); + if (result) { + return result; + } + if (ctx->state == FLUF_CBOR_LL_DECODER_STATE_FINISHED) { + return FLUF_IO_ERR_LOGIC; + } + if (ctx->state != FLUF_CBOR_LL_DECODER_STATE_OK + || ctx->subparser_type != FLUF_CBOR_LL_SUBPARSER_NONE + || ctx->current_item.value_type != FLUF_CBOR_LL_VALUE_ARRAY) { + return FLUF_IO_ERR_FORMAT; + } + if ((result = nested_state_push(ctx))) { + return result; + } + ctx->needs_preprocessing = true; + ctx->after_tag = false; + if (out_size) { + *out_size = nested_state_top(ctx)->all_items; + } + return 0; +} + +int fluf_cbor_ll_decoder_enter_map(fluf_cbor_ll_decoder_t *ctx, + ptrdiff_t *out_pair_count) { + int result = ensure_value_or_error_available(ctx); + if (result) { + return result; + } + if (ctx->state == FLUF_CBOR_LL_DECODER_STATE_FINISHED) { + return FLUF_IO_ERR_LOGIC; + } + if (ctx->state != FLUF_CBOR_LL_DECODER_STATE_OK + || ctx->subparser_type != FLUF_CBOR_LL_SUBPARSER_NONE + || ctx->current_item.value_type != FLUF_CBOR_LL_VALUE_MAP) { + return FLUF_IO_ERR_FORMAT; + } + if ((result = nested_state_push(ctx))) { + return result; + } + ctx->needs_preprocessing = true; + ctx->after_tag = false; + if (out_pair_count) { + *out_pair_count = nested_state_top(ctx)->all_items; + if (*out_pair_count > 0) { + *out_pair_count /= 2; + } + } + return 0; +} + +int fluf_cbor_ll_decoder_nesting_level(fluf_cbor_ll_decoder_t *ctx, + size_t *out_nesting_level) { + int result = ensure_value_or_error_available(ctx); + if (result) { + return result; + } + if (ctx->state != FLUF_CBOR_LL_DECODER_STATE_OK) { + *out_nesting_level = 0; + return 0; + } + switch (ctx->subparser_type) { +# ifdef FLUF_WITH_CBOR_STRING_TIME + case FLUF_CBOR_LL_SUBPARSER_STRING_TIME: + if (!ctx->subparser.bytes_or_string_time.string_time.initialized) { + *out_nesting_level = ctx->nest_stack_size; + return 0; + } +# endif // FLUF_WITH_CBOR_STRING_TIME + // fall through + case FLUF_CBOR_LL_SUBPARSER_BYTES: +# ifdef FLUF_WITH_CBOR_INDEFINITE_BYTES + if (ctx->subparser.bytes_or_string_time.indefinite) { + *out_nesting_level = + ctx->subparser.bytes_or_string_time.initial_nesting_level + - 1; + return 0; + } +# endif // FLUF_WITH_CBOR_INDEFINITE_BYTES + // fall through + case FLUF_CBOR_LL_SUBPARSER_NONE: + case FLUF_CBOR_LL_SUBPARSER_EPOCH_BASED_TIME: + *out_nesting_level = ctx->nest_stack_size; + return 0; +# ifdef FLUF_WITH_CBOR_DECIMAL_FRACTIONS + case FLUF_CBOR_LL_SUBPARSER_DECIMAL_FRACTION: + *out_nesting_level = ctx->subparser.decimal_fraction.array_level - 1; + return 0; +# endif // FLUF_WITH_CBOR_DECIMAL_FRACTIONS + } + return FLUF_IO_ERR_LOGIC; +} +# endif // FLUF_MAX_CBOR_NEST_STACK_SIZE > 0 + +#endif // defined(FLUF_WITH_SENML_CBOR) || defined(FLUF_WITH_LWM2M_CBOR) || + // defined(FLUF_WITH_CBOR) diff --git a/src/fluf/fluf_cbor_encoder.c b/src/fluf/fluf_cbor_encoder.c new file mode 100644 index 00000000..b074cb00 --- /dev/null +++ b/src/fluf/fluf_cbor_encoder.c @@ -0,0 +1,145 @@ +/* + * Copyright 2023-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 "fluf_cbor_encoder.h" +#include "fluf_internal.h" + +// HACK: +// The size of the internal_buff has been calculated so that a +// single record never exceeds its size. +static int prepare_payload(const fluf_io_out_entry_t *entry, + fluf_io_buff_t *buff_ctx) { + switch (entry->type) { + case FLUF_DATA_TYPE_BYTES: { + if (entry->value.bytes_or_string.offset != 0 + || (entry->value.bytes_or_string.full_length_hint + && entry->value.bytes_or_string.full_length_hint + != entry->value.bytes_or_string.chunk_length)) { + return FLUF_IO_ERR_INPUT_ARG; + } + buff_ctx->bytes_in_internal_buff = fluf_cbor_ll_bytes_begin( + buff_ctx->internal_buff, + entry->value.bytes_or_string.chunk_length); + buff_ctx->is_extended_type = true; + buff_ctx->remaining_bytes = entry->value.bytes_or_string.chunk_length; + break; + } + case FLUF_DATA_TYPE_STRING: { + if (entry->value.bytes_or_string.offset != 0 + || (entry->value.bytes_or_string.full_length_hint + && entry->value.bytes_or_string.full_length_hint + != entry->value.bytes_or_string.chunk_length)) { + return FLUF_IO_ERR_INPUT_ARG; + } + size_t string_length = entry->value.bytes_or_string.chunk_length; + if (!string_length && entry->value.bytes_or_string.data + && *(const char *) entry->value.bytes_or_string.data) { + string_length = + strlen((const char *) entry->value.bytes_or_string.data); + } + buff_ctx->bytes_in_internal_buff = + fluf_cbor_ll_string_begin(buff_ctx->internal_buff, + string_length); + buff_ctx->is_extended_type = true; + buff_ctx->remaining_bytes = string_length; + break; + } + case FLUF_DATA_TYPE_EXTERNAL_BYTES: { + buff_ctx->bytes_in_internal_buff = + fluf_cbor_ll_bytes_begin(buff_ctx->internal_buff, + entry->value.external_data.length); + buff_ctx->is_extended_type = true; + buff_ctx->remaining_bytes = entry->value.external_data.length; + break; + } + case FLUF_DATA_TYPE_EXTERNAL_STRING: { + buff_ctx->bytes_in_internal_buff = + fluf_cbor_ll_string_begin(buff_ctx->internal_buff, + entry->value.external_data.length); + buff_ctx->is_extended_type = true; + buff_ctx->remaining_bytes = entry->value.external_data.length; + break; + } + case FLUF_DATA_TYPE_TIME: { + // time tag acording to [RFC8949 3.4.2] + buff_ctx->bytes_in_internal_buff = + fluf_cbor_ll_encode_tag(buff_ctx->internal_buff, + CBOR_TAG_INTEGER_DATE_TIME); + assert(buff_ctx->bytes_in_internal_buff + + FLUF_CBOR_LL_SINGLE_CALL_MAX_LEN + <= _FLUF_IO_CBOR_SIMPLE_RECORD_MAX_LENGTH); + buff_ctx->bytes_in_internal_buff += fluf_cbor_ll_encode_int( + &buff_ctx->internal_buff[buff_ctx->bytes_in_internal_buff], + entry->value.time_value); + break; + } + case FLUF_DATA_TYPE_INT: { + buff_ctx->bytes_in_internal_buff = + fluf_cbor_ll_encode_int(buff_ctx->internal_buff, + entry->value.int_value); + break; + } + case FLUF_DATA_TYPE_DOUBLE: { + buff_ctx->bytes_in_internal_buff = + fluf_cbor_ll_encode_double(buff_ctx->internal_buff, + entry->value.double_value); + break; + } + case FLUF_DATA_TYPE_BOOL: { + buff_ctx->bytes_in_internal_buff = + fluf_cbor_ll_encode_bool(buff_ctx->internal_buff, + entry->value.bool_value); + break; + } + case FLUF_DATA_TYPE_OBJLNK: { + buff_ctx->bytes_in_internal_buff = _fluf_io_out_add_objlink( + buff_ctx, 0, entry->value.objlnk.oid, entry->value.objlnk.iid); + break; + } + case FLUF_DATA_TYPE_UINT: { + buff_ctx->bytes_in_internal_buff = + fluf_cbor_ll_encode_uint(buff_ctx->internal_buff, + entry->value.uint_value); + break; + } + default: { return FLUF_IO_ERR_IO_TYPE; } + } + assert(buff_ctx->bytes_in_internal_buff <= _FLUF_IO_CTX_BUFFER_LENGTH); + buff_ctx->remaining_bytes += buff_ctx->bytes_in_internal_buff; + return 0; +} + +int _fluf_cbor_out_ctx_new_entry(fluf_io_out_ctx_t *ctx, + const fluf_io_out_entry_t *entry) { + assert(ctx->_format == FLUF_COAP_FORMAT_CBOR); + + if (ctx->_encoder._cbor.entry_added) { + return FLUF_IO_ERR_LOGIC; + } + + int res = prepare_payload(entry, &ctx->_buff); + if (res) { + return res; + } + ctx->_encoder._cbor.entry_added = true; + return 0; +} + +int _fluf_cbor_encoder_init(fluf_io_out_ctx_t *ctx) { + ctx->_encoder._cbor.entry_added = false; + return 0; +} diff --git a/src/fluf/fluf_cbor_encoder.h b/src/fluf/fluf_cbor_encoder.h new file mode 100644 index 00000000..b9de6b58 --- /dev/null +++ b/src/fluf/fluf_cbor_encoder.h @@ -0,0 +1,40 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#ifndef FLUF_CBOR_ENCODER_H +#define FLUF_CBOR_ENCODER_H + +#include +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int _fluf_cbor_encoder_init(fluf_io_out_ctx_t *ctx); + +int _fluf_cbor_out_ctx_new_entry(fluf_io_out_ctx_t *ctx, + const fluf_io_out_entry_t *entry); + +int _fluf_senml_cbor_encoder_init(fluf_io_out_ctx_t *ctx, + const fluf_uri_path_t *base_path, + size_t items_count, + bool encode_time); + +int _fluf_senml_cbor_out_ctx_new_entry(fluf_io_out_ctx_t *ctx, + const fluf_io_out_entry_t *entry); + +#ifdef __cplusplus +} +#endif + +#endif // FLUF_CBOR_ENCODER_H diff --git a/src/fluf/fluf_cbor_encoder_ll.c b/src/fluf/fluf_cbor_encoder_ll.c new file mode 100644 index 00000000..e5eb9ff2 --- /dev/null +++ b/src/fluf/fluf_cbor_encoder_ll.c @@ -0,0 +1,138 @@ +/* + * Copyright 2023-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 "fluf_internal.h" + +static void +write_to_buffer(void *buffer, const void *data, size_t data_length) { + memcpy(buffer, data, data_length); +} + +static size_t +write_cbor_header(void *buffer, cbor_major_type_t major_type, uint8_t value) { + uint8_t header = (uint8_t) ((((uint8_t) major_type) << 5) | value); + write_to_buffer(buffer, &header, 1); + return 1; +} + +static size_t encode_type_and_number(void *buffer, + cbor_major_type_t major_type, + uint64_t value) { + size_t bytes_written = 1; + if (value < 24) { + write_cbor_header(buffer, major_type, (uint8_t) value); + } else if (value <= UINT8_MAX) { + uint8_t portable = (uint8_t) value; + write_cbor_header(buffer, major_type, CBOR_EXT_LENGTH_1BYTE); + write_to_buffer(&((uint8_t *) buffer)[bytes_written], &portable, + sizeof(portable)); + bytes_written += sizeof(portable); + } else if (value <= UINT16_MAX) { + uint16_t portable = avs_convert_be16((uint16_t) value); + write_cbor_header(buffer, major_type, CBOR_EXT_LENGTH_2BYTE); + write_to_buffer(&((uint8_t *) buffer)[bytes_written], &portable, + sizeof(portable)); + bytes_written += sizeof(portable); + } else if (value <= UINT32_MAX) { + uint32_t portable = avs_convert_be32((uint32_t) value); + write_cbor_header(buffer, major_type, CBOR_EXT_LENGTH_4BYTE); + write_to_buffer(&((uint8_t *) buffer)[bytes_written], &portable, + sizeof(portable)); + bytes_written += sizeof(portable); + } else { + uint64_t portable = avs_convert_be64((uint64_t) value); + write_cbor_header(buffer, major_type, CBOR_EXT_LENGTH_8BYTE); + write_to_buffer(&((uint8_t *) buffer)[bytes_written], &portable, + sizeof(portable)); + bytes_written += sizeof(portable); + } + return bytes_written; +} + +size_t fluf_cbor_ll_encode_uint(void *buffer, uint64_t value) { + return encode_type_and_number(buffer, CBOR_MAJOR_TYPE_UINT, value); +} + +size_t fluf_cbor_ll_encode_int(void *buffer, int64_t value) { + if (value >= 0) { + return fluf_cbor_ll_encode_uint(buffer, (uint64_t) value); + } + + value = -(value + 1); + return encode_type_and_number(buffer, CBOR_MAJOR_TYPE_NEGATIVE_INT, + (uint64_t) value); +} + +size_t fluf_cbor_ll_encode_bool(void *buffer, bool value) { + return write_cbor_header(buffer, CBOR_MAJOR_TYPE_FLOAT_OR_SIMPLE_VALUE, + value ? CBOR_VALUE_BOOL_TRUE + : CBOR_VALUE_BOOL_FALSE); +} + +size_t fluf_cbor_ll_encode_float(void *buffer, float value) { + uint32_t portable = avs_htonf(value); + size_t bytes_written = + write_cbor_header(buffer, CBOR_MAJOR_TYPE_FLOAT_OR_SIMPLE_VALUE, + CBOR_EXT_LENGTH_4BYTE); + write_to_buffer(&((uint8_t *) buffer)[bytes_written], &portable, + sizeof(portable)); + bytes_written += sizeof(portable); + return bytes_written; +} + +size_t fluf_cbor_ll_encode_double(void *buffer, double value) { + if (((float) value) == value) { + return fluf_cbor_ll_encode_float(buffer, (float) value); + } + + uint64_t portable = avs_htond(value); + size_t bytes_written = + write_cbor_header(buffer, CBOR_MAJOR_TYPE_FLOAT_OR_SIMPLE_VALUE, + CBOR_EXT_LENGTH_8BYTE); + write_to_buffer(&((uint8_t *) buffer)[bytes_written], &portable, + sizeof(portable)); + bytes_written += sizeof(portable); + return bytes_written; +} + +size_t fluf_cbor_ll_encode_tag(void *buff, uint64_t value) { + return encode_type_and_number(buff, CBOR_MAJOR_TYPE_TAG, value); +} + +size_t fluf_cbor_ll_string_begin(void *buffer, size_t size) { + return encode_type_and_number(buffer, CBOR_MAJOR_TYPE_TEXT_STRING, size); +} + +size_t fluf_cbor_ll_bytes_begin(void *buffer, size_t size) { + return encode_type_and_number(buffer, CBOR_MAJOR_TYPE_BYTE_STRING, size); +} + +size_t fluf_cbor_ll_definite_map_begin(void *buffer, size_t items_count) { + return encode_type_and_number(buffer, CBOR_MAJOR_TYPE_MAP, items_count); +} + +size_t fluf_cbor_ll_definite_array_begin(void *buffer, size_t items_count) { + return encode_type_and_number(buffer, CBOR_MAJOR_TYPE_ARRAY, items_count); +} + +size_t fluf_cbor_ll_indefinite_map_begin(void *buffer) { + return write_cbor_header(buffer, CBOR_MAJOR_TYPE_MAP, + CBOR_EXT_LENGTH_INDEFINITE); +} + +size_t fluf_cbor_ll_indefinite_map_end(void *buffer) { + const unsigned char break_char = CBOR_INDEFINITE_STRUCTURE_BREAK; + size_t bytes_written = sizeof(break_char); + write_to_buffer(buffer, &break_char, bytes_written); + return bytes_written; +} diff --git a/src/fluf/fluf_coap_udp_header.h b/src/fluf/fluf_coap_udp_header.h new file mode 100644 index 00000000..c493488e --- /dev/null +++ b/src/fluf/fluf_coap_udp_header.h @@ -0,0 +1,173 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#ifndef FLUF_COAP_SRC_UDP_UDP_HEADER_H +#define FLUF_COAP_SRC_UDP_UDP_HEADER_H + +#include +#include +#include + +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define _FLUF_FIELD_GET(field, mask, shift) (((field) & (mask)) >> (shift)) +#define _FLUF_FIELD_SET(field, mask, shift, value) \ + ((field) = (uint8_t) (((field) & ~(mask)) \ + | (uint8_t) (((value) << (shift)) & (mask)))) + +/** + * Magic value defined in RFC7252, used internally when constructing/parsing + * CoAP packets. + */ +#define _FLUF_COAP_PAYLOAD_MARKER ((uint8_t) { 0xFF }) + +#define _FLUF_COAP_MESSAGE_ID_LEN 2 + +typedef struct fluf_coap_udp_header { + + uint8_t version_type_token_length; + + uint8_t code; + + uint8_t message_id[_FLUF_COAP_MESSAGE_ID_LEN]; +} fluf_coap_udp_header_t; + +AVS_STATIC_ASSERT(AVS_ALIGNOF(fluf_coap_udp_header_t) == 1, + fluf_coap_udp_header_t_must_always_be_properly_aligned); + +/** @{ + * Sanity checks that ensure no padding is inserted anywhere inside + * @ref fluf_coap_udp_header_t . + */ +AVS_STATIC_ASSERT(offsetof(fluf_coap_udp_header_t, version_type_token_length) + == 0, + vttl_field_is_at_start_of_fluf_coap_udp_header_t); +AVS_STATIC_ASSERT(offsetof(fluf_coap_udp_header_t, code) == 1, + no_padding_before_code_field_of_fluf_coap_udp_header_t); +AVS_STATIC_ASSERT(offsetof(fluf_coap_udp_header_t, message_id) == 2, + no_padding_before_message_id_field_of_fluf_coap_udp_header_t); +AVS_STATIC_ASSERT(sizeof(fluf_coap_udp_header_t) == 4, + no_padding_in_fluf_coap_udp_header_t); +/** @} */ + +#define _FLUF_COAP_UDP_HEADER_VERSION_MASK 0xC0 +#define _FLUF_COAP_UDP_HEADER_VERSION_SHIFT 6 + +static inline uint8_t +_fluf_coap_udp_header_get_version(const fluf_coap_udp_header_t *hdr) { + int val = _FLUF_FIELD_GET(hdr->version_type_token_length, + _FLUF_COAP_UDP_HEADER_VERSION_MASK, + _FLUF_COAP_UDP_HEADER_VERSION_SHIFT); + assert(val >= 0 && val <= 3); + return (uint8_t) val; +} + +static inline void +_fluf_coap_udp_header_set_version(fluf_coap_udp_header_t *hdr, + uint8_t version) { + assert(version <= 3); + _FLUF_FIELD_SET(hdr->version_type_token_length, + _FLUF_COAP_UDP_HEADER_VERSION_MASK, + _FLUF_COAP_UDP_HEADER_VERSION_SHIFT, version); +} + +#define _FLUF_COAP_UDP_HEADER_TOKEN_LENGTH_MASK 0x0F +#define _FLUF_COAP_UDP_HEADER_TOKEN_LENGTH_SHIFT 0 + +static inline uint8_t +_fluf_coap_udp_header_get_token_length(const fluf_coap_udp_header_t *hdr) { + int val = _FLUF_FIELD_GET(hdr->version_type_token_length, + _FLUF_COAP_UDP_HEADER_TOKEN_LENGTH_MASK, + _FLUF_COAP_UDP_HEADER_TOKEN_LENGTH_SHIFT); + assert(val >= 0 && val <= _FLUF_COAP_UDP_HEADER_TOKEN_LENGTH_MASK); + return (uint8_t) val; +} + +static inline void +_fluf_coap_udp_header_set_token_length(fluf_coap_udp_header_t *hdr, + uint8_t token_length) { + assert(token_length <= FLUF_COAP_MAX_TOKEN_LENGTH); + _FLUF_FIELD_SET(hdr->version_type_token_length, + _FLUF_COAP_UDP_HEADER_TOKEN_LENGTH_MASK, + _FLUF_COAP_UDP_HEADER_TOKEN_LENGTH_SHIFT, token_length); +} + +#define _FLUF_COAP_UDP_HEADER_TYPE_MASK 0x30 +#define _FLUF_COAP_UDP_HEADER_TYPE_SHIFT 4 + +#define _FLUF_COAP_UDP_TYPE_FIRST FLUF_COAP_UDP_TYPE_CONFIRMABLE +#define _FLUF_COAP_UDP_TYPE_LAST FLUF_COAP_UDP_TYPE_RESET + +static inline fluf_coap_udp_type_t +_fluf_coap_udp_header_get_type(const fluf_coap_udp_header_t *hdr) { + int val = _FLUF_FIELD_GET(hdr->version_type_token_length, + _FLUF_COAP_UDP_HEADER_TYPE_MASK, + _FLUF_COAP_UDP_HEADER_TYPE_SHIFT); + assert(val >= _FLUF_COAP_UDP_TYPE_FIRST && val <= _FLUF_COAP_UDP_TYPE_LAST); + return (fluf_coap_udp_type_t) val; +} + +static inline void _fluf_coap_udp_header_set_type(fluf_coap_udp_header_t *hdr, + fluf_coap_udp_type_t type) { + _FLUF_FIELD_SET(hdr->version_type_token_length, + _FLUF_COAP_UDP_HEADER_TYPE_MASK, + _FLUF_COAP_UDP_HEADER_TYPE_SHIFT, type); +} + +static inline uint16_t extract_u16(const uint8_t *data) { + uint16_t result; + memcpy(&result, data, sizeof(uint16_t)); + return avs_convert_be16(result); +} + +static inline uint16_t +_fluf_coap_udp_header_get_id(const fluf_coap_udp_header_t *hdr) { + return extract_u16(hdr->message_id); +} + +static inline void _fluf_coap_udp_header_set_id(fluf_coap_udp_header_t *hdr, + uint16_t msg_id) { + uint16_t msg_id_nbo = avs_convert_be16(msg_id); + memcpy(hdr->message_id, &msg_id_nbo, sizeof(msg_id_nbo)); +} + +static inline void _fluf_coap_udp_header_set(fluf_coap_udp_header_t *hdr, + fluf_coap_udp_type_t type, + uint8_t token_length, + uint8_t code, + uint16_t id) { + _fluf_coap_udp_header_set_version(hdr, 1); + _fluf_coap_udp_header_set_type(hdr, type); + _fluf_coap_udp_header_set_token_length(hdr, token_length); + hdr->code = code; + _fluf_coap_udp_header_set_id(hdr, id); +} + +static inline fluf_coap_udp_header_t +_fluf_coap_udp_header_init(fluf_coap_udp_type_t type, + uint8_t token_length, + uint8_t code, + uint16_t id) { + fluf_coap_udp_header_t hdr = { 0 }; + _fluf_coap_udp_header_set(&hdr, type, token_length, code, id); + return hdr; +} + +#ifdef __cplusplus +} +#endif + +#endif // FLUF_COAP_SRC_UDP_UDP_HEADER_H diff --git a/src/fluf/fluf_coap_udp_msg.c b/src/fluf/fluf_coap_udp_msg.c new file mode 100644 index 00000000..64f117ca --- /dev/null +++ b/src/fluf/fluf_coap_udp_msg.c @@ -0,0 +1,260 @@ +/* + * Copyright 2023-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 "fluf_coap_udp_msg.h" +#include "fluf_options.h" + +typedef struct { + uint8_t *write_ptr; + size_t bytes_left; +} bytes_appender_t; + +typedef struct { + uint8_t *read_ptr; + size_t bytes_left; +} bytes_dispenser_t; + +static int +bytes_append(bytes_appender_t *appender, const void *data, size_t size_bytes) { + if (appender->bytes_left < size_bytes) { + return FLUF_ERR_BUFF; + } + memcpy(appender->write_ptr, data ? data : "", size_bytes); + appender->write_ptr += size_bytes; + appender->bytes_left -= size_bytes; + return 0; +} + +static int +bytes_extract(bytes_dispenser_t *dispenser, void *out, size_t size_bytes) { + if (dispenser->bytes_left < size_bytes) { + return FLUF_ERR_MALFORMED_MESSAGE; + } + + if (out) { + memcpy(out, dispenser->read_ptr, size_bytes); + } + + dispenser->read_ptr += size_bytes; + dispenser->bytes_left -= size_bytes; + return 0; +} + +static uint8_t code_get_class(uint8_t code) { + return (uint8_t) _FLUF_FIELD_GET(code, _FLUF_COAP_CODE_CLASS_MASK, + _FLUF_COAP_CODE_CLASS_SHIFT); +} + +static uint8_t code_get_detail(uint8_t code) { + return (uint8_t) _FLUF_FIELD_GET(code, _FLUF_COAP_CODE_DETAIL_MASK, + _FLUF_COAP_CODE_DETAIL_SHIFT); +} + +static bool fluf_coap_code_is_request(uint8_t code) { + return code_get_class(code) == 0 && code_get_detail(code) > 0; +} + +static bool is_msg_header_valid(const fluf_coap_udp_header_t *hdr) { + uint8_t version = _fluf_coap_udp_header_get_version(hdr); + if (version != 1) { + return false; + } + + if (_fluf_coap_udp_header_get_token_length(hdr) + > FLUF_COAP_MAX_TOKEN_LENGTH) { + return false; + } + + const fluf_coap_udp_type_t type = _fluf_coap_udp_header_get_type(hdr); + + switch (type) { + case FLUF_COAP_UDP_TYPE_ACKNOWLEDGEMENT: + if (fluf_coap_code_is_request(hdr->code)) { + return false; + } + break; + case FLUF_COAP_UDP_TYPE_NON_CONFIRMABLE: + // FLUF_COAP_CODE_EMPTY with FLUF_COAP_UDP_TYPE_CONFIRMABLE + // means "CoAP ping" + if (hdr->code == FLUF_COAP_CODE_EMPTY) { + return false; + } + break; + case FLUF_COAP_UDP_TYPE_RESET: + if (hdr->code != FLUF_COAP_CODE_EMPTY) { + return false; + } + break; + + default: + break; + } + + return true; +} + +static int decode_header(fluf_coap_udp_header_t *out_hdr, + bytes_dispenser_t *dispenser) { + if (bytes_extract(dispenser, out_hdr, sizeof(*out_hdr)) + || !is_msg_header_valid(out_hdr)) { + return FLUF_ERR_MALFORMED_MESSAGE; + } + + if (out_hdr->code == FLUF_COAP_CODE_EMPTY && dispenser->bytes_left > 0) { + return FLUF_ERR_MALFORMED_MESSAGE; + } + + return 0; +} + +static int decode_payload(void **out_payload, + size_t *out_payload_size, + bytes_dispenser_t *dispenser) { + *out_payload = dispenser->read_ptr; + *out_payload_size = dispenser->bytes_left; + + if (*out_payload_size == 0) { + // no payload after options + *out_payload = NULL; + return 0; + } + + // ensured by decode_options + assert(*dispenser->read_ptr == _FLUF_COAP_PAYLOAD_MARKER); + + *out_payload = dispenser->read_ptr + 1; + *out_payload_size -= 1; + + if (*out_payload_size == 0) { + return FLUF_ERR_MALFORMED_MESSAGE; + } + + return 0; +} + +static inline int decode_token(fluf_coap_udp_msg_t *out_msg, + bytes_dispenser_t *dispenser) { + uint8_t token_size = + _fluf_coap_udp_header_get_token_length(&out_msg->header); + out_msg->token.size = token_size; + + assert(token_size <= sizeof(out_msg->token.bytes)); + + if (bytes_extract(dispenser, out_msg->token.bytes, out_msg->token.size)) { + return FLUF_ERR_MALFORMED_MESSAGE; + } + + return 0; +} + +static int decode_options(fluf_coap_udp_msg_t *out_msg, + bytes_dispenser_t *dispenser) { + int res; + size_t bytes_read; + + res = _fluf_coap_options_decode(out_msg->options, dispenser->read_ptr, + dispenser->bytes_left, &bytes_read); + + dispenser->read_ptr += bytes_read; + dispenser->bytes_left -= bytes_read; + + return res; +} + +int _fluf_coap_udp_msg_decode(fluf_coap_udp_msg_t *out_msg, + void *packet, + size_t packet_size) { + bytes_dispenser_t dispenser = { + .read_ptr = (uint8_t *) packet, + .bytes_left = packet_size + }; + + int res; + res = decode_header(&out_msg->header, &dispenser); + if (res) { + return res; + } + res = decode_token(out_msg, &dispenser); + if (res) { + return res; + } + res = decode_options(out_msg, &dispenser); + if (res) { + return res; + } + res = decode_payload(&out_msg->payload, &out_msg->payload_size, &dispenser); + + return res; +} + +int _fluf_coap_udp_header_serialize(fluf_coap_udp_msg_t *msg, + uint8_t *buf, + size_t buf_size) { + assert(msg); + assert(buf); + + assert(_fluf_coap_udp_header_get_token_length(&msg->header) + == msg->token.size); + + bytes_appender_t appender = { + .write_ptr = buf, + .bytes_left = buf_size + }; + + if (bytes_append(&appender, &msg->header, sizeof(msg->header)) + || bytes_append(&appender, msg->token.bytes, msg->token.size)) { + return FLUF_ERR_BUFF; + } + msg->occupied_buff_size = buf_size - appender.bytes_left; + + // prepare options buffer + if (msg->options) { + msg->options->buff_begin = buf + msg->occupied_buff_size; + msg->options->buff_size = appender.bytes_left; + } + + return 0; +} + +int _fluf_coap_udp_msg_serialize(fluf_coap_udp_msg_t *msg, + uint8_t *buf, + size_t buf_size, + size_t *out_bytes_written) { + assert(msg); + assert(buf); + assert(out_bytes_written); + assert(((int) buf_size - (int) msg->occupied_buff_size) >= 0); + + bytes_appender_t appender; + + if (msg->options && msg->options->options_number) { + fluf_coap_option_t last_option = + msg->options->options[msg->options->options_number - 1]; + msg->occupied_buff_size += + (size_t) ((last_option.payload + last_option.payload_len) + - msg->options->buff_begin); + } + + appender.write_ptr = buf + msg->occupied_buff_size; + appender.bytes_left = buf_size - msg->occupied_buff_size; + + if (msg->payload && msg->payload_size > 0) { + if (bytes_append(&appender, &_FLUF_COAP_PAYLOAD_MARKER, + sizeof(_FLUF_COAP_PAYLOAD_MARKER)) + || bytes_append(&appender, msg->payload, msg->payload_size)) { + return FLUF_ERR_BUFF; + } + } + + *out_bytes_written = buf_size - appender.bytes_left; + + return 0; +} diff --git a/src/fluf/fluf_coap_udp_msg.h b/src/fluf/fluf_coap_udp_msg.h new file mode 100644 index 00000000..c7471988 --- /dev/null +++ b/src/fluf/fluf_coap_udp_msg.h @@ -0,0 +1,55 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#ifndef FLUF_COAP_SRC_UDP_UDP_MSG_H +#define FLUF_COAP_SRC_UDP_UDP_MSG_H + +#include +#include + +#include "fluf_coap_udp_header.h" +#include "fluf_options.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + + fluf_coap_udp_header_t header; + + fluf_coap_token_t token; + + fluf_coap_options_t *options; + + void *payload; + + size_t payload_size; + + size_t occupied_buff_size; +} fluf_coap_udp_msg_t; + +int _fluf_coap_udp_msg_decode(fluf_coap_udp_msg_t *out_msg, + void *packet, + size_t packet_size); + +int _fluf_coap_udp_header_serialize(fluf_coap_udp_msg_t *msg, + uint8_t *buf, + size_t buf_size); + +int _fluf_coap_udp_msg_serialize(fluf_coap_udp_msg_t *msg, + uint8_t *buf, + size_t buf_size, + size_t *out_bytes_written); + +#ifdef __cplusplus +} +#endif + +#endif // FLUF_COAP_SRC_UDP_UDP_MSG_H diff --git a/src/fluf/fluf_decode.c b/src/fluf/fluf_decode.c new file mode 100644 index 00000000..845c258c --- /dev/null +++ b/src/fluf/fluf_decode.c @@ -0,0 +1,378 @@ +/* + * Copyright 2023-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 "fluf_attributes.h" +#include "fluf_block.h" +#include "fluf_coap_udp_msg.h" + +#define _RET_IF_ERROR(Val) \ + if (Val) { \ + return Val; \ + } + +#define _URI_PATH_MAX_LEN_STR sizeof("65534") +#define _OBSERVE_OPTION_MAX_LEN 3 + +static int get_uri_path(fluf_coap_options_t *options, + fluf_uri_path_t *uri, + bool *is_bs_uri) { + size_t it = 0; + size_t out_option_size; + char buff[_URI_PATH_MAX_LEN_STR]; + + *is_bs_uri = false; + uri->uri_len = 0; + + while (1) { + int res = _fluf_coap_options_get_data_iterate( + options, _FLUF_COAP_OPTION_URI_PATH, &it, &out_option_size, + buff, sizeof(buff)); + if (!res) { + if (uri->uri_len == FLUF_URI_PATH_MAX_LENGTH) { + return FLUF_ERR_MALFORMED_MESSAGE; // uri path too long + } + // if `bs` in first record -> BootstrapFinish operation + if (!uri->uri_len && out_option_size == 2 && buff[0] == 'b' + && buff[1] == 's') { + *is_bs_uri = true; + return 0; + } + // empty option + if (!out_option_size) { + if (!uri->uri_len) { // empty path in first segment + return 0; + } else { + return FLUF_ERR_MALFORMED_MESSAGE; + } + } + // try to convert string value to int + uint32_t converted_value; + if (fluf_string_to_uint32_value(buff, out_option_size, + &converted_value)) { + return FLUF_ERR_MALFORMED_MESSAGE; + } + uri->ids[uri->uri_len] = (uint16_t) converted_value; + uri->uri_len++; + } else if (res == _FLUF_COAP_OPTION_MISSING) { + return 0; + } else { + return FLUF_ERR_MALFORMED_MESSAGE; + } + } +} + +static int get_location_path(fluf_coap_options_t *opt, + fluf_location_path_t *loc_path) { + memset(loc_path, 0, sizeof(fluf_location_path_t)); + bool rd_flag_read = false; + + for (size_t i = 0; i < opt->options_number; i++) { + if (opt->options[i].option_number == _FLUF_COAP_OPTION_LOCATION_PATH) { + if (!rd_flag_read) { + rd_flag_read = true; + if (opt->options[i].payload_len != 2 + || ((const char *) opt->options[i].payload)[0] != 'r' + || ((const char *) opt->options[i].payload)[1] != 'd') { + // first location path argument need to be "/rd" + return FLUF_ERR_MALFORMED_MESSAGE; + } + } else { + if (loc_path->location_count + >= FLUL_MAX_ALLOWED_LOCATION_PATHS_NUMBER) { + return FLUF_ERR_LOCATION_PATHS_NUMBER; + } + loc_path->location_len[loc_path->location_count] = + opt->options[i].payload_len; + loc_path->location[loc_path->location_count] = + (const char *) opt->options[i].payload; + loc_path->location_count++; + } + } + } + return 0; +} + +static int get_observe_option(fluf_coap_options_t *options, + bool *opt_present, + uint8_t *out_value) { + uint8_t observe_buff[_OBSERVE_OPTION_MAX_LEN] = { 0 }; + size_t observe_option_size = 0; + + *out_value = 0; + *opt_present = false; + + int res = _fluf_coap_options_get_data_iterate( + options, _FLUF_COAP_OPTION_OBSERVE, NULL, &observe_option_size, + observe_buff, sizeof(observe_buff)); + if (res == _FLUF_COAP_OPTION_MISSING) { + return 0; + } else if (res) { + return FLUF_ERR_MALFORMED_MESSAGE; + } + + *opt_present = true; + for (size_t i = 0; i < observe_option_size; i++) { + if (observe_buff[i]) { + // only two possible values `0` or `1` + // check RFC7641: https://datatracker.ietf.org/doc/rfc7641/ + *out_value = 1; + break; + } + } + return 0; +} + +static int fluf_etag_decode(fluf_coap_options_t *opts, fluf_etag_t *etag) { + size_t etag_size = 0; + int res = _fluf_coap_options_get_data_iterate(opts, _FLUF_COAP_OPTION_ETAG, + NULL, &etag_size, etag->bytes, + FLUF_MAX_ETAG_LENGTH); + if (res == _FLUF_COAP_OPTION_MISSING) { + return 0; + } + etag->size = (uint8_t) etag_size; + return res; +} + +static int validate_uri_path(fluf_op_t operation, fluf_uri_path_t *uri) { + switch (operation) { + case FLUF_OP_DM_READ: + case FLUF_OP_DM_WRITE_PARTIAL_UPDATE: + case FLUF_OP_DM_WRITE_REPLACE: + case FLUF_OP_INF_OBSERVE: + case FLUF_OP_INF_CANCEL_OBSERVE: + if (!fluf_uri_path_has(uri, FLUF_ID_OID)) { + return FLUF_ERR_INPUT_ARG; + } + break; + case FLUF_OP_DM_DISCOVER: + if (fluf_uri_path_has(uri, FLUF_ID_RIID)) { + return FLUF_ERR_INPUT_ARG; + } + break; + case FLUF_OP_DM_EXECUTE: + if (!fluf_uri_path_is(uri, FLUF_ID_RID)) { + return FLUF_ERR_INPUT_ARG; + } + break; + case FLUF_OP_DM_CREATE: + if (!fluf_uri_path_is(uri, FLUF_ID_OID)) { + return FLUF_ERR_INPUT_ARG; + } + break; + case FLUF_OP_DM_DELETE: + if (!(fluf_uri_path_is(uri, FLUF_ID_IID) + || fluf_uri_path_is(uri, FLUF_ID_RIID))) { + return FLUF_ERR_INPUT_ARG; + } + break; + default: + break; + } + return 0; +} + +static int decode_request_msg(fluf_coap_options_t *options, + fluf_data_t *data, + bool is_bs_uri, + fluf_coap_udp_type_t type) { + uint8_t observe_value; + bool observe_opt_present; + if (get_observe_option(options, &observe_opt_present, &observe_value)) { + return FLUF_ERR_MALFORMED_MESSAGE; + } + + if (type == FLUF_COAP_UDP_TYPE_NON_CONFIRMABLE) { + if (data->msg_code == FLUF_COAP_CODE_POST) { + data->operation = FLUF_OP_DM_EXECUTE; + return 0; + } + return FLUF_ERR_MALFORMED_MESSAGE; + } + + switch (data->msg_code) { + case FLUF_COAP_CODE_GET: + if (observe_opt_present) { + if (observe_value) { + data->operation = FLUF_OP_INF_CANCEL_OBSERVE; + } else { + data->operation = FLUF_OP_INF_OBSERVE; + } + } else { + if (data->accept == FLUF_COAP_FORMAT_LINK_FORMAT) { + data->operation = FLUF_OP_DM_DISCOVER; + } else { + data->operation = FLUF_OP_DM_READ; + } + } + break; + + case FLUF_COAP_CODE_POST: + if (is_bs_uri) { + data->operation = FLUF_OP_BOOTSTRAP_FINISH; + } else if (fluf_uri_path_is(&data->uri, FLUF_ID_OID)) { + data->operation = FLUF_OP_DM_CREATE; + } else if (fluf_uri_path_is(&data->uri, FLUF_ID_IID)) { + data->operation = FLUF_OP_DM_WRITE_PARTIAL_UPDATE; + } else if (fluf_uri_path_is(&data->uri, FLUF_ID_RID)) { + data->operation = FLUF_OP_DM_EXECUTE; + } else { + return FLUF_ERR_MALFORMED_MESSAGE; + } + break; + + case FLUF_COAP_CODE_FETCH: + if (observe_opt_present) { + if (observe_value) { + data->operation = FLUF_OP_INF_CANCEL_OBSERVE_COMP; + } else { + data->operation = FLUF_OP_INF_OBSERVE_COMP; + } + } else { + data->operation = FLUF_OP_DM_READ_COMP; + } + break; + + case FLUF_COAP_CODE_PUT: + if (data->content_format != FLUF_COAP_FORMAT_NOT_DEFINED) { + data->operation = FLUF_OP_DM_WRITE_REPLACE; + } else { + data->operation = FLUF_OP_DM_WRITE_ATTR; + } + break; + + case FLUF_COAP_CODE_IPATCH: + data->operation = FLUF_OP_DM_WRITE_COMP; + break; + case FLUF_COAP_CODE_DELETE: + data->operation = FLUF_OP_DM_DELETE; + break; + default: + return FLUF_ERR_MALFORMED_MESSAGE; + } + + return 0; +} + +static int decode_udp_msg(uint8_t *msg, size_t msg_size, fluf_data_t *data) { + // coap msg decode + fluf_coap_udp_msg_t coap_msg; + _FLUF_COAP_OPTIONS_INIT_EMPTY(opts, FLUF_MAX_ALLOWED_OPTIONS_NUMBER); + coap_msg.options = &opts; + int res = _fluf_coap_udp_msg_decode(&coap_msg, msg, msg_size); + _RET_IF_ERROR(res); + + data->payload = coap_msg.payload; + data->payload_size = coap_msg.payload_size; + + // copy token and message id + data->coap.coap_udp.token.size = coap_msg.token.size; + memcpy(data->coap.coap_udp.token.bytes, coap_msg.token.bytes, + coap_msg.token.size); + data->coap.coap_udp.message_id = + _fluf_coap_udp_header_get_id(&coap_msg.header); + data->coap.coap_udp.type = _fluf_coap_udp_header_get_type(&coap_msg.header); + + data->msg_code = coap_msg.header.code; + + // recognize operation + if (data->coap.coap_udp.type == FLUF_COAP_UDP_TYPE_RESET) { + data->operation = FLUF_OP_COAP_RESET; + } else if (data->coap.coap_udp.type == FLUF_COAP_UDP_TYPE_CONFIRMABLE + && data->msg_code == FLUF_COAP_CODE_EMPTY) { + data->operation = FLUF_OP_COAP_PING; + } else if (data->msg_code >= FLUF_COAP_CODE_GET + && data->msg_code <= FLUF_COAP_CODE_IPATCH + && (data->coap.coap_udp.type == FLUF_COAP_UDP_TYPE_CONFIRMABLE + || data->coap.coap_udp.type + == FLUF_COAP_UDP_TYPE_NON_CONFIRMABLE)) { + // update content format and accept option if present in message + _fluf_coap_options_get_u16_iterate(coap_msg.options, + _FLUF_COAP_OPTION_CONTENT_FORMAT, + NULL, &data->content_format); + _fluf_coap_options_get_u16_iterate(coap_msg.options, + _FLUF_COAP_OPTION_ACCEPT, NULL, + &data->accept); + + // get uri path if present + bool is_bs_uri; + res = get_uri_path(coap_msg.options, &data->uri, &is_bs_uri); + _RET_IF_ERROR(res); + // server initiated exchange + res = decode_request_msg(coap_msg.options, data, is_bs_uri, + data->coap.coap_udp.type); + _RET_IF_ERROR(res); + res = validate_uri_path(data->operation, &data->uri); + } else if (data->msg_code >= FLUF_COAP_CODE_CREATED + && data->msg_code <= FLUF_COAP_CODE_PROXYING_NOT_SUPPORTED) { + // server response + data->operation = FLUF_OP_RESPONSE; + } else if (data->msg_code == FLUF_COAP_CODE_EMPTY + && data->coap.coap_udp.type + == FLUF_COAP_UDP_TYPE_ACKNOWLEDGEMENT) { + data->operation = FLUF_OP_RESPONSE; + return 0; + } else { + res = FLUF_ERR_COAP_BAD_MSG; + } + _RET_IF_ERROR(res); + + // decode attributes + if (data->operation == FLUF_OP_DM_DISCOVER) { + res = fluf_attr_discover_decode(coap_msg.options, + &data->attr.discover_attr); + } else if (data->operation == FLUF_OP_DM_WRITE_ATTR + || data->operation == FLUF_OP_INF_OBSERVE + || data->operation == FLUF_OP_INF_OBSERVE_COMP) { + res = fluf_attr_notification_attr_decode(coap_msg.options, + &data->attr.notification_attr); + } + _RET_IF_ERROR(res); + + // decode block option if appears + res = _fluf_block_decode(coap_msg.options, &data->block); + _RET_IF_ERROR(res); + + // check etag presence + res = fluf_etag_decode(coap_msg.options, &data->etag); + _RET_IF_ERROR(res); + + // decode location-path if present + if (data->operation == FLUF_OP_RESPONSE) { + res = get_location_path(coap_msg.options, &data->location_path); + } + + return res; +} + +int fluf_msg_decode(uint8_t *msg, + size_t msg_size, + fluf_binding_type_t binding, + fluf_data_t *data) { + int res; + + data->accept = FLUF_COAP_FORMAT_NOT_DEFINED; + data->content_format = FLUF_COAP_FORMAT_NOT_DEFINED; + data->binding = binding; + + switch (data->binding) { + case FLUF_BINDING_UDP: + case FLUF_BINDING_DTLS_PSK: + res = decode_udp_msg(msg, msg_size, data); + break; + default: + res = FLUF_ERR_BINDING; + break; + } + + return res; +} diff --git a/src/fluf/fluf_discover.c b/src/fluf/fluf_discover.c new file mode 100644 index 00000000..91630ea4 --- /dev/null +++ b/src/fluf/fluf_discover.c @@ -0,0 +1,343 @@ +/* + * Copyright 2023-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 + +#include "fluf_internal.h" + +#ifndef FLUF_WITHOUT_BOOTSTRAP_DISCOVER_CTX + +int fluf_io_bootstrap_discover_ctx_init(fluf_io_bootstrap_discover_ctx_t *ctx, + const fluf_uri_path_t *base_path) { + assert(ctx && base_path); + if (fluf_uri_path_has(base_path, FLUF_ID_IID)) { + return FLUF_IO_ERR_INPUT_ARG; + } + memset(ctx, 0, sizeof(fluf_io_bootstrap_discover_ctx_t)); + ctx->base_path = *base_path; + + return 0; +} + +int fluf_io_bootstrap_discover_ctx_new_entry( + fluf_io_bootstrap_discover_ctx_t *ctx, + const fluf_uri_path_t *path, + const char *version, + const uint16_t *ssid, + const char *uri) { + assert(ctx); + + if (ctx->buff.bytes_in_internal_buff) { + return FLUF_IO_ERR_LOGIC; + } + if (!(fluf_uri_path_is(path, FLUF_ID_OID) + || fluf_uri_path_is(path, FLUF_ID_IID)) + || fluf_uri_path_outside_base(path, &ctx->base_path) + || !fluf_uri_path_increasing(&ctx->last_path, path)) { + return FLUF_IO_ERR_INPUT_ARG; + } + if (ssid && path->ids[FLUF_ID_OID] != FLUF_OBJ_ID_SECURITY + && path->ids[FLUF_ID_OID] != FLUF_OBJ_ID_SERVER + && path->ids[FLUF_ID_OID] != FLUF_OBJ_ID_OSCORE) { + return FLUF_IO_ERR_INPUT_ARG; + } + if (!ssid && path->ids[FLUF_ID_OID] == FLUF_OBJ_ID_SERVER) { + return FLUF_IO_ERR_INPUT_ARG; + } + if (uri && path->ids[FLUF_ID_OID] != FLUF_OBJ_ID_SECURITY) { + return FLUF_IO_ERR_INPUT_ARG; + } + if (fluf_uri_path_is(path, FLUF_ID_OID) && (uri || ssid)) { + return FLUF_IO_ERR_INPUT_ARG; + } + if (fluf_uri_path_is(path, FLUF_ID_IID) && version) { + return FLUF_IO_ERR_INPUT_ARG; + } + + if (!ctx->first_record_added) { +# ifdef FLUF_WITH_LWM2M12 + const char *payload_begin = ";lwm2m=1.2"; +# else // FLUF_WITH_LWM2M12 + const char *payload_begin = ";lwm2m=1.1"; +# endif // FLUF_WITH_LWM2M12 + + size_t record_len = strlen(payload_begin); + memcpy(ctx->buff.internal_buff, payload_begin, record_len); + ctx->buff.bytes_in_internal_buff = record_len; + ctx->buff.remaining_bytes = record_len; + } + + int res = _fluf_io_add_link_format_record(path, version, NULL, false, + &ctx->buff); + if (res) { + return res; + } + + if (ssid) { + size_t write_pointer = ctx->buff.bytes_in_internal_buff; + memcpy(&ctx->buff.internal_buff[write_pointer], ";ssid=", 6); + write_pointer += 6; + char ssid_str[FLUF_U16_STR_MAX_LEN]; + size_t ssid_str_len = fluf_uint16_to_string_value(*ssid, ssid_str); + memcpy(&ctx->buff.internal_buff[write_pointer], ssid_str, ssid_str_len); + write_pointer += ssid_str_len; + ctx->buff.bytes_in_internal_buff = write_pointer; + ctx->buff.remaining_bytes = write_pointer; + } + if (uri) { + size_t write_pointer = ctx->buff.bytes_in_internal_buff; + memcpy(&ctx->buff.internal_buff[write_pointer], ";uri=\"", 6); + write_pointer += 6; + ctx->buff.bytes_in_internal_buff = write_pointer; + ctx->buff.remaining_bytes = write_pointer; + ctx->buff.is_extended_type = true; + // + 1 to add \" on the end + ctx->buff.remaining_bytes += strlen(uri) + 1; + ctx->uri = uri; + } + + ctx->last_path = *path; + ctx->first_record_added = true; + return 0; +} + +int fluf_io_bootstrap_discover_ctx_get_payload( + fluf_io_bootstrap_discover_ctx_t *ctx, + void *out_buff, + size_t out_buff_len, + size_t *out_copied_bytes) { + assert(ctx); + return _fluf_io_get_payload(out_buff, out_buff_len, out_copied_bytes, + &ctx->buff, NULL, ctx->uri); +} + +#endif // FLUF_WITHOUT_BOOTSTRAP_DISCOVER_CTX + +#ifndef FLUF_WITHOUT_DISCOVER_CTX + +static bool res_instances_will_be_written(const fluf_uri_path_t *base_path, + uint8_t depth) { + return (uint8_t) base_path->uri_len + depth > FLUF_ID_RIID; +} + +static size_t add_attribute(fluf_io_buff_t *ctx, + const char *name, + const uint32_t *uint_value, + const double *double_value) { + assert(!!uint_value != !!double_value); + + size_t record_len = 0; + + ctx->internal_buff[record_len++] = ';'; + memcpy(&ctx->internal_buff[record_len], name, strlen(name)); + record_len += strlen(name); + ctx->internal_buff[record_len++] = '='; + + if (uint_value) { + record_len += fluf_uint32_to_string_value( + *uint_value, (char *) &ctx->internal_buff[record_len]); + } else { + record_len += fluf_double_to_simple_str_value( + *double_value, (char *) &ctx->internal_buff[record_len]); + } + return record_len; +} + +static size_t get_attribute_record(fluf_io_buff_t *ctx, + fluf_attr_notification_t *attributes) { + if (attributes->has_min_period) { + attributes->has_min_period = false; + return add_attribute(ctx, "pmin", &attributes->min_period, NULL); + } + if (attributes->has_max_period) { + attributes->has_max_period = false; + return add_attribute(ctx, "pmax", &attributes->max_period, NULL); + } + if (attributes->has_greater_than) { + attributes->has_greater_than = false; + return add_attribute(ctx, "gt", NULL, &attributes->greater_than); + } + if (attributes->has_less_than) { + attributes->has_less_than = false; + return add_attribute(ctx, "lt", NULL, &attributes->less_than); + } + if (attributes->has_step) { + attributes->has_step = false; + return add_attribute(ctx, "st", NULL, &attributes->step); + } + if (attributes->has_min_eval_period) { + attributes->has_min_eval_period = false; + return add_attribute(ctx, "epmin", &attributes->min_eval_period, NULL); + } + if (attributes->has_max_eval_period) { + attributes->has_max_eval_period = false; + return add_attribute(ctx, "epmax", &attributes->max_eval_period, NULL); + } +# ifdef FLUF_WITH_LWM2M12 + if (attributes->has_edge) { + attributes->has_edge = false; + return add_attribute(ctx, "edge", &attributes->edge, NULL); + } + if (attributes->has_con) { + attributes->has_con = false; + return add_attribute(ctx, "con", &attributes->con, NULL); + } + if (attributes->has_hqmax) { + attributes->has_hqmax = false; + return add_attribute(ctx, "hqmax", &attributes->hqmax, NULL); + } +# endif // FLUF_WITH_LWM2M12 + + return 0; +} + +static int get_attributes_payload(fluf_io_discover_ctx_t *ctx, + void *out_buff, + size_t out_buff_len, + size_t *copied_bytes) { + while (1) { + if (ctx->attr_record_offset == ctx->attr_record_len) { + ctx->attr_record_len = get_attribute_record(&ctx->buff, &ctx->attr); + ctx->attr_record_offset = 0; + } + + size_t bytes_to_copy = + AVS_MIN(ctx->attr_record_len - ctx->attr_record_offset, + out_buff_len - *copied_bytes); + + memcpy(&((uint8_t *) out_buff)[*copied_bytes], + &(ctx->buff.internal_buff[ctx->attr_record_offset]), + bytes_to_copy); + *copied_bytes += bytes_to_copy; + + // no more records + if (ctx->attr_record_len == ctx->attr_record_offset) { + ctx->buff.remaining_bytes = 0; + ctx->buff.offset = 0; + ctx->buff.bytes_in_internal_buff = 0; + ctx->buff.is_extended_type = false; + return 0; + } + ctx->attr_record_offset += bytes_to_copy; + + if (!(out_buff_len - *copied_bytes)) { + return FLUF_IO_NEED_NEXT_CALL; + } + } +} + +int fluf_io_discover_ctx_init(fluf_io_discover_ctx_t *ctx, + const fluf_uri_path_t *base_path, + const uint8_t *depth) { + assert(ctx && base_path); + + if ((depth && *depth > 3) || !fluf_uri_path_has(base_path, FLUF_ID_OID) + || fluf_uri_path_is(base_path, FLUF_ID_RIID)) { + return FLUF_IO_ERR_INPUT_ARG; + } + memset(ctx, 0, sizeof(fluf_io_discover_ctx_t)); + ctx->base_path = *base_path; + + if (depth) { + ctx->depth = *depth; + } else { + // default depth value + if (fluf_uri_path_is(base_path, FLUF_ID_OID)) { + ctx->depth = 2; + } else { + ctx->depth = 1; + } + } + + return 0; +} + +int fluf_io_discover_ctx_new_entry(fluf_io_discover_ctx_t *ctx, + const fluf_uri_path_t *path, + const fluf_attr_notification_t *attributes, + const char *version, + const uint16_t *dim) { + assert(ctx && path); + + if (ctx->buff.bytes_in_internal_buff) { + return FLUF_IO_ERR_LOGIC; + } + + if (path->uri_len - ctx->base_path.uri_len > ctx->depth) { + return FLUF_IO_WARNING_DEPTH; + } + + if ((ctx->dim_counter && !fluf_uri_path_is(path, FLUF_ID_RIID)) + || (!ctx->dim_counter && fluf_uri_path_is(path, FLUF_ID_RIID))) { + return FLUF_IO_ERR_LOGIC; + } + + if (fluf_uri_path_outside_base(path, &ctx->base_path) + || !fluf_uri_path_has(path, FLUF_ID_OID) + || !fluf_uri_path_increasing(&ctx->last_path, path) + || (version && !fluf_uri_path_is(path, FLUF_ID_OID)) + || (dim && !fluf_uri_path_is(path, FLUF_ID_RID))) { + return FLUF_IO_ERR_INPUT_ARG; + } + + if (dim && res_instances_will_be_written(&ctx->base_path, ctx->depth)) { + ctx->dim_counter = *dim; + } + + int res = _fluf_io_add_link_format_record( + path, version, dim, !ctx->first_record_added, &ctx->buff); + if (res) { + return res; + } + + if (attributes) { + ctx->attr = *attributes; + ctx->buff.is_extended_type = true; + // HACK: add one byte to add attributes when copying + ctx->buff.remaining_bytes++; + } + + ctx->first_record_added = true; + ctx->last_path = *path; + if (ctx->dim_counter && fluf_uri_path_is(path, FLUF_ID_RIID)) { + ctx->dim_counter--; + } + return 0; +} + +int fluf_io_discover_ctx_get_payload(fluf_io_discover_ctx_t *ctx, + void *out_buff, + size_t out_buff_len, + size_t *out_copied_bytes) { + assert(ctx && out_buff && out_buff_len && out_copied_bytes); + + int ret = _fluf_io_get_payload(out_buff, out_buff_len, out_copied_bytes, + &ctx->buff, NULL, NULL); + + // there are attributes left and link_format record is copied + if (ctx->buff.is_extended_type + && ctx->buff.offset >= ctx->buff.bytes_in_internal_buff) { + return get_attributes_payload(ctx, out_buff, out_buff_len, + out_copied_bytes); + } + return ret; +} + +#endif // FLUF_WITHOUT_DISCOVER_CTX diff --git a/src/fluf/fluf_internal.h b/src/fluf/fluf_internal.h new file mode 100644 index 00000000..66f07e05 --- /dev/null +++ b/src/fluf/fluf_internal.h @@ -0,0 +1,114 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#ifndef FLUF_INTERNAL_H +#define FLUF_INTERNAL_H + +#include +#include +#include + +#include +#include +#include + +#define CBOR_TAG_INTEGER_DATE_TIME 0x01 + +/** + * Enumeration for supported SenML labels. Their numeric values correspond to + * their CBOR representation wherever possible. + */ +typedef enum { + SENML_LABEL_BASE_TIME = -3, + SENML_LABEL_BASE_NAME = -2, + SENML_LABEL_NAME = 0, + SENML_LABEL_VALUE = 2, + SENML_LABEL_VALUE_STRING = 3, + SENML_LABEL_VALUE_BOOL = 4, + SENML_LABEL_TIME = 6, + SENML_LABEL_VALUE_OPAQUE = 8, + /* NOTE: Objlnk is represented as a string "vlo" */ + SENML_EXT_LABEL_OBJLNK = 0x766C6F /* "vlo */ +} senml_label_t; + +#define SENML_EXT_OBJLNK_REPR "vlo" + +/* See "2.1. Major Types" in RFC 7049 */ +typedef enum { + _CBOR_MAJOR_TYPE_BEGIN = 0, + CBOR_MAJOR_TYPE_UINT = 0, + CBOR_MAJOR_TYPE_NEGATIVE_INT = 1, + CBOR_MAJOR_TYPE_BYTE_STRING = 2, + CBOR_MAJOR_TYPE_TEXT_STRING = 3, + CBOR_MAJOR_TYPE_ARRAY = 4, + CBOR_MAJOR_TYPE_MAP = 5, + CBOR_MAJOR_TYPE_TAG = 6, + CBOR_MAJOR_TYPE_FLOAT_OR_SIMPLE_VALUE = 7, + _CBOR_MAJOR_TYPE_END +} cbor_major_type_t; + +typedef enum { + /** + * Section "2. Specification of the CBOR Encoding": + * + * > When it [5 lower bits of major type] is 24 to 27, the additional + * > bytes for a variable-length integer immediately follow; the values 24 + * > to 27 of the additional information specify that its length is a 1-, + * > 2-, 4-, or 8-byte unsigned integer, respectively. + * + * > Additional informationvalue 31 is used for indefinite-length items, + * > described in Section 2.2. Additional information values 28 to 30 are + * > reserved for future expansion. + */ + CBOR_EXT_LENGTH_1BYTE = 24, + CBOR_EXT_LENGTH_2BYTE = 25, + CBOR_EXT_LENGTH_4BYTE = 26, + CBOR_EXT_LENGTH_8BYTE = 27, + CBOR_EXT_LENGTH_INDEFINITE = 31 +} cbor_ext_length_t; + +/** + * This enum is for: + * + * > Major type 7: floating-point numbers and simple data types that need + * > no content, as well as the "break" stop code. See Section 2.3. + */ +typedef enum { + /* See "2.3. Floating-Point Numbers and Values with No Content" */ + CBOR_VALUE_BOOL_FALSE = 20, + CBOR_VALUE_BOOL_TRUE = 21, + CBOR_VALUE_NULL = 22, + CBOR_VALUE_UNDEFINED = 23, + CBOR_VALUE_IN_NEXT_BYTE = CBOR_EXT_LENGTH_1BYTE, + CBOR_VALUE_FLOAT_16 = CBOR_EXT_LENGTH_2BYTE, + CBOR_VALUE_FLOAT_32 = CBOR_EXT_LENGTH_4BYTE, + CBOR_VALUE_FLOAT_64 = CBOR_EXT_LENGTH_8BYTE +} cbor_primitive_value_t; + +#define CBOR_INDEFINITE_STRUCTURE_BREAK 0xFF + +size_t _fluf_io_out_add_objlink(fluf_io_buff_t *buff_ctx, + size_t buf_pos, + fluf_oid_t oid, + fluf_iid_t iid); + +int _fluf_io_add_link_format_record(const fluf_uri_path_t *uri_path, + const char *version, + const uint16_t *dim, + bool first_record, + fluf_io_buff_t *ctx); + +int _fluf_io_get_payload(void *out_buff, + size_t out_buff_len, + size_t *copied_bytes, + fluf_io_buff_t *ctx, + const fluf_io_out_entry_t *entry, + const char *bootstrap_uri); + +#endif // FLUF_INTERNAL_H diff --git a/src/fluf/fluf_io.c b/src/fluf/fluf_io.c new file mode 100644 index 00000000..43d7635f --- /dev/null +++ b/src/fluf/fluf_io.c @@ -0,0 +1,428 @@ +/* + * Copyright 2023-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 "fluf_cbor_encoder.h" +#include "fluf_internal.h" +#include "fluf_tlv_decoder.h" + +AVS_STATIC_ASSERT(_FLUF_IO_CTX_BUFFER_LENGTH + >= FLUF_CBOR_LL_SINGLE_CALL_MAX_LEN, + CBOR_buffer_too_small); + +static uint16_t supported_formats_list[] = { +#ifdef FLUF_WITH_PLAINTEXT_SUPPORT + FLUF_COAP_FORMAT_PLAINTEXT, +#endif // FLUF_WITH_PLAINTEXT_SUPPORT +#ifdef FLUF_WITH_DISCOVERY_ENCODER_SUPPORT + FLUF_COAP_FORMAT_LINK_FORMAT, +#endif // FLUF_WITH_DISCOVERY_ENCODER_SUPPORT + FLUF_COAP_FORMAT_CBOR, +#ifdef FLUF_WITH_LWM2M12 + FLUF_COAP_FORMAT_OMA_LWM2M_CBOR, +#endif // FLUF_WITH_LWM2M12 +#ifndef FLUF_WITHOUT_SENML_CBOR_SUPPORT + FLUF_COAP_FORMAT_SENML_CBOR, FLUF_COAP_FORMAT_SENML_ETCH_CBOR +#endif // FLUF_WITHOUT_SENML_CBOR_SUPPORT +}; + +static int copy_to_buffer(uint8_t *buffer, + size_t buffer_length, + const fluf_io_out_entry_t *entry, + fluf_io_buff_t *buff_ctx, + const char *bootstrap_uri) { + size_t bytes_to_copy = buff_ctx->remaining_bytes < buffer_length + ? buff_ctx->remaining_bytes + : buffer_length; + size_t copied_bytes = 0; + // first copy from internal buffer + if (buff_ctx->offset < buff_ctx->bytes_in_internal_buff) { + size_t bytes_to_copy_from_int_buff = + AVS_MIN(buff_ctx->bytes_in_internal_buff - buff_ctx->offset, + bytes_to_copy); + memcpy(buffer, + &(buff_ctx->internal_buff[buff_ctx->offset]), + bytes_to_copy_from_int_buff); + copied_bytes = bytes_to_copy_from_int_buff; + bytes_to_copy -= copied_bytes; + } + // copy extended data + assert(buff_ctx->is_extended_type || !bytes_to_copy); + if (entry && buff_ctx->is_extended_type && bytes_to_copy) { + size_t extended_offset = + buff_ctx->offset > buff_ctx->bytes_in_internal_buff + ? (buff_ctx->offset - buff_ctx->bytes_in_internal_buff) + : 0; + if (entry->type == FLUF_DATA_TYPE_BYTES + || entry->type == FLUF_DATA_TYPE_STRING) { + memcpy(&buffer[copied_bytes], + &((const uint8_t *) entry->value.bytes_or_string + .data)[extended_offset], + bytes_to_copy); + } else { + int res = entry->value.external_data.get_external_data( + &buffer[copied_bytes], bytes_to_copy, extended_offset, + entry->value.external_data.user_args); + if (res) { + return res; + } + } + copied_bytes += bytes_to_copy; + } // uri from Bootstrap-Discover + else if (bootstrap_uri && buff_ctx->is_extended_type && bytes_to_copy) { + size_t extended_offset = + buff_ctx->offset > buff_ctx->bytes_in_internal_buff + ? (buff_ctx->offset - buff_ctx->bytes_in_internal_buff) + : 0; + // copy last part of the message + if (buff_ctx->remaining_bytes <= buffer_length) { + memcpy(&buffer[copied_bytes], &bootstrap_uri[extended_offset], + bytes_to_copy - 1); + buffer[copied_bytes + bytes_to_copy - 1] = '\"'; + } else { + memcpy(&buffer[copied_bytes], &bootstrap_uri[extended_offset], + bytes_to_copy); + } + copied_bytes += bytes_to_copy; + } + + buff_ctx->remaining_bytes -= copied_bytes; + buff_ctx->offset += copied_bytes; + + return 0; +} + +static int check_format(uint16_t given_format, size_t items_count) { + if (given_format == FLUF_COAP_FORMAT_NOT_DEFINED) { + return 0; + } + bool is_present = false; + for (size_t i = 0; i < AVS_ARRAY_SIZE(supported_formats_list); i++) { + if (given_format == supported_formats_list[i]) { + is_present = true; + break; + } + } + if (!is_present) { + return -1; + } + if ((given_format == FLUF_COAP_FORMAT_CBOR +#ifdef FLUF_WITH_PLAINTEXT_SUPPORT + || given_format == FLUF_WITH_PLAINTEXT_SUPPORT +#endif // FLUF_WITH_PLAINTEXT_SUPPORT + ) && items_count > 1) { + return -1; + } + return 0; +} + +static uint16_t choose_format(size_t items_count, uint16_t given_format) { + if (given_format != FLUF_COAP_FORMAT_NOT_DEFINED) { + return given_format; + } + uint16_t format; + + if (items_count > 1) { +#ifdef FLUF_WITH_LWM2M12 + // TODO: FLUF_COAP_FORMAT_OMA_LWM2M_CBOR + format = FLUF_COAP_FORMAT_SENML_CBOR; +#else // FLUF_WITH_LWM2M12 + format = FLUF_COAP_FORMAT_SENML_CBOR; +#endif // FLUF_WITH_LWM2M12 + } else { + format = FLUF_COAP_FORMAT_CBOR; + } + + return format; +} + +int fluf_io_out_ctx_init(fluf_io_out_ctx_t *ctx, + fluf_op_t operation_type, + const fluf_uri_path_t *base_path, + size_t items_count, + uint16_t format) { + assert(ctx); + bool use_base_path = false; + bool encode_time = false; + + if (!items_count || !ctx) { + return FLUF_IO_ERR_INPUT_ARG; + } + switch (operation_type) { + case FLUF_OP_DM_READ: + case FLUF_OP_INF_OBSERVE: + case FLUF_OP_INF_CANCEL_OBSERVE: + use_base_path = true; + break; + case FLUF_OP_DM_READ_COMP: + case FLUF_OP_INF_OBSERVE_COMP: + case FLUF_OP_INF_CANCEL_OBSERVE_COMP: + break; + case FLUF_OP_INF_NON_CON_NOTIFY: + case FLUF_OP_INF_CON_NOTIFY: + case FLUF_OP_INF_SEND: + encode_time = true; + break; + default: + return FLUF_IO_ERR_INPUT_ARG; + } + if (check_format(format, items_count)) { + return FLUF_IO_ERR_FORMAT; + } + + memset(ctx, 0, sizeof(fluf_io_out_ctx_t)); + ctx->_format = choose_format(items_count, format); + + if (ctx->_format == FLUF_COAP_FORMAT_CBOR) { + return _fluf_cbor_encoder_init(ctx); + } else if (ctx->_format == FLUF_COAP_FORMAT_SENML_CBOR + || ctx->_format == FLUF_COAP_FORMAT_SENML_ETCH_CBOR) { + const fluf_uri_path_t path = + use_base_path ? *base_path : FLUF_MAKE_ROOT_PATH(); + return _fluf_senml_cbor_encoder_init(ctx, &path, items_count, + encode_time); + } else { + // not implemented yet + return -1; + } +} + +int fluf_io_out_ctx_new_entry(fluf_io_out_ctx_t *ctx, + const fluf_io_out_entry_t *entry) { + assert(ctx && entry); + int res = FLUF_IO_ERR_INPUT_ARG; + + if (ctx->_format == FLUF_COAP_FORMAT_CBOR) { + res = _fluf_cbor_out_ctx_new_entry(ctx, entry); + } else if (ctx->_format == FLUF_COAP_FORMAT_SENML_CBOR + || ctx->_format == FLUF_COAP_FORMAT_SENML_ETCH_CBOR) { + res = _fluf_senml_cbor_out_ctx_new_entry(ctx, entry); + } + if (!res) { + ctx->_entry = entry; + } + return res; +} + +int _fluf_io_get_payload(void *out_buff, + size_t out_buff_len, + size_t *copied_bytes, + fluf_io_buff_t *ctx, + const fluf_io_out_entry_t *entry, + const char *bootstrap_uri) { + assert(out_buff && out_buff_len && copied_bytes && ctx); + + if (!ctx->remaining_bytes) { + return FLUF_IO_ERR_LOGIC; + } + + size_t bytes_to_copy = ctx->remaining_bytes; + int res = copy_to_buffer((uint8_t *) out_buff, out_buff_len, entry, ctx, + bootstrap_uri); + if (res) { + return res; + } + + if (!ctx->remaining_bytes) { + ctx->offset = 0; + ctx->bytes_in_internal_buff = 0; + ctx->is_extended_type = false; + } else { + res = FLUF_IO_NEED_NEXT_CALL; + } + *copied_bytes = bytes_to_copy - ctx->remaining_bytes; + + return res; +} + +int fluf_io_out_ctx_get_payload(fluf_io_out_ctx_t *ctx, + void *out_buff, + size_t out_buff_len, + size_t *out_copied_bytes) { + assert(ctx); + return _fluf_io_get_payload(out_buff, out_buff_len, out_copied_bytes, + &ctx->_buff, ctx->_entry, NULL); +} + +uint16_t fluf_io_out_ctx_get_format(fluf_io_out_ctx_t *ctx) { + return ctx->_format; +} + +size_t _fluf_io_out_add_objlink(fluf_io_buff_t *buff_ctx, + size_t buf_pos, + fluf_oid_t oid, + fluf_iid_t iid) { + char buffer[_FLUF_IO_CBOR_SIMPLE_RECORD_MAX_LENGTH] = { 0 }; + + size_t str_size = fluf_uint16_to_string_value(oid, buffer); + buffer[str_size++] = ':'; + str_size += fluf_uint16_to_string_value(iid, &buffer[str_size]); + + size_t header_size = + fluf_cbor_ll_string_begin(&buff_ctx->internal_buff[buf_pos], + str_size); + memcpy(&((uint8_t *) buff_ctx->internal_buff)[buf_pos + header_size], + buffer, str_size); + return header_size + str_size; +} + +int _fluf_io_add_link_format_record(const fluf_uri_path_t *uri_path, + const char *version, + const uint16_t *dim, + bool first_record, + fluf_io_buff_t *ctx) { + assert(ctx->remaining_bytes == ctx->bytes_in_internal_buff); + size_t write_pointer = ctx->bytes_in_internal_buff; + + if (!first_record) { + ctx->internal_buff[write_pointer++] = ','; + } + ctx->internal_buff[write_pointer++] = '<'; + for (uint16_t i = 0; i < uri_path->uri_len; i++) { + ctx->internal_buff[write_pointer++] = '/'; + write_pointer += fluf_uint16_to_string_value( + uri_path->ids[i], (char *) &ctx->internal_buff[write_pointer]); + } + ctx->internal_buff[write_pointer++] = '>'; + + if (dim) { + if (!fluf_uri_path_is(uri_path, FLUF_ID_RID)) { + return FLUF_IO_ERR_INPUT_ARG; + } + memcpy(&ctx->internal_buff[write_pointer], ";dim=", 5); + write_pointer += 5; + write_pointer += fluf_uint16_to_string_value( + *dim, (char *) &ctx->internal_buff[write_pointer]); + } + if (version) { + if (fluf_validate_obj_version(version)) { + return FLUF_IO_ERR_INPUT_ARG; + } + memcpy(&ctx->internal_buff[write_pointer], ";ver=", 5); + write_pointer += 5; + size_t ver_len = strlen(version); + memcpy(&ctx->internal_buff[write_pointer], version, ver_len); + write_pointer += ver_len; + } + + ctx->bytes_in_internal_buff = write_pointer; + ctx->remaining_bytes = write_pointer; + return 0; +} + +int fluf_io_in_ctx_init(fluf_io_in_ctx_t *ctx, + fluf_op_t operation_type, + const fluf_uri_path_t *base_path, + uint16_t format) { + (void) operation_type; + assert(ctx); + memset(ctx, 0, sizeof(*ctx)); + ctx->_format = format; + switch (format) { + case FLUF_COAP_FORMAT_OMA_LWM2M_TLV: + return _fluf_tlv_decoder_init(ctx, base_path); + default: + return FLUF_IO_ERR_INPUT_ARG; + } +} + +int fluf_io_in_ctx_feed_payload(fluf_io_in_ctx_t *ctx, + void *buff, + size_t buff_size, + bool payload_finished) { + assert(ctx); + switch (ctx->_format) { + case FLUF_COAP_FORMAT_OMA_LWM2M_TLV: + return _fluf_tlv_decoder_feed_payload(ctx, buff, buff_size, + payload_finished); + default: + return FLUF_IO_ERR_LOGIC; + } +} + +int fluf_io_in_ctx_get_entry(fluf_io_in_ctx_t *ctx, + fluf_data_type_t *inout_type_bitmask, + const fluf_res_value_t **out_value, + const fluf_uri_path_t **out_path) { + assert(ctx); + switch (ctx->_format) { + case FLUF_COAP_FORMAT_OMA_LWM2M_TLV: + return _fluf_tlv_decoder_get_entry(ctx, inout_type_bitmask, out_value, + out_path); + default: + return FLUF_IO_ERR_LOGIC; + } +} + +int fluf_io_in_ctx_get_entry_count(fluf_io_in_ctx_t *ctx, size_t *out_count) { + assert(ctx); + assert(out_count); + switch (ctx->_format) { + default: + return FLUF_IO_ERR_FORMAT; + } +} + +#ifndef FLUF_WITHOUT_REGISTER_CTX + +int fluf_io_register_ctx_new_entry(fluf_io_register_ctx_t *ctx, + const fluf_uri_path_t *path, + const char *version) { + assert(ctx); + if (ctx->buff.bytes_in_internal_buff) { + return FLUF_IO_ERR_LOGIC; + } + if (!(fluf_uri_path_is(path, FLUF_ID_OID) + || fluf_uri_path_is(path, FLUF_ID_IID)) + || !fluf_uri_path_increasing(&ctx->last_path, path)) { + return FLUF_IO_ERR_INPUT_ARG; + } + if (path->ids[FLUF_ID_OID] == FLUF_OBJ_ID_SECURITY + || path->ids[FLUF_ID_OID] == FLUF_OBJ_ID_OSCORE) { + return FLUF_IO_ERR_INPUT_ARG; + } + if (fluf_uri_path_is(path, FLUF_ID_IID) && version) { + return FLUF_IO_ERR_INPUT_ARG; + } + + int res = _fluf_io_add_link_format_record( + path, version, NULL, !ctx->first_record_added, &ctx->buff); + if (res) { + return res; + } + + ctx->last_path = *path; + ctx->first_record_added = true; + return 0; +} + +int fluf_io_register_ctx_get_payload(fluf_io_register_ctx_t *ctx, + void *out_buff, + size_t out_buff_len, + size_t *out_copied_bytes) { + assert(ctx); + return _fluf_io_get_payload(out_buff, out_buff_len, out_copied_bytes, + &ctx->buff, NULL, NULL); +} + +void fluf_io_register_ctx_init(fluf_io_register_ctx_t *ctx) { + assert(ctx); + memset(ctx, 0, sizeof(fluf_io_register_ctx_t)); +} + +#endif // FLUF_WITHOUT_REGISTER_CTX diff --git a/src/fluf/fluf_options.c b/src/fluf/fluf_options.c new file mode 100644 index 00000000..bbd4d57a --- /dev/null +++ b/src/fluf/fluf_options.c @@ -0,0 +1,460 @@ +/* + * Copyright 2023-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 "fluf_coap_udp_header.h" +#include "fluf_coap_udp_msg.h" +#include "fluf_options.h" + +#define _FLUF_COAP_OPTION_HEADER_MAX_LEN 5 + +#define _FLUF_FIELD_GET(field, mask, shift) (((field) & (mask)) >> (shift)) + +#define _FLUF_COAP_OPTION_DELTA_MASK 0xF0 +#define _FLUF_COAP_OPTION_DELTA_SHIFT 4 +#define _FLUF_COAP_OPTION_LENGTH_MASK 0x0F +#define _FLUF_COAP_OPTION_LENGTH_SHIFT 0 + +#define _FLUF_COAP_OPTION_U8_MASK 0xFF +#define _FLUF_COAP_OPTION_U16_MASK 0xFF00 +#define _FLUF_COAP_OPTION_U16_SHIFT 8 + +#define _FLUF_COAP_EXT_U8 13 +#define _FLUF_COAP_EXT_U16 14 + +#define _FLUF_COAP_EXT_U8_BASE ((int) 13) +#define _FLUF_COAP_EXT_U16_BASE ((int) 269) + +static int update_extended_option(uint16_t *value, + const uint8_t **buff_pointer, + const uint8_t *buff_end) { + if (*value == _FLUF_COAP_EXT_U8) { + if (*buff_pointer >= buff_end) { + return FLUF_ERR_BUFF; + } + (*buff_pointer)++; + *value = _FLUF_COAP_EXT_U8_BASE + **buff_pointer; + } else if (*value == _FLUF_COAP_EXT_U16) { + if (*buff_pointer + 1 >= buff_end) { + return FLUF_ERR_BUFF; + } + (*buff_pointer)++; + *value = _FLUF_COAP_EXT_U16_BASE + + ((uint16_t) (**buff_pointer << _FLUF_COAP_OPTION_U16_SHIFT) + & _FLUF_COAP_OPTION_U16_MASK); + (*buff_pointer)++; + *value += **buff_pointer; + } + + return 0; +} + +static int get_option_from_buff(const uint8_t **buff_pointer, + const uint8_t *buff_end, + fluf_coap_option_t *opt, + uint16_t last_opt_number) { + opt->option_number = + _FLUF_FIELD_GET(**buff_pointer, _FLUF_COAP_OPTION_DELTA_MASK, + _FLUF_COAP_OPTION_DELTA_SHIFT); + opt->payload_len = + _FLUF_FIELD_GET(**buff_pointer, _FLUF_COAP_OPTION_LENGTH_MASK, + _FLUF_COAP_OPTION_LENGTH_SHIFT); + + int res = update_extended_option(&(opt->option_number), buff_pointer, + buff_end); + if (res) { + return res; + } + opt->option_number += last_opt_number; + size_t temp_payload_len = opt->payload_len; + res = update_extended_option((uint16_t *) &temp_payload_len, buff_pointer, + buff_end); + if (res) { + return res; + } + opt->payload_len = temp_payload_len; + + // move to payload position + (*buff_pointer)++; + if (*buff_pointer + opt->payload_len > buff_end) { + return FLUF_ERR_BUFF; + } + opt->payload = *buff_pointer; + (*buff_pointer) += opt->payload_len; + + return 0; +} + +static size_t prepare_option_header(uint8_t *opt_header, + uint16_t previous_opt_number, + uint16_t opt_number, + size_t payload_size) { + size_t header_size = 1; + + uint16_t new_opt_number = opt_number - previous_opt_number; + + if (new_opt_number < _FLUF_COAP_EXT_U8_BASE) { + opt_header[0] = + (uint8_t) (new_opt_number << _FLUF_COAP_OPTION_DELTA_SHIFT); + } else if (new_opt_number < _FLUF_COAP_EXT_U16_BASE) { + opt_header[0] = + (uint8_t) (_FLUF_COAP_EXT_U8 << _FLUF_COAP_OPTION_DELTA_SHIFT); + header_size++; + opt_header[header_size - 1] = + (uint8_t) (new_opt_number - _FLUF_COAP_EXT_U8_BASE); + } else { + opt_header[0] = _FLUF_COAP_EXT_U16 << _FLUF_COAP_OPTION_DELTA_SHIFT; + header_size++; + new_opt_number = new_opt_number - _FLUF_COAP_EXT_U16_BASE; + opt_header[header_size - 1] = + ((uint8_t) new_opt_number >> _FLUF_COAP_OPTION_U16_SHIFT) + & _FLUF_COAP_OPTION_U8_MASK; + header_size++; + opt_header[header_size - 1] = + (uint8_t) new_opt_number & _FLUF_COAP_OPTION_U8_MASK; + } + + if (payload_size < _FLUF_COAP_EXT_U8_BASE) { + opt_header[0] += (uint8_t) payload_size; + } else if (payload_size < _FLUF_COAP_EXT_U16_BASE) { + opt_header[0] += _FLUF_COAP_EXT_U8; + header_size++; + opt_header[header_size - 1] = + (uint8_t) (payload_size - _FLUF_COAP_EXT_U8_BASE); + } else { + opt_header[0] += _FLUF_COAP_EXT_U16; + header_size++; + opt_header[header_size - 1] = ((payload_size - _FLUF_COAP_EXT_U16_BASE) + >> _FLUF_COAP_OPTION_U16_SHIFT) + & _FLUF_COAP_OPTION_U8_MASK; + header_size++; + opt_header[header_size - 1] = (payload_size - _FLUF_COAP_EXT_U16_BASE) + & _FLUF_COAP_OPTION_U8_MASK; + } + + return header_size; +} + +int _fluf_coap_options_add_data(fluf_coap_options_t *opts, + uint16_t opt_number, + const void *data, + size_t data_size) { + assert(opts->buff_begin); + assert(opts->options); + assert(opts->options_size); + + size_t new_opt_position = opts->options_number; + uint16_t previous_opt_number = 0; + + if (opts->options_number == opts->options_size) { + // there is no space for new option + return FLUF_ERR_OPTIONS_ARRAY; + } + + // find place to insert new options + for (size_t i = 0; i < opts->options_number; i++) { + if (opt_number < opts->options[i].option_number) { + new_opt_position = i; + break; + } + } + if (new_opt_position) { + previous_opt_number = opts->options[new_opt_position - 1].option_number; + } + + // prepare new option record + uint8_t opt_header[_FLUF_COAP_OPTION_HEADER_MAX_LEN] = { 0 }; + size_t opt_header_len = 0; + + opt_header_len = prepare_option_header(opt_header, previous_opt_number, + opt_number, data_size); + size_t new_opt_total_size = opt_header_len + data_size; + + // check if new option fit + // last element pointer + last element payload_len + new option total size + // can't oversize msg buffer + uint8_t *buff_end = opts->buff_begin + opts->buff_size; + if (opts->options_number) { + fluf_coap_option_t last_opt = opts->options[opts->options_number - 1]; + if (last_opt.payload + last_opt.payload_len + new_opt_total_size + >= buff_end) { + return FLUF_ERR_BUFF; + } + } else { + if (opts->buff_begin + new_opt_total_size >= buff_end) { + return FLUF_ERR_BUFF; + } + } + + size_t memory_offset = new_opt_total_size; + uint8_t *memory_to_move_start_point = opts->buff_begin; + + // update memory buffer + if (new_opt_position != opts->options_number) { + // new option is not last element + // update record that comes after the new one + uint8_t next_opt_header[_FLUF_COAP_OPTION_HEADER_MAX_LEN] = { 0 }; + size_t next_opt_header_old_len = 0; + size_t next_opt_header_len = 0; + + uint16_t prev_opt_number = 0; + + if (new_opt_position) { + prev_opt_number = opts->options[new_opt_position - 1].option_number; + memory_to_move_start_point = + opts->buff_begin + + ((opts->options[new_opt_position - 1].payload + + opts->options[new_opt_position - 1].payload_len) + - opts->buff_begin); + } + + next_opt_header_old_len = prepare_option_header( + next_opt_header, prev_opt_number, + opts->options[new_opt_position].option_number, + opts->options[new_opt_position].payload_len); + next_opt_header_len = prepare_option_header( + next_opt_header, opt_number, + opts->options[new_opt_position].option_number, + opts->options[new_opt_position].payload_len); + + memory_offset += next_opt_header_len - next_opt_header_old_len; + + // calculate size of memory block to move + fluf_coap_option_t last_option = + opts->options[opts->options_number - 1]; + size_t memory_to_move_size = + (size_t) ((last_option.payload + last_option.payload_len) + - memory_to_move_start_point); + + memmove((memory_to_move_start_point + memory_offset), + memory_to_move_start_point, memory_to_move_size); + memcpy(memory_to_move_start_point, opt_header, opt_header_len); + memcpy(memory_to_move_start_point + opt_header_len, data, data_size); + memcpy(memory_to_move_start_point + opt_header_len + data_size, + next_opt_header, next_opt_header_len); + } else { + if (new_opt_position) { + memory_to_move_start_point = + opts->buff_begin + + ((opts->options[new_opt_position - 1].payload + + opts->options[new_opt_position - 1].payload_len) + - opts->buff_begin); + } + + memcpy(memory_to_move_start_point, opt_header, opt_header_len); + memcpy(memory_to_move_start_point + opt_header_len, data, data_size); + } + + // update options array + if (opts->options_number) { + for (size_t i = opts->options_number - 1; i >= new_opt_position; i--) { + memcpy((void *) &opts->options[i + 1], + (const void *) &opts->options[i], + sizeof(fluf_coap_option_t)); + opts->options[i + 1].payload = + opts->options[i + 1].payload + memory_offset; + if (!i) { + break; + } + } + } + opts->options[new_opt_position].payload_len = data_size; + opts->options[new_opt_position].option_number = opt_number; + opts->options[new_opt_position].payload = + memory_to_move_start_point + opt_header_len; + + opts->options_number++; + + return 0; +} + +static int add_uint(fluf_coap_options_t *opts, + uint16_t opt_number, + void *value, + size_t value_size) { + uint8_t *data_ptr = (uint8_t *) value; + size_t data_size = value_size; + + while (data_size > 0 && !(*data_ptr)) { + data_ptr++; + data_size--; + } + return _fluf_coap_options_add_data(opts, opt_number, data_ptr, data_size); +} + +int _fluf_coap_options_add_u16(fluf_coap_options_t *opts, + uint16_t opt_number, + uint16_t value) { + uint16_t portable = avs_convert_be16((uint16_t) value); + return add_uint(opts, opt_number, &portable, sizeof(portable)); +} + +int _fluf_coap_options_add_u32(fluf_coap_options_t *opts, + uint16_t opt_number, + uint32_t value) { + uint32_t portable = avs_convert_be32((uint32_t) value); + return add_uint(opts, opt_number, &portable, sizeof(portable)); +} + +int _fluf_coap_options_add_u64(fluf_coap_options_t *opts, + uint16_t opt_number, + uint64_t value) { + uint64_t portable = avs_convert_be64((uint64_t) value); + return add_uint(opts, opt_number, &portable, sizeof(portable)); +} + +int _fluf_coap_options_get_data_iterate(const fluf_coap_options_t *opts, + uint16_t option_number, + size_t *iterator, + size_t *out_option_size, + void *out_buffer, + size_t out_buffer_size) { + + size_t opt_occurance = 0; + size_t requested_opt = 0; + if (iterator) { + requested_opt = *iterator; + (*iterator)++; + } + + for (size_t i = 0; i < opts->options_number; i++) { + if (opts->options[i].option_number == option_number) { + if (opt_occurance == requested_opt) { + if (out_buffer_size < opts->options[i].payload_len) { + return FLUF_ERR_BUFF; + } + memcpy(out_buffer, opts->options[i].payload, + opts->options[i].payload_len); + if (out_option_size) { + *out_option_size = opts->options[i].payload_len; + } + return 0; + } else { + opt_occurance++; + } + } + } + return _FLUF_COAP_OPTION_MISSING; +} + +int _fluf_coap_options_get_string_iterate(const fluf_coap_options_t *opts, + uint16_t option_number, + size_t *iterator, + size_t *out_option_size, + char *out_buffer, + size_t out_buffer_size) { + int res = _fluf_coap_options_get_data_iterate(opts, option_number, iterator, + out_option_size, + (void *) out_buffer, + out_buffer_size); + + if (!res) { + if (*out_option_size + 1 <= out_buffer_size) { + out_buffer[*out_option_size] = '\0'; + (*out_option_size)++; + } else { + return FLUF_ERR_BUFF; + } + } + return res; +} + +int _fluf_coap_options_get_u16_iterate(const fluf_coap_options_t *opts, + uint16_t option_number, + size_t *iterator, + uint16_t *out_value) { + uint8_t value[sizeof(uint16_t)]; + size_t out_option_size; + + int ret_val = + _fluf_coap_options_get_data_iterate(opts, option_number, iterator, + &out_option_size, &value, + sizeof(uint16_t)); + + if (!ret_val) { + *out_value = 0; + for (size_t i = 0; i < out_option_size; ++i) { + *out_value = (uint16_t) (*out_value << 8); + *out_value = (uint16_t) (*out_value | value[i]); + } + } + return ret_val; +} + +int _fluf_coap_options_get_u32_iterate(const fluf_coap_options_t *opts, + uint16_t option_number, + size_t *iterator, + uint32_t *out_value) { + uint8_t value[sizeof(uint32_t)]; + size_t out_option_size; + + int ret_val = + _fluf_coap_options_get_data_iterate(opts, option_number, iterator, + &out_option_size, &value, + sizeof(uint32_t)); + + if (!ret_val) { + *out_value = 0; + for (size_t i = 0; i < out_option_size; ++i) { + *out_value = (uint32_t) (*out_value << 8); + *out_value = (uint32_t) (*out_value | value[i]); + } + } + return ret_val; +} + +int _fluf_coap_options_decode(fluf_coap_options_t *opts, + const uint8_t *msg, + size_t msg_size, + size_t *bytes_read) { + assert(msg); + assert(opts->options); + assert(opts->options_size); + + const uint8_t *buff_pointer = msg; + const uint8_t *buff_end = msg + msg_size; + uint16_t last_opt_number = 0; + + // clear counter + opts->options_number = 0; + + while (buff_pointer < buff_end) { + // end of the options field + if (*buff_pointer == _FLUF_COAP_PAYLOAD_MARKER) { + *bytes_read = (size_t) (buff_pointer - msg); + return 0; + } + if (opts->options_number == opts->options_size) { + // we reach the limit before 0xFF marker -> return error + return FLUF_ERR_OPTIONS_ARRAY; + } + + int res = get_option_from_buff( + &buff_pointer, buff_end, + (fluf_coap_option_t *) &opts->options[opts->options_number], + last_opt_number); + if (res) { + // error in get_option_from_buff + return res; + } + last_opt_number = opts->options[opts->options_number].option_number; + opts->options_number++; + } + + *bytes_read = (size_t) (buff_pointer - msg); + return 0; +} diff --git a/src/fluf/fluf_options.h b/src/fluf/fluf_options.h new file mode 100644 index 00000000..98a19741 --- /dev/null +++ b/src/fluf/fluf_options.h @@ -0,0 +1,144 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#ifndef _FLUF_COAP_OPTION_H +#define _FLUF_COAP_OPTION_H + +#include +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * CoAP option numbers, as defined in RFC7252/RFC7641/RFC7959. + * @{ + */ +#define _FLUF_COAP_OPTION_IF_MATCH 1 +#define _FLUF_COAP_OPTION_URI_HOST 3 +#define _FLUF_COAP_OPTION_ETAG 4 +#define _FLUF_COAP_OPTION_IF_NONE_MATCH 5 +#define _FLUF_COAP_OPTION_OBSERVE 6 +#define _FLUF_COAP_OPTION_URI_PORT 7 +#define _FLUF_COAP_OPTION_LOCATION_PATH 8 +#define _FLUF_COAP_OPTION_OSCORE 9 +#define _FLUF_COAP_OPTION_URI_PATH 11 +#define _FLUF_COAP_OPTION_CONTENT_FORMAT 12 +#define _FLUF_COAP_OPTION_MAX_AGE 14 +#define _FLUF_COAP_OPTION_URI_QUERY 15 +#define _FLUF_COAP_OPTION_ACCEPT 17 +#define _FLUF_COAP_OPTION_LOCATION_QUERY 20 +#define _FLUF_COAP_OPTION_BLOCK2 23 +#define _FLUF_COAP_OPTION_BLOCK1 27 +#define _FLUF_COAP_OPTION_PROXY_URI 35 +#define _FLUF_COAP_OPTION_PROXY_SCHEME 39 +#define _FLUF_COAP_OPTION_SIZE1 60 + +/** + * Constant returned from some of option-retrieving functions, indicating + * the absence of requested option. + */ +#define _FLUF_COAP_OPTION_MISSING 1 + +#define _FLUF_COAP_OPTIONS_INIT_EMPTY(Name, OptionsSize) \ + fluf_coap_option_t _Opt##Name[OptionsSize]; \ + fluf_coap_options_t Name = { \ + .options_size = OptionsSize, \ + .options_number = 0, \ + .options = _Opt##Name, \ + .buff_size = 0, \ + .buff_begin = NULL \ + } + +typedef struct fluf_coap_option { + const uint8_t *payload; + size_t payload_len; + uint16_t option_number; +} fluf_coap_option_t; + +/**size_t *iterator + * Note: this struct MUST be initialized with + * @ref _FLUF_COAP_OPTIONS_INIT_EMPTY or _FLUF_COAP_OPTIONS_INIT_EMPTY_WITH_BUFF + * before it is used. + */ +typedef struct fluf_coap_options { + fluf_coap_option_t *options; + size_t options_size; + size_t options_number; + + uint8_t *buff_begin; + size_t buff_size; +} fluf_coap_options_t; + +int _fluf_coap_options_decode(fluf_coap_options_t *opts, + const uint8_t *msg, + size_t msg_size, + size_t *bytes_read); + +/* + * - 0 on success, + * - _FLUF_COAP_OPTION_MISSING when there are no more options with + * given @p option_number to retrieve + */ +int _fluf_coap_options_get_data_iterate(const fluf_coap_options_t *opts, + uint16_t option_number, + size_t *iterator, + size_t *out_option_size, + void *out_buffer, + size_t out_buffer_size); + +int _fluf_coap_options_get_string_iterate(const fluf_coap_options_t *opts, + uint16_t option_number, + size_t *iterator, + size_t *out_option_size, + char *out_buffer, + size_t out_buffer_size); + +int _fluf_coap_options_get_u16_iterate(const fluf_coap_options_t *opts, + uint16_t option_number, + size_t *iterator, + uint16_t *out_value); + +int _fluf_coap_options_get_u32_iterate(const fluf_coap_options_t *opts, + uint16_t option_number, + size_t *iterator, + uint32_t *out_value); + +int _fluf_coap_options_add_data(fluf_coap_options_t *opts, + uint16_t opt_number, + const void *data, + size_t data_size); + +static inline int _fluf_coap_options_add_string(fluf_coap_options_t *opts, + uint16_t opt_number, + const char *data) { + return _fluf_coap_options_add_data(opts, opt_number, data, strlen(data)); +} + +int _fluf_coap_options_add_u16(fluf_coap_options_t *opts, + uint16_t opt_number, + uint16_t value); + +int _fluf_coap_options_add_u32(fluf_coap_options_t *opts, + uint16_t opt_number, + uint32_t value); + +int _fluf_coap_options_add_u64(fluf_coap_options_t *opts, + uint16_t opt_number, + uint64_t value); + +#ifdef __cplusplus +} +#endif + +#endif // _FLUF_COAP_OPTION_H diff --git a/src/fluf/fluf_prepare.c b/src/fluf/fluf_prepare.c new file mode 100644 index 00000000..0f0d148c --- /dev/null +++ b/src/fluf/fluf_prepare.c @@ -0,0 +1,214 @@ +/* + * Copyright 2023-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 "fluf_attributes.h" +#include "fluf_block.h" +#include "fluf_coap_udp_msg.h" + +#define _RET_IF_ERROR(Val) \ + if (Val) { \ + return Val; \ + } + +static uint16_t g_fluf_msg_id; +static avs_rand_seed_t g_rand_seed; + +static int add_uri_path(fluf_coap_options_t *opts, fluf_data_t *data) { + int res = 0; + + if (data->operation == FLUF_OP_BOOTSTRAP_REQ) { + res = _fluf_coap_options_add_string(opts, _FLUF_COAP_OPTION_URI_PATH, + "bs"); + } else if (data->operation == FLUF_OP_BOOTSTRAP_PACK_REQ) { + res = _fluf_coap_options_add_string(opts, _FLUF_COAP_OPTION_URI_PATH, + "bspack"); + } else if (data->operation == FLUF_OP_REGISTER) { + res = _fluf_coap_options_add_string(opts, _FLUF_COAP_OPTION_URI_PATH, + "rd"); + } else if (data->operation == FLUF_OP_INF_SEND) { + res = _fluf_coap_options_add_string(opts, _FLUF_COAP_OPTION_URI_PATH, + "dp"); + } else if (data->operation == FLUF_OP_UPDATE + || data->operation == FLUF_OP_DEREGISTER) { + res = _fluf_coap_options_add_string(opts, _FLUF_COAP_OPTION_URI_PATH, + "rd"); + if (res) { + return res; + } + for (size_t i = 0; i < data->location_path.location_count; i++) { + res = _fluf_coap_options_add_data( + opts, _FLUF_COAP_OPTION_URI_PATH, + data->location_path.location[i], + data->location_path.location_len[i]); + if (res) { + return res; + } + } + } + + if (res) { + return res; + } + return 0; +} + +static int prepare_udp_msg(uint8_t *buff, + size_t buff_size, + size_t *out_msg_size, + fluf_data_t *data) { + if (data->operation == FLUF_OP_INF_CON_NOTIFY) { + // new msg_id, token reuse + data->coap.coap_udp.type = FLUF_COAP_UDP_TYPE_CONFIRMABLE; + data->coap.coap_udp.message_id = ++g_fluf_msg_id; + } else if (data->operation == FLUF_OP_INF_NON_CON_NOTIFY) { + // new msg_id, token reuse + data->coap.coap_udp.type = FLUF_COAP_UDP_TYPE_NON_CONFIRMABLE; + data->coap.coap_udp.message_id = ++g_fluf_msg_id; + } else if (data->operation == FLUF_OP_RESPONSE) { + // msg_id and token reuse + data->coap.coap_udp.type = FLUF_COAP_UDP_TYPE_ACKNOWLEDGEMENT; + } else { + // client request with new msg_id and token + data->coap.coap_udp.type = FLUF_COAP_UDP_TYPE_CONFIRMABLE; + data->coap.coap_udp.message_id = ++g_fluf_msg_id; + data->coap.coap_udp.token.size = FLUF_COAP_MAX_TOKEN_LENGTH; + + assert(FLUF_COAP_MAX_TOKEN_LENGTH == 8); + uint64_t token = avs_rand64_r(&g_rand_seed); + memcpy(data->coap.coap_udp.token.bytes, &token, sizeof(token)); + } + + switch (data->operation) { + case FLUF_OP_BOOTSTRAP_REQ: + data->msg_code = FLUF_COAP_CODE_POST; + break; + case FLUF_OP_BOOTSTRAP_PACK_REQ: + data->msg_code = FLUF_COAP_CODE_GET; + break; + case FLUF_OP_REGISTER: + data->msg_code = FLUF_COAP_CODE_POST; + break; + case FLUF_OP_UPDATE: + data->msg_code = FLUF_COAP_CODE_POST; + break; + case FLUF_OP_DEREGISTER: + data->msg_code = FLUF_COAP_CODE_DELETE; + break; + case FLUF_OP_INF_CON_NOTIFY: + case FLUF_OP_INF_NON_CON_NOTIFY: + data->msg_code = FLUF_COAP_CODE_CONTENT; + break; + case FLUF_OP_INF_SEND: + data->msg_code = FLUF_COAP_CODE_POST; + break; + case FLUF_OP_RESPONSE: + // msg code must be defined + break; + default: + return -1; + } + + _FLUF_COAP_OPTIONS_INIT_EMPTY(opts, FLUF_MAX_ALLOWED_OPTIONS_NUMBER); + fluf_coap_udp_msg_t msg = { + .header = _fluf_coap_udp_header_init( + data->coap.coap_udp.type, data->coap.coap_udp.token.size, + data->msg_code, data->coap.coap_udp.message_id), + .options = &opts, + .payload = data->payload, + .payload_size = data->payload_size, + .token = data->coap.coap_udp.token + }; + int res = _fluf_coap_udp_header_serialize(&msg, buff, buff_size); + _RET_IF_ERROR(res); + + // content-format + if (data->payload_size) { + if (data->content_format != FLUF_COAP_FORMAT_NOT_DEFINED) { + res = _fluf_coap_options_add_u16(&opts, + _FLUF_COAP_OPTION_CONTENT_FORMAT, + data->content_format); + _RET_IF_ERROR(res); + } else { + return FLUF_ERR_INPUT_ARG; + } + } + + // accept option: only for BootstrapPack-Request + if (data->accept != FLUF_COAP_FORMAT_NOT_DEFINED + && data->operation == FLUF_OP_BOOTSTRAP_PACK_REQ) { + res = _fluf_coap_options_add_u16(&opts, _FLUF_COAP_OPTION_ACCEPT, + data->accept); + _RET_IF_ERROR(res); + } + + // uri-path + res = add_uri_path(&opts, data); + _RET_IF_ERROR(res); + + // observe option: only for Notify + if (data->operation == FLUF_OP_INF_CON_NOTIFY + || data->operation == FLUF_OP_INF_NON_CON_NOTIFY) { + res = _fluf_coap_options_add_u64(&opts, _FLUF_COAP_OPTION_OBSERVE, + data->observe_number); + _RET_IF_ERROR(res); + } + + // block option + if (data->block.block_type != FLUF_OPTION_BLOCK_NOT_DEFINED) { + res = _fluf_block_prepare(&opts, &data->block); + _RET_IF_ERROR(res); + } + + // etag + if (data->etag.size) { + res = _fluf_coap_options_add_data(&opts, _FLUF_COAP_OPTION_ETAG, + data->etag.bytes, data->etag.size); + _RET_IF_ERROR(res); + } + + // attributes: uri-query + if (data->operation == FLUF_OP_REGISTER + || data->operation == FLUF_OP_UPDATE) { + res = fluf_attr_register_prepare(&opts, &data->attr.register_attr); + } else if (data->operation == FLUF_OP_BOOTSTRAP_REQ) { + res = fluf_attr_bootstrap_prepare(&opts, &data->attr.bootstrap_attr); + } + _RET_IF_ERROR(res); + // msg serialize + return _fluf_coap_udp_msg_serialize(&msg, buff, buff_size, out_msg_size); +} + +int fluf_msg_prepare(fluf_data_t *data, + uint8_t *out_buff, + size_t out_buff_size, + size_t *out_msg_size) { + int res; + + switch (data->binding) { + case FLUF_BINDING_UDP: + case FLUF_BINDING_DTLS_PSK: + res = prepare_udp_msg(out_buff, out_buff_size, out_msg_size, data); + break; + default: + res = FLUF_ERR_BINDING; + break; + } + + return res; +} + +void fluf_init(uint32_t random_seed) { + g_rand_seed = (avs_rand_seed_t) random_seed; + g_fluf_msg_id = (uint16_t) avs_rand32_r(&g_rand_seed); +} diff --git a/src/fluf/fluf_senml_cbor_encoder.c b/src/fluf/fluf_senml_cbor_encoder.c new file mode 100644 index 00000000..8b767064 --- /dev/null +++ b/src/fluf/fluf_senml_cbor_encoder.c @@ -0,0 +1,256 @@ +/* + * Copyright 2023-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 "fluf_cbor_encoder.h" +#include "fluf_internal.h" + +#define _SENML_CBOR_PATH_MAX_LEN sizeof("/65534/65534/65534/65534") + +static size_t add_path(uint8_t *out_buff, + const fluf_uri_path_t *path, + size_t start_index, + size_t end_index, + int64_t label) { + size_t path_buf_pos = 0; + size_t out_buf_pos = 0; + char path_buff[_SENML_CBOR_PATH_MAX_LEN] = { 0 }; + + for (size_t i = start_index; i < end_index; i++) { + path_buff[path_buf_pos++] = '/'; + path_buf_pos += fluf_uint16_to_string_value(path->ids[i], + &path_buff[path_buf_pos]); + } + + out_buf_pos += fluf_cbor_ll_encode_int(out_buff, label); + out_buf_pos += + fluf_cbor_ll_string_begin(&out_buff[out_buf_pos], path_buf_pos); + memcpy(&out_buff[out_buf_pos], path_buff, path_buf_pos); + + return out_buf_pos + path_buf_pos; +} + +// HACK: +// The size of the internal_buff has been calculated so that a +// single record never exceeds its size. +static int prepare_payload(const fluf_io_out_entry_t *entry, + fluf_internal_senml_cbor_encoder_t *senml_cbor, + fluf_io_buff_t *buff_ctx, + bool first_entry) { + size_t buf_pos = 0; + size_t path_len = fluf_uri_path_length(&entry->path); + if (fluf_uri_path_outside_base(&entry->path, &senml_cbor->base_path) + || !fluf_uri_path_has(&entry->path, FLUF_ID_RID)) { + return FLUF_IO_ERR_INPUT_ARG; + } + + double time_s = entry->timestamp; + if (isnan(time_s)) { + time_s = 0.0; + } + + bool with_base_name = (first_entry && senml_cbor->base_path_len); + bool with_name = (path_len != senml_cbor->base_path_len); + bool with_time = + (senml_cbor->encode_time && senml_cbor->last_timestamp != time_s); + + // array + if (first_entry) { + buf_pos += fluf_cbor_ll_definite_array_begin( + &buff_ctx->internal_buff[buf_pos], senml_cbor->items_count); + } + // map + size_t map_size = (size_t) (with_base_name + with_name + with_time + 1); + buf_pos += + fluf_cbor_ll_definite_map_begin(&buff_ctx->internal_buff[buf_pos], + map_size); + + // basename - only once for READ operation + if (with_base_name) { + buf_pos += add_path(&buff_ctx->internal_buff[buf_pos], + &senml_cbor->base_path, 0, + senml_cbor->base_path_len, SENML_LABEL_BASE_NAME); + } + // name + if (with_name) { + buf_pos += + add_path(&buff_ctx->internal_buff[buf_pos], &entry->path, + senml_cbor->base_path_len, path_len, SENML_LABEL_NAME); + } + // base time + if (with_time) { + senml_cbor->last_timestamp = time_s; + buf_pos += fluf_cbor_ll_encode_int(&buff_ctx->internal_buff[buf_pos], + SENML_LABEL_BASE_TIME); + buf_pos += fluf_cbor_ll_encode_double(&buff_ctx->internal_buff[buf_pos], + time_s); + } + + // value + switch (entry->type) { + case FLUF_DATA_TYPE_BYTES: { + if (entry->value.bytes_or_string.offset != 0 + || (entry->value.bytes_or_string.full_length_hint + && entry->value.bytes_or_string.full_length_hint + != entry->value.bytes_or_string.chunk_length)) { + return FLUF_IO_ERR_INPUT_ARG; + } + buf_pos += fluf_cbor_ll_encode_uint(&buff_ctx->internal_buff[buf_pos], + SENML_LABEL_VALUE_OPAQUE); + buf_pos += fluf_cbor_ll_bytes_begin( + &buff_ctx->internal_buff[buf_pos], + entry->value.bytes_or_string.chunk_length); + buff_ctx->is_extended_type = true; + buff_ctx->remaining_bytes = entry->value.bytes_or_string.chunk_length; + break; + } + case FLUF_DATA_TYPE_STRING: { + if (entry->value.bytes_or_string.offset != 0 + || (entry->value.bytes_or_string.full_length_hint + && entry->value.bytes_or_string.full_length_hint + != entry->value.bytes_or_string.chunk_length)) { + return FLUF_IO_ERR_INPUT_ARG; + } + size_t string_length = entry->value.bytes_or_string.chunk_length; + if (!string_length && entry->value.bytes_or_string.data + && *(const char *) entry->value.bytes_or_string.data) { + string_length = + strlen((const char *) entry->value.bytes_or_string.data); + } + buf_pos += fluf_cbor_ll_encode_uint(&buff_ctx->internal_buff[buf_pos], + SENML_LABEL_VALUE_STRING); + buf_pos += fluf_cbor_ll_string_begin(&buff_ctx->internal_buff[buf_pos], + string_length); + buff_ctx->is_extended_type = true; + buff_ctx->remaining_bytes = string_length; + break; + } + case FLUF_DATA_TYPE_EXTERNAL_BYTES: { + buf_pos += fluf_cbor_ll_encode_uint(&buff_ctx->internal_buff[buf_pos], + SENML_LABEL_VALUE_OPAQUE); + buf_pos += fluf_cbor_ll_bytes_begin(&buff_ctx->internal_buff[buf_pos], + entry->value.external_data.length); + buff_ctx->is_extended_type = true; + buff_ctx->remaining_bytes = entry->value.external_data.length; + break; + } + case FLUF_DATA_TYPE_EXTERNAL_STRING: { + buf_pos += fluf_cbor_ll_encode_uint(&buff_ctx->internal_buff[buf_pos], + SENML_LABEL_VALUE_STRING); + buf_pos += fluf_cbor_ll_string_begin(&buff_ctx->internal_buff[buf_pos], + entry->value.external_data.length); + buff_ctx->is_extended_type = true; + buff_ctx->remaining_bytes = entry->value.external_data.length; + break; + } + case FLUF_DATA_TYPE_TIME: { + buf_pos += fluf_cbor_ll_encode_uint(&buff_ctx->internal_buff[buf_pos], + SENML_LABEL_VALUE); + buf_pos += fluf_cbor_ll_encode_tag(&buff_ctx->internal_buff[buf_pos], + CBOR_TAG_INTEGER_DATE_TIME); + buf_pos += fluf_cbor_ll_encode_int(&buff_ctx->internal_buff[buf_pos], + entry->value.time_value); + break; + } + case FLUF_DATA_TYPE_INT: { + buf_pos += fluf_cbor_ll_encode_uint(&buff_ctx->internal_buff[buf_pos], + SENML_LABEL_VALUE); + buf_pos += fluf_cbor_ll_encode_int(&buff_ctx->internal_buff[buf_pos], + entry->value.int_value); + break; + } + case FLUF_DATA_TYPE_DOUBLE: { + buf_pos += fluf_cbor_ll_encode_uint(&buff_ctx->internal_buff[buf_pos], + SENML_LABEL_VALUE); + buf_pos += fluf_cbor_ll_encode_double(&buff_ctx->internal_buff[buf_pos], + entry->value.double_value); + break; + } + case FLUF_DATA_TYPE_BOOL: { + buf_pos += fluf_cbor_ll_encode_uint(&buff_ctx->internal_buff[buf_pos], + SENML_LABEL_VALUE_BOOL); + buf_pos += fluf_cbor_ll_encode_bool(&buff_ctx->internal_buff[buf_pos], + entry->value.bool_value); + break; + } + case FLUF_DATA_TYPE_OBJLNK: { + size_t objlink_repr_len = sizeof(SENML_EXT_OBJLNK_REPR) - 1; + buf_pos += fluf_cbor_ll_string_begin(&buff_ctx->internal_buff[buf_pos], + objlink_repr_len); + memcpy(&buff_ctx->internal_buff[buf_pos], SENML_EXT_OBJLNK_REPR, + objlink_repr_len); + buf_pos += objlink_repr_len; + buf_pos += _fluf_io_out_add_objlink(buff_ctx, buf_pos, + entry->value.objlnk.oid, + entry->value.objlnk.iid); + break; + } + case FLUF_DATA_TYPE_UINT: { + buf_pos += fluf_cbor_ll_encode_uint(&buff_ctx->internal_buff[buf_pos], + SENML_LABEL_VALUE); + buf_pos += fluf_cbor_ll_encode_uint(&buff_ctx->internal_buff[buf_pos], + entry->value.uint_value); + break; + } + default: { return FLUF_IO_ERR_IO_TYPE; } + } + assert(buf_pos <= _FLUF_IO_CTX_BUFFER_LENGTH); + buff_ctx->bytes_in_internal_buff = buf_pos; + buff_ctx->remaining_bytes += buff_ctx->bytes_in_internal_buff; + return 0; +} + +int _fluf_senml_cbor_out_ctx_new_entry(fluf_io_out_ctx_t *ctx, + const fluf_io_out_entry_t *entry) { + assert(ctx->_format == FLUF_COAP_FORMAT_SENML_CBOR + || ctx->_format == FLUF_COAP_FORMAT_SENML_ETCH_CBOR); + + fluf_internal_senml_cbor_encoder_t *senml_cbor = &ctx->_encoder._senml; + fluf_io_buff_t *buff_ctx = &ctx->_buff; + + if (buff_ctx->remaining_bytes) { + return FLUF_IO_ERR_LOGIC; + } + + int res = prepare_payload(entry, senml_cbor, buff_ctx, + !senml_cbor->first_entry_added); + if (res) { + return res; + } + senml_cbor->first_entry_added = true; + return 0; +} + +int _fluf_senml_cbor_encoder_init(fluf_io_out_ctx_t *ctx, + const fluf_uri_path_t *base_path, + size_t items_count, + bool encode_time) { + if (!base_path) { + return FLUF_IO_ERR_INPUT_ARG; + } + + fluf_internal_senml_cbor_encoder_t *senml_cbor = &ctx->_encoder._senml; + senml_cbor->first_entry_added = false; + senml_cbor->base_path_len = fluf_uri_path_length(base_path); + if (senml_cbor->base_path_len) { + senml_cbor->base_path = *base_path; + } + senml_cbor->items_count = items_count; + senml_cbor->encode_time = encode_time; + senml_cbor->last_timestamp = 0.0; + return 0; +} diff --git a/src/fluf/fluf_tlv_decoder.c b/src/fluf/fluf_tlv_decoder.c new file mode 100644 index 00000000..3b5acca7 --- /dev/null +++ b/src/fluf/fluf_tlv_decoder.c @@ -0,0 +1,580 @@ +/* + * Copyright 2023-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 "fluf_internal.h" +#include "fluf_tlv_decoder.h" + +typedef enum { + TLV_ID_IID = 0, + TLV_ID_RIID = 1, + TLV_ID_RID_ARRAY = 2, + TLV_ID_RID = 3 +} fluf_tlv_id_type_t; + +static tlv_entry_t *tlv_entry_push(fluf_internal_tlv_decoder_t *tlv) { + if (tlv->entries == NULL) { + tlv->entries = &tlv->entries_block[0]; + } else { + AVS_ASSERT(tlv->entries < &tlv->entries_block[FLUF_TLV_MAX_DEPTH - 1], + "TLV decoder stack overflow"); + tlv->entries++; + } + return tlv->entries; +} + +static void tlv_entry_pop(fluf_internal_tlv_decoder_t *tlv) { + assert(tlv->entries); + if (tlv->entries == &tlv->entries_block[0]) { + tlv->entries = NULL; + } else { + tlv->entries--; + } +} + +static int tlv_get_all_remaining_bytes(fluf_io_in_ctx_t *ctx, + size_t *out_tlv_value_size, + void **out_chunk) { + if (ctx->_decoder._tlv.buff_size == ctx->_decoder._tlv.buff_offset) { + return -1; + } + *out_chunk = ((uint8_t *) ctx->_decoder._tlv.buff) + + ctx->_decoder._tlv.buff_offset; + // in the buff there could be: exactly one TLV entry, more than one TLV + // entry as well as only a partial TLV entry + *out_tlv_value_size = AVS_MIN( + ctx->_decoder._tlv.entries->length + - ctx->_decoder._tlv.entries->bytes_read, + ctx->_decoder._tlv.buff_size - ctx->_decoder._tlv.buff_offset); + ctx->_decoder._tlv.entries->bytes_read += *out_tlv_value_size; + ctx->_decoder._tlv.buff_offset += *out_tlv_value_size; + return 0; +} + +static int tlv_buff_read_by_copy(fluf_io_in_ctx_t *ctx, + void *out_chunk, + size_t chunk_size) { + if (ctx->_decoder._tlv.buff_size - ctx->_decoder._tlv.buff_offset + < chunk_size) { + return -1; + } + memcpy(out_chunk, + ((uint8_t *) ctx->_decoder._tlv.buff) + + ctx->_decoder._tlv.buff_offset, + chunk_size); + ctx->_decoder._tlv.buff_offset += chunk_size; + return 0; +} + +static int tlv_get_bytes(fluf_io_in_ctx_t *ctx) { + size_t already_read = ctx->_out_value.bytes_or_string.chunk_length; + int result = tlv_get_all_remaining_bytes( + ctx, &ctx->_out_value.bytes_or_string.chunk_length, + &ctx->_out_value.bytes_or_string.data); + if (result == -1 && ctx->_decoder._tlv.entries->length) { + ctx->_decoder._tlv.want_payload = true; + return FLUF_IO_WANT_NEXT_PAYLOAD; + } + if (already_read) { + ctx->_out_value.bytes_or_string.offset += already_read; + } + ctx->_out_value.bytes_or_string.full_length_hint = + ctx->_decoder._tlv.entries->length; + return 0; +} + +static int tlv_get_int(fluf_io_in_ctx_t *ctx, int64_t *value) { + /** Note: value is either &ctx->_out_value.int_value or + * &ctx->_out_value.time_value, so its value will be preserved between + * calls. */ + uint8_t *bytes; + size_t bytes_read; + if (!avs_is_power_of_2(ctx->_decoder._tlv.entries->length) + || ctx->_decoder._tlv.entries->length > 8) { + return FLUF_IO_ERR_FORMAT; + } + int result = + tlv_get_all_remaining_bytes(ctx, &bytes_read, (void **) &bytes); + if (result == -1) { + ctx->_decoder._tlv.want_payload = true; + return FLUF_IO_WANT_NEXT_PAYLOAD; + } + if (ctx->_decoder._tlv.entries->bytes_read - bytes_read == 0) { + *value = (bytes_read > 0 && ((int8_t) bytes[0]) < 0) ? -1 : 0; + } + for (size_t i = 0; i < bytes_read; ++i) { + *(uint64_t *) value <<= 8; + *value += bytes[i]; + } + return 0; +} + +static int tlv_get_uint(fluf_io_in_ctx_t *ctx) { + uint8_t *bytes; + size_t bytes_read; + if (!avs_is_power_of_2(ctx->_decoder._tlv.entries->length) + || ctx->_decoder._tlv.entries->length > 8) { + return FLUF_IO_ERR_FORMAT; + } + int result = + tlv_get_all_remaining_bytes(ctx, &bytes_read, (void **) &bytes); + if (result == -1) { + ctx->_decoder._tlv.want_payload = true; + return FLUF_IO_WANT_NEXT_PAYLOAD; + } + if (ctx->_decoder._tlv.entries->bytes_read - bytes_read == 0) { + ctx->_out_value.uint_value = 0; + } + for (size_t i = 0; i < bytes_read; ++i) { + ctx->_out_value.uint_value <<= 8; + ctx->_out_value.uint_value += bytes[i]; + } + return 0; +} + +static int tlv_get_double(fluf_io_in_ctx_t *ctx) { + uint8_t *bytes; + size_t bytes_already_read = ctx->_decoder._tlv.entries->bytes_read; + size_t bytes_read; + int result = + tlv_get_all_remaining_bytes(ctx, &bytes_read, (void **) &bytes); + if (result == -1) { + ctx->_decoder._tlv.want_payload = true; + return FLUF_IO_WANT_NEXT_PAYLOAD; + } + memcpy((uint8_t *) &ctx->_out_value.double_value + bytes_already_read, + bytes, bytes_read); + if (ctx->_decoder._tlv.entries->bytes_read + == ctx->_decoder._tlv.entries->length) { + switch (ctx->_decoder._tlv.entries->length) { + case 4: + ctx->_out_value.double_value = + avs_ntohf(*(uint32_t *) &ctx->_out_value.double_value); + break; + case 8: + ctx->_out_value.double_value = + avs_ntohd(*(uint64_t *) &ctx->_out_value.double_value); + break; + default: + return FLUF_IO_ERR_FORMAT; + } + } + return 0; +} + +static int tlv_get_bool(fluf_io_in_ctx_t *ctx) { + uint8_t *bytes; + size_t bytes_read; + if (ctx->_decoder._tlv.entries->length != 1) { + return FLUF_IO_ERR_FORMAT; + } + int result = + tlv_get_all_remaining_bytes(ctx, &bytes_read, (void **) &bytes); + if (result == -1) { + ctx->_decoder._tlv.want_payload = true; + return FLUF_IO_WANT_NEXT_PAYLOAD; + } + switch (bytes[0]) { + case 0: + ctx->_out_value.bool_value = false; + return 0; + case 1: + ctx->_out_value.bool_value = true; + return 0; + default: + return FLUF_IO_ERR_FORMAT; + } +} + +static int tlv_get_objlnk(fluf_io_in_ctx_t *ctx) { + uint8_t *bytes; + size_t bytes_already_read = ctx->_decoder._tlv.entries->bytes_read; + size_t bytes_read; + if (ctx->_decoder._tlv.entries->length != 4) { + return FLUF_IO_ERR_FORMAT; + } + int result = + tlv_get_all_remaining_bytes(ctx, &bytes_read, (void **) &bytes); + if (result == -1) { + ctx->_decoder._tlv.want_payload = true; + return FLUF_IO_WANT_NEXT_PAYLOAD; + } + for (size_t i = 0; i < bytes_read; ++i) { + if (bytes_already_read + i < 2) { + memcpy((uint8_t *) &ctx->_out_value.objlnk.oid + bytes_already_read + + i, + &bytes[i], 1); + } else { + memcpy((uint8_t *) &ctx->_out_value.objlnk.iid + bytes_already_read + + i - 2, + &bytes[i], 1); + } + } + if (ctx->_decoder._tlv.entries->bytes_read + == ctx->_decoder._tlv.entries->length) { + ctx->_out_value.objlnk.oid = + avs_convert_be16(ctx->_out_value.objlnk.oid); + ctx->_out_value.objlnk.iid = + avs_convert_be16(ctx->_out_value.objlnk.iid); + } + return 0; +} + +static int tlv_id_length_buff_read_by_copy(fluf_io_in_ctx_t *ctx, + uint8_t *out, + size_t length) { + if (ctx->_decoder._tlv.id_length_buff_read_offset + length + > sizeof(ctx->_decoder._tlv.id_length_buff)) { + return -1; + } + memcpy(out, + ((uint8_t *) ctx->_decoder._tlv.id_length_buff) + + ctx->_decoder._tlv.id_length_buff_read_offset, + length); + ctx->_decoder._tlv.id_length_buff_read_offset += length; + return 0; +} + +#define DEF_READ_SHORTENED(Type) \ + static int read_shortened_##Type(fluf_io_in_ctx_t *ctx, size_t length, \ + Type *out) { \ + uint8_t bytes[sizeof(Type)]; \ + if (tlv_id_length_buff_read_by_copy(ctx, bytes, length)) { \ + return -1; \ + } \ + *out = 0; \ + for (size_t i = 0; i < length; ++i) { \ + *out = (Type) ((*out << 8) + bytes[i]); \ + } \ + return 0; \ + } + +DEF_READ_SHORTENED(uint16_t) +DEF_READ_SHORTENED(size_t) + +static fluf_tlv_id_type_t tlv_type_from_typefield(uint8_t typefield) { + return (fluf_tlv_id_type_t) ((typefield >> 6) & 3); +} + +static fluf_id_type_t convert_id_type(uint8_t typefield) { + switch (tlv_type_from_typefield(typefield)) { + default: + AVS_UNREACHABLE("Invalid TLV ID type"); + case TLV_ID_IID: + return FLUF_ID_IID; + case TLV_ID_RIID: + return FLUF_ID_RIID; + case TLV_ID_RID_ARRAY: + case TLV_ID_RID: + return FLUF_ID_RID; + } +} + +static int get_id(fluf_io_in_ctx_t *ctx, + fluf_id_type_t *out_type, + uint16_t *out_id, + bool *out_has_value, + size_t *out_bytes_read) { + uint8_t typefield = ctx->_decoder._tlv.type_field; + *out_bytes_read = 1; + fluf_tlv_id_type_t tlv_type = tlv_type_from_typefield(typefield); + *out_type = convert_id_type(typefield); + size_t id_length = (typefield & 0x20) ? 2 : 1; + if (read_shortened_uint16_t(ctx, id_length, out_id)) { + return FLUF_IO_ERR_FORMAT; + } + *out_bytes_read += id_length; + + size_t length_length = ((typefield >> 3) & 3); + if (!length_length) { + ctx->_decoder._tlv.entries->length = (typefield & 7); + } else if (read_shortened_size_t(ctx, length_length, + &ctx->_decoder._tlv.entries->length)) { + return FLUF_IO_ERR_FORMAT; + } + *out_bytes_read += length_length; + /** + * This may seem a little bit strange, but entries that do not have any + * payload may be considered as having a value - that is, an empty one. On + * the other hand, if they DO have the payload, then it only makes sense to + * return them if they're "terminal" - i.e. they're either resource + * instances or single resources with value. + */ + *out_has_value = !ctx->_decoder._tlv.entries->length + || tlv_type == TLV_ID_RIID || tlv_type == TLV_ID_RID; + ctx->_decoder._tlv.entries->bytes_read = 0; + ctx->_decoder._tlv.entries->type = *out_type; + return 0; +} + +static int get_type_and_header(fluf_io_in_ctx_t *ctx) { + if (ctx->_decoder._tlv.type_field == 0xFF) { + if (tlv_buff_read_by_copy(ctx, &ctx->_decoder._tlv.type_field, 1)) { + ctx->_decoder._tlv.want_payload = true; + return FLUF_IO_WANT_NEXT_PAYLOAD; + } + if (ctx->_decoder._tlv.type_field == 0xFF) { + return FLUF_IO_ERR_FORMAT; + } + size_t id_length = (ctx->_decoder._tlv.type_field & 0x20) ? 2 : 1; + size_t length_length = ((ctx->_decoder._tlv.type_field >> 3) & 3); + ctx->_decoder._tlv.id_length_buff_bytes_need = + id_length + length_length; + } + if (ctx->_decoder._tlv.id_length_buff_bytes_need > 0) { + if (ctx->_decoder._tlv.buff_size - ctx->_decoder._tlv.buff_offset + <= 0) { + ctx->_decoder._tlv.want_payload = true; + return FLUF_IO_WANT_NEXT_PAYLOAD; + } + size_t bytes_to_read = AVS_MIN( + ctx->_decoder._tlv.id_length_buff_bytes_need, + ctx->_decoder._tlv.buff_size - ctx->_decoder._tlv.buff_offset); + if (tlv_buff_read_by_copy( + ctx, + ctx->_decoder._tlv.id_length_buff + + ctx->_decoder._tlv.id_length_buff_write_offset, + bytes_to_read)) { + return FLUF_IO_ERR_FORMAT; + } + ctx->_decoder._tlv.id_length_buff_write_offset += bytes_to_read; + ctx->_decoder._tlv.id_length_buff_bytes_need -= bytes_to_read; + if (ctx->_decoder._tlv.id_length_buff_bytes_need == 0) { + return 0; + } + ctx->_decoder._tlv.want_payload = true; + return FLUF_IO_WANT_NEXT_PAYLOAD; + } + return 0; +} + +static int tlv_get_path(fluf_io_in_ctx_t *ctx) { + if (ctx->_decoder._tlv.has_path) { + ctx->_out_path = ctx->_decoder._tlv.current_path; + return 0; + } + bool has_value = false; + fluf_id_type_t type; + uint16_t id; + while (!has_value) { + int result; + if ((result = get_type_and_header(ctx))) { + return result; + } + tlv_entry_t *parent = ctx->_decoder._tlv.entries; + if (!tlv_entry_push(&ctx->_decoder._tlv)) { + return FLUF_COAP_CODE_INTERNAL_SERVER_ERROR; + } + size_t header_len; + if ((result = get_id(ctx, &type, &id, &has_value, &header_len))) { + return result; + } + if (id == FLUF_ID_INVALID) { + return FLUF_IO_ERR_FORMAT; + } + if (parent) { + // Assume the child entry is fully read (which is in fact necessary + // to be able to return back to the parent). + parent->bytes_read += + ctx->_decoder._tlv.entries->length + header_len; + if (parent->bytes_read > parent->length) { + return FLUF_IO_ERR_FORMAT; + } + } + ctx->_decoder._tlv.current_path.ids[(size_t) type] = id; + ctx->_decoder._tlv.current_path.uri_len = (size_t) type + 1; + + if (fluf_uri_path_outside_base(&ctx->_decoder._tlv.current_path, + &ctx->_decoder._tlv.uri_path)) { + return FLUF_IO_ERR_FORMAT; + } + ctx->_decoder._tlv.type_field = 0xFF; + } + ctx->_out_path = ctx->_decoder._tlv.current_path; + ctx->_decoder._tlv.has_path = true; + return 0; +} + +static int tlv_next_entry(fluf_io_in_ctx_t *ctx) { + if (!ctx->_decoder._tlv.has_path) { + // Next entry is already available and should be processed. + return 0; + } + if (!ctx->_decoder._tlv.entries) { + return FLUF_IO_ERR_FORMAT; + } + if (ctx->_decoder._tlv.entries->length + > ctx->_decoder._tlv.entries->bytes_read) { + void *ignored; + size_t ignored_bytes_read; + int result = + tlv_get_all_remaining_bytes(ctx, &ignored_bytes_read, &ignored); + if (result) { + return result; + } + } + ctx->_decoder._tlv.has_path = false; + ctx->_decoder._tlv.type_field = 0xFF; + while (ctx->_decoder._tlv.entries + && ctx->_decoder._tlv.entries->length + == ctx->_decoder._tlv.entries->bytes_read) { + ctx->_decoder._tlv.current_path.ids[ctx->_decoder._tlv.entries->type] = + FLUF_ID_INVALID; + ctx->_decoder._tlv.current_path.uri_len = + (size_t) ctx->_decoder._tlv.entries->type; + tlv_entry_pop(&ctx->_decoder._tlv); + } + return 0; +} + +int _fluf_tlv_decoder_init(fluf_io_in_ctx_t *ctx, + const fluf_uri_path_t *request_uri) { + assert(ctx); + assert(request_uri); + assert(!fluf_uri_path_equal(request_uri, &FLUF_MAKE_ROOT_PATH())); + memset(&ctx->_out_value, 0x00, sizeof(ctx->_out_value)); + memset(&ctx->_out_path, 0x00, sizeof(ctx->_out_path)); + ctx->_decoder._tlv.uri_path = *request_uri; + ctx->_decoder._tlv.current_path = ctx->_decoder._tlv.uri_path; + ctx->_decoder._tlv.type_field = 0xFF; + ctx->_decoder._tlv.want_payload = true; + + return 0; +} + +int _fluf_tlv_decoder_feed_payload(fluf_io_in_ctx_t *ctx, + void *buff, + size_t buff_size, + bool payload_finished) { + if (ctx->_decoder._tlv.want_payload) { + ctx->_decoder._tlv.buff = buff; + ctx->_decoder._tlv.buff_size = buff_size; + ctx->_decoder._tlv.buff_offset = 0; + ctx->_decoder._tlv.payload_finished = payload_finished; + ctx->_decoder._tlv.want_payload = false; + return 0; + } + return FLUF_IO_ERR_LOGIC; +} + +int _fluf_tlv_decoder_get_entry(fluf_io_in_ctx_t *ctx, + fluf_data_type_t *inout_type_bitmask, + const fluf_res_value_t **out_value, + const fluf_uri_path_t **out_path) { + assert(ctx); + assert(inout_type_bitmask); + assert(out_value); + assert(out_path); + int result; + if (ctx->_decoder._tlv.want_payload) { + return FLUF_IO_WANT_NEXT_PAYLOAD; + } + *out_value = NULL; + *out_path = NULL; + if (ctx->_decoder._tlv.payload_finished + && (ctx->_decoder._tlv.buff_size == ctx->_decoder._tlv.buff_offset) + && !ctx->_decoder._tlv.want_disambiguation) { + *out_path = NULL; + return FLUF_IO_EOF; + } + + if (!ctx->_decoder._tlv.entries || !ctx->_decoder._tlv.has_path) { + memset(&ctx->_out_value, 0x00, sizeof(ctx->_out_value)); + memset(&ctx->_out_path, 0x00, sizeof(ctx->_out_path)); + result = tlv_get_path(ctx); + if (result) { + *out_path = NULL; + if (result == FLUF_IO_WANT_NEXT_PAYLOAD + && ctx->_decoder._tlv.payload_finished) { + return FLUF_IO_ERR_FORMAT; + } + return result; + } + *out_path = &ctx->_out_path; + if (ctx->_decoder._tlv.entries->length == 0) { + if (ctx->_decoder._tlv.entries->type == FLUF_ID_IID + || ctx->_decoder._tlv.entries->type == FLUF_ID_RIID) { + *inout_type_bitmask = FLUF_DATA_TYPE_NULL; + return tlv_next_entry(ctx); + } + } + } + + ctx->_decoder._tlv.want_disambiguation = false; + switch (*inout_type_bitmask) { + case FLUF_DATA_TYPE_NULL: + return FLUF_IO_ERR_FORMAT; + case FLUF_DATA_TYPE_BYTES: + case FLUF_DATA_TYPE_STRING: { + result = tlv_get_bytes(ctx); + break; + } + case FLUF_DATA_TYPE_INT: + result = tlv_get_int(ctx, &ctx->_out_value.int_value); + break; + case FLUF_DATA_TYPE_UINT: + result = tlv_get_uint(ctx); + break; + case FLUF_DATA_TYPE_DOUBLE: + result = tlv_get_double(ctx); + break; + case FLUF_DATA_TYPE_BOOL: + result = tlv_get_bool(ctx); + break; + case FLUF_DATA_TYPE_OBJLNK: + result = tlv_get_objlnk(ctx); + break; + case FLUF_DATA_TYPE_TIME: + result = tlv_get_int(ctx, &ctx->_out_value.time_value); + break; + default: + ctx->_decoder._tlv.want_disambiguation = true; + *out_path = &ctx->_out_path; + return FLUF_IO_WANT_TYPE_DISAMBIGUATION; + } + if (result) { + if (result == FLUF_IO_WANT_NEXT_PAYLOAD + && ctx->_decoder._tlv.payload_finished) { + return FLUF_IO_ERR_FORMAT; + } + return result; + } + + // reason about parsing state + if (ctx->_decoder._tlv.entries->bytes_read + == ctx->_decoder._tlv.entries->length) { + if ((result = tlv_next_entry(ctx))) { + return result; + } + *out_path = &ctx->_out_path; + *out_value = &ctx->_out_value; + return 0; + } else { + if (!ctx->_decoder._tlv.payload_finished + && (ctx->_decoder._tlv.buff_size + == ctx->_decoder._tlv.buff_offset)) { + if (*inout_type_bitmask == FLUF_DATA_TYPE_BYTES + || *inout_type_bitmask == FLUF_DATA_TYPE_STRING) { + *out_path = &ctx->_out_path; + *out_value = &ctx->_out_value; + return 0; + } + ctx->_decoder._tlv.want_payload = true; + return FLUF_IO_WANT_NEXT_PAYLOAD; + } + return FLUF_IO_ERR_FORMAT; + } +} diff --git a/src/fluf/fluf_tlv_decoder.h b/src/fluf/fluf_tlv_decoder.h new file mode 100644 index 00000000..7e65b25a --- /dev/null +++ b/src/fluf/fluf_tlv_decoder.h @@ -0,0 +1,28 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#ifndef FLUF_IN_CTX_FLUF_TLV_DECODER_H +#define FLUF_IN_CTX_FLUF_TLV_DECODER_H + +#include + +int _fluf_tlv_decoder_init(fluf_io_in_ctx_t *ctx, + const fluf_uri_path_t *request_uri); + +int _fluf_tlv_decoder_feed_payload(fluf_io_in_ctx_t *ctx, + void *buff, + size_t buff_size, + bool payload_finished); + +int _fluf_tlv_decoder_get_entry(fluf_io_in_ctx_t *ctx, + fluf_data_type_t *inout_type_bitmask, + const fluf_res_value_t **out_value, + const fluf_uri_path_t **out_path); + +#endif // FLUF_IN_CTX_FLUF_TLV_DECODER_H diff --git a/src/fluf/fluf_utils.c b/src/fluf/fluf_utils.c new file mode 100644 index 00000000..ee4665b1 --- /dev/null +++ b/src/fluf/fluf_utils.c @@ -0,0 +1,290 @@ +/* + * Copyright 2023-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 + +bool fluf_uri_path_increasing(const fluf_uri_path_t *previous_path, + const fluf_uri_path_t *current_path) { + size_t path_to_check_len = + AVS_MIN(previous_path->uri_len, current_path->uri_len); + + for (size_t i = 0; i < path_to_check_len; i++) { + if (current_path->ids[i] > previous_path->ids[i]) { + return true; + } else if (current_path->ids[i] < previous_path->ids[i]) { + return false; + } + } + return previous_path->uri_len < current_path->uri_len; +} + +int fluf_validate_obj_version(const char *version) { + if (!version) { + return 0; + } + // accepted format is X.Y where X and Y are digits + if (!isdigit(version[0]) || version[1] != '.' || !isdigit(version[2]) + || version[3] != '\0') { + return FLUF_IO_ERR_INPUT_ARG; + } + return 0; +} + +static size_t uint64_to_string_value_internal(uint64_t value, + char *out_buff, + size_t *dot_position, + bool ignore_zeros) { + char buff[FLUF_U64_STR_MAX_LEN + 1] = { 0 }; + int idx = sizeof(buff) - 1; + size_t msg_size = 0; + do { + char digit = (char) ('0' + (value % 10)); + value /= 10; + if (ignore_zeros) { + if (digit == '0') { + continue; + } else { + ignore_zeros = false; + } + } + buff[idx--] = digit; + msg_size++; + if (dot_position && *dot_position == msg_size) { + buff[idx--] = '.'; + msg_size++; + } + } while (value > 0); + assert(!dot_position || *dot_position < msg_size); + + memcpy(out_buff, &buff[idx + 1], msg_size); + return msg_size; +} + +size_t fluf_uint64_to_string_value(uint64_t value, char *out_buff) { + return uint64_to_string_value_internal(value, out_buff, NULL, false); +} + +size_t fluf_uint32_to_string_value(uint32_t value, char *out_buff) { + return uint64_to_string_value_internal( + (uint64_t) value, out_buff, NULL, false); +} + +size_t fluf_uint16_to_string_value(uint16_t value, char *out_buff) { + return uint64_to_string_value_internal( + (uint64_t) value, out_buff, NULL, false); +} + +#define _MAX_FRACTION_SIZE_IN_EXPONENTIAL_NOTATION \ + (sizeof("2.2250738585072014") - 1 - 2) + +size_t fluf_double_to_simple_str_value(double value, char *out_buff) { + size_t out_len = 0; + char buff[FLUF_U64_STR_MAX_LEN + 1] = { 0 }; + size_t bytes_to_copy; + + if (isnan(value)) { + memcpy(out_buff, "nan", 3); + return 3; + } else if (value == 0.0) { + out_buff[0] = '0'; + return 1; + } + + if (value < 0.0) { + out_buff[out_len++] = '-'; + value = -value; + } + if (isinf(value)) { + memcpy(&out_buff[out_len], "inf", 3); + out_len += 3; + return out_len; + } + + // X.Y format + if (value > 1.0 && value < UINT64_MAX && (value - (uint64_t) value)) { + size_t dot_position = 0; + while (value - (uint64_t) value) { + value = value * 10.0; + dot_position++; + } + out_len += uint64_to_string_value_internal( + (uint64_t) value, &out_buff[out_len], &dot_position, false); + } else if (value >= 1.0 && value < UINT64_MAX) { // X format + size_t nil_counter = 0; + while (value > UINT64_MAX) { + value = value / 10.0; + nil_counter++; + } + out_len += uint64_to_string_value_internal( + (uint64_t) value, &out_buff[out_len], NULL, false); + memset(&out_buff[out_len], '0', nil_counter); + out_len += nil_counter; + } else if (value >= UINT64_MAX) { // X.YeZ format + size_t exponential_value = 0; + double temp_value = value; + while (temp_value > 10.0) { + temp_value = temp_value / 10.0; + exponential_value++; + } + while (value > UINT64_MAX) { + value = value / 10.0; + } + bytes_to_copy = uint64_to_string_value_internal( + (uint64_t) value, buff, NULL, true); + out_buff[out_len++] = buff[0]; + bytes_to_copy--; + out_buff[out_len++] = '.'; + bytes_to_copy = AVS_MIN(bytes_to_copy, + _MAX_FRACTION_SIZE_IN_EXPONENTIAL_NOTATION); + memcpy(&out_buff[out_len], &buff[1], bytes_to_copy); + out_len += bytes_to_copy; + out_buff[out_len++] = 'e'; + out_len += uint64_to_string_value_internal( + (uint64_t) exponential_value, &out_buff[out_len], NULL, false); + } else if (value < 1 && value > 1e-10) { // 0.X format + size_t nil_counter = 0; + while (value - (uint64_t) value) { + value = value * 10.0; + nil_counter++; + } + + bytes_to_copy = uint64_to_string_value_internal( + (uint64_t) value, buff, NULL, false); + size_t nil_to_add = nil_counter - bytes_to_copy; + memcpy(&out_buff[out_len], "0.", 2); + out_len += 2; + if (nil_to_add > 0) { + memset(&out_buff[out_len], '0', nil_to_add); + out_len += nil_to_add; + } + memcpy(&out_buff[out_len], buff, bytes_to_copy); + out_len += bytes_to_copy; + } else { // X.Ye-Z format + size_t exponential_value = 0; + double temp_value = value; + while (temp_value < 1.0) { + temp_value = temp_value * 10.0; + exponential_value++; + } + while (value - (uint64_t) value) { + value = value * 10.0; + } + bytes_to_copy = uint64_to_string_value_internal( + (uint64_t) value, buff, NULL, true); + out_buff[out_len++] = buff[0]; + bytes_to_copy--; + if (bytes_to_copy) { + out_buff[out_len++] = '.'; + bytes_to_copy = AVS_MIN(bytes_to_copy, + _MAX_FRACTION_SIZE_IN_EXPONENTIAL_NOTATION); + } + memcpy(&out_buff[out_len], &buff[1], bytes_to_copy); + out_len += bytes_to_copy; + out_buff[out_len++] = 'e'; + out_buff[out_len++] = '-'; + out_len += uint64_to_string_value_internal( + (uint64_t) exponential_value, &out_buff[out_len], NULL, false); + } + + return out_len; +} + +int fluf_string_to_uint32_value(const char *buff, + size_t buff_len, + uint32_t *out_val) { + uint32_t value = 0; + uint32_t multiplicator = 1; + uint8_t single_value = 0; + + if (!buff_len) { + return -1; + } + + for (size_t i = buff_len; i > 0; i--) { + single_value = (uint8_t) buff[i - 1] - '0'; + if (single_value > 9) { + return -1; // incorrect buffer + } + value += (uint32_t) single_value * multiplicator; + multiplicator = multiplicator * 10; + } + *out_val = value; + return 0; +} + +int fluf_string_to_simple_double_value(const char *buff, + size_t buff_len, + double *out_val) { + bool is_negative = false; + if (!buff_len) { + return -1; + } + if (buff[0] == '-') { + is_negative = true; + buff++; + buff_len--; + } + if (!buff_len) { + return -1; + } + + bool is_fractional_part = false; + for (size_t i = buff_len; i > 0; i--) { + if (buff[i - 1] == '.') { + is_fractional_part = true; + break; + } + } + + uint8_t single_value; + double fractional_value = 0; + double fractional_divider = 1.0; + double integer_value = 0; + double multiplicator = 1.0; + for (size_t i = buff_len; i > 0; i--) { + if (buff[i - 1] == '.') { + is_fractional_part = false; + multiplicator = 1; + } else { + single_value = (uint8_t) buff[i - 1] - '0'; + if (single_value > 9) { + return -1; // incorrect buffer + } + if (is_fractional_part) { + fractional_value += single_value * multiplicator; + fractional_divider *= 10; + } else { + integer_value += single_value * multiplicator; + } + multiplicator = multiplicator * 10; + } + } + + *out_val = integer_value; + + if (fractional_value) { + *out_val += fractional_value / fractional_divider; + } + if (is_negative) { + *out_val = -(*out_val); + } + + return 0; +} diff --git a/tests/anj/dm/dm_core.c b/tests/anj/dm/dm_core.c new file mode 100644 index 00000000..0c7fe34e --- /dev/null +++ b/tests/anj/dm/dm_core.c @@ -0,0 +1,144 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +/** + * @brief Tests for data model Core API + * @note Note that all strings and values used in this file have + * no special meaning, they are used only for testing purposes. + */ + +#include + +#include + +#define OID_1 1 +#define OID_2 2 +#define OID_3 3 +#define OID_4 4 + +typedef struct test_object_struct { + const dm_object_def_t *def; +} test_object_t; + +static const dm_object_def_t DEF_OID_1 = { + .oid = OID_1, +}; + +static const dm_object_def_t DEF_OID_2 = { + .oid = OID_2, +}; + +static const dm_object_def_t DEF_OID_3 = { + .oid = OID_3, +}; + +static const dm_object_def_t DEF_OID_4 = { + .oid = OID_4, +}; + +static const test_object_t TEST_OBJECT_1 = { + .def = &DEF_OID_1, +}; + +static const test_object_t TEST_OBJECT_2 = { + .def = &DEF_OID_2, +}; + +static const test_object_t TEST_OBJECT_3 = { + .def = &DEF_OID_3, +}; + +static const test_object_t TEST_OBJECT_4 = { + .def = &DEF_OID_4, +}; + +#define OBJ_MAX 4 +static dm_t dm; +static dm_installed_object_t objects[OBJ_MAX]; + +#define SET_UP() dm_initialize(&dm, objects, OBJ_MAX) + +AVS_UNIT_TEST(DataModelCore, RegisterAscendingOrder) { + SET_UP(); + AVS_UNIT_ASSERT_EQUAL(dm.objects_count, 0); + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT_1.def)); + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT_2.def)); + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT_3.def)); + AVS_UNIT_ASSERT_EQUAL((*dm.objects[0].def)->oid, OID_1); + AVS_UNIT_ASSERT_EQUAL((*dm.objects[1].def)->oid, OID_2); + AVS_UNIT_ASSERT_EQUAL((*dm.objects[2].def)->oid, OID_3); + AVS_UNIT_ASSERT_EQUAL(dm.objects_count, 3); +} + +AVS_UNIT_TEST(DataModelCore, RegisterDescendingOrder) { + SET_UP(); + AVS_UNIT_ASSERT_EQUAL(dm.objects_count, 0); + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT_3.def)); + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT_2.def)); + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT_1.def)); + AVS_UNIT_ASSERT_EQUAL((*dm.objects[0].def)->oid, OID_1); + AVS_UNIT_ASSERT_EQUAL((*dm.objects[1].def)->oid, OID_2); + AVS_UNIT_ASSERT_EQUAL((*dm.objects[2].def)->oid, OID_3); + AVS_UNIT_ASSERT_EQUAL(dm.objects_count, 3); +} + +AVS_UNIT_TEST(DataModelCore, RegisterUnordered) { + SET_UP(); + AVS_UNIT_ASSERT_EQUAL(dm.objects_count, 0); + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT_2.def)); + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT_1.def)); + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT_3.def)); + AVS_UNIT_ASSERT_EQUAL((*dm.objects[0].def)->oid, OID_1); + AVS_UNIT_ASSERT_EQUAL((*dm.objects[1].def)->oid, OID_2); + AVS_UNIT_ASSERT_EQUAL((*dm.objects[2].def)->oid, OID_3); + AVS_UNIT_ASSERT_EQUAL(dm.objects_count, 3); +} + +AVS_UNIT_TEST(DataModelCore, RegisterForbidenRegistered) { + SET_UP(); + AVS_UNIT_ASSERT_EQUAL(dm.objects_count, 0); + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT_2.def)); + AVS_UNIT_ASSERT_EQUAL((*dm.objects[0].def)->oid, OID_2); + AVS_UNIT_ASSERT_EQUAL(dm.objects_count, 1); + AVS_UNIT_ASSERT_FAILED(dm_register_object(&dm, &TEST_OBJECT_2.def)); + AVS_UNIT_ASSERT_EQUAL(dm.objects_count, 1); +} + +AVS_UNIT_TEST(DataModelCore, RegisterTooMany) { + dm_initialize(&dm, objects, 1); + AVS_UNIT_ASSERT_EQUAL(dm.objects_count, 0); + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT_2.def)); + AVS_UNIT_ASSERT_FAILED(dm_register_object(&dm, &TEST_OBJECT_1.def)); + AVS_UNIT_ASSERT_EQUAL((*dm.objects[0].def)->oid, OID_2); + AVS_UNIT_ASSERT_EQUAL(dm.objects_count, 1); +} + +AVS_UNIT_TEST(DataModelCore, Deregister) { + SET_UP(); + AVS_UNIT_ASSERT_EQUAL(dm.objects_count, 0); + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT_1.def)); + AVS_UNIT_ASSERT_EQUAL(dm.objects_count, 1); + AVS_UNIT_ASSERT_SUCCESS(dm_unregister_object(&dm, &TEST_OBJECT_1.def)); + AVS_UNIT_ASSERT_EQUAL(dm.objects_count, 0); +} + +AVS_UNIT_TEST(DataModelCore, DeregisterOneOfMany) { + SET_UP(); + AVS_UNIT_ASSERT_EQUAL(dm.objects_count, 0); + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT_2.def)); + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT_1.def)); + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT_3.def)); + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT_4.def)); + AVS_UNIT_ASSERT_EQUAL(dm.objects_count, 4); + AVS_UNIT_ASSERT_SUCCESS(dm_unregister_object(&dm, &TEST_OBJECT_2.def)); + AVS_UNIT_ASSERT_EQUAL(dm.objects_count, 3); + AVS_UNIT_ASSERT_EQUAL((*dm.objects[0].def)->oid, OID_1); + AVS_UNIT_ASSERT_EQUAL((*dm.objects[1].def)->oid, OID_3); + AVS_UNIT_ASSERT_EQUAL((*dm.objects[2].def)->oid, OID_4); +} diff --git a/tests/anj/dm/dm_core_register_discover.c b/tests/anj/dm/dm_core_register_discover.c new file mode 100644 index 00000000..b1313215 --- /dev/null +++ b/tests/anj/dm/dm_core_register_discover.c @@ -0,0 +1,605 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +/** + * @brief Tests for Data Model Core API + * @note Note that all strings and values read from Data Model in this file have + * no special meaning, they are used only for testing purposes. + */ +#include + +#include + +#include +#include + +#include "../../../src/anj/dm_core.h" + +#define OID_4 4 // test object 4 +#define OID_5 5 // test object 5 +#define OID_6 6 // test object 6 + +static int list_instances(dm_t *dm, + const dm_object_def_t *const *obj_ptr, + dm_list_ctx_t *ctx) { + (void) dm; + (void) obj_ptr; + dm_emit(ctx, 0); + dm_emit(ctx, 1); + return 0; +} + +static int list_resources(dm_t *dm, + const dm_object_def_t *const *obj_ptr, + fluf_iid_t iid, + dm_resource_list_ctx_t *ctx) { + (void) dm; + (void) obj_ptr; + (void) iid; + dm_emit_res(ctx, 0, DM_RES_R, DM_RES_PRESENT); + dm_emit_res(ctx, 1, DM_RES_R, DM_RES_PRESENT); + return 0; +} + +static int list_resources_OID_6(dm_t *dm, + const dm_object_def_t *const *obj_ptr, + fluf_iid_t iid, + dm_resource_list_ctx_t *ctx) { + (void) dm; + (void) obj_ptr; + (void) iid; + dm_emit_res(ctx, 0, DM_RES_R, DM_RES_PRESENT); + dm_emit_res(ctx, 1, DM_RES_RM, DM_RES_PRESENT); + dm_emit_res(ctx, 2, DM_RES_E, DM_RES_PRESENT); + return 0; +} + +static int list_resource_instances(dm_t *dm, + const dm_object_def_t *const *obj_ptr, + fluf_iid_t iid, + fluf_rid_t rid, + dm_list_ctx_t *ctx) { + (void) dm; + (void) obj_ptr; + (void) iid; + + switch (rid) { + case 1: { + for (fluf_riid_t i = 0; i < 5; ++i) { + dm_emit(ctx, i); + } + return 0; + } + default: + AVS_UNREACHABLE( + "Attempted to list instances in a single-instance resource"); + return FLUF_COAP_CODE_INTERNAL_SERVER_ERROR; + } +} + +static const dm_object_def_t DEF_TEST_OBJ_4 = { + .oid = OID_4, + .handlers.list_resources = list_resources, + .handlers.list_instances = dm_list_instances_SINGLE +}; +static const dm_object_def_t *const DEF_TEST_OBJ_4_PTR = &DEF_TEST_OBJ_4; + +static const dm_object_def_t DEF_TEST_OBJ_5 = { + .oid = OID_5, + .handlers.list_resources = list_resources, + .handlers.list_instances = dm_list_instances_SINGLE +}; +static const dm_object_def_t *const DEF_TEST_OBJ_5_PTR = &DEF_TEST_OBJ_5; + +const dm_object_def_t DEF_TEST_OBJ_6 = { + .oid = OID_6, + .handlers.list_resource_instances = list_resource_instances, + .handlers.list_resources = list_resources_OID_6, + .handlers.list_instances = list_instances +}; +const dm_object_def_t *const DEF_TEST_OBJ_6_PTR = &DEF_TEST_OBJ_6; + +#define TEST_BUFF_LEN 20 +static struct test_buffer { + fluf_uri_path_t buff[TEST_BUFF_LEN]; + size_t buff_len; +} g_test_buffer; + +static int register_clb(void *arg, fluf_uri_path_t *uri) { + struct test_buffer *buff = (struct test_buffer *) arg; + buff->buff[buff->buff_len++] = *uri; + return 0; +} + +static int discover_clb(void *arg, fluf_uri_path_t *uri) { + struct test_buffer *buff = (struct test_buffer *) arg; + buff->buff[buff->buff_len++] = *uri; + return 0; +} + +#define OBJ_MAX 4 +static dm_t dm; +static dm_installed_object_t objects[OBJ_MAX]; + +#define SET_UP() \ + g_test_buffer = (struct test_buffer) { 0 }; \ + memset(&g_test_buffer, 0x00, sizeof(g_test_buffer)); \ + dm_initialize(&dm, objects, OBJ_MAX) + +static void fluf_uri_path_t_compare(const fluf_uri_path_t *a, + const fluf_uri_path_t *b) { + AVS_UNIT_ASSERT_EQUAL(a->uri_len, b->uri_len); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(a->ids, b->ids, FLUF_URI_PATH_MAX_LENGTH); +} + +AVS_UNIT_TEST(DataModelRegisterDiscover, QueryDMForRegister) { + SET_UP(); + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &DEF_TEST_OBJ_4_PTR)); + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &DEF_TEST_OBJ_5_PTR)); + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &DEF_TEST_OBJ_6_PTR)); + + dm_register_ctx_t ctx = { + .callback = register_clb, + .arg = &g_test_buffer + }; + AVS_UNIT_ASSERT_SUCCESS(dm_register_prepare(&dm, &ctx)); + fluf_uri_path_t expected[8] = { + [0] = FLUF_MAKE_OBJECT_PATH(OID_4), + [1] = FLUF_MAKE_INSTANCE_PATH(OID_4, 0), + [2] = FLUF_MAKE_OBJECT_PATH(OID_5), + [3] = FLUF_MAKE_INSTANCE_PATH(OID_5, 0), + [4] = FLUF_MAKE_OBJECT_PATH(OID_6), + [5] = FLUF_MAKE_INSTANCE_PATH(OID_6, 0), + [6] = FLUF_MAKE_INSTANCE_PATH(OID_6, 1), + }; + AVS_UNIT_ASSERT_EQUAL(g_test_buffer.buff_len, 7); + for (size_t i = 0; i < AVS_ARRAY_SIZE(expected); ++i) { + fluf_uri_path_t_compare(&g_test_buffer.buff[i], &expected[i]); + } +} + +AVS_UNIT_TEST(DataModelRegisterDiscover, CorePrepareDiscoverObject) { + SET_UP(); + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &DEF_TEST_OBJ_6_PTR)); + + fluf_uri_path_t uri = FLUF_MAKE_OBJECT_PATH(OID_6); + dm_discover_ctx_t ctx = { + .callback = discover_clb, + .arg = &g_test_buffer + }; + AVS_UNIT_ASSERT_SUCCESS(dm_discover_resp_prepare(&dm, &uri, NULL, &ctx)); + + fluf_uri_path_t expected[10] = { + [0] = FLUF_MAKE_OBJECT_PATH(OID_6), + [1] = FLUF_MAKE_INSTANCE_PATH(OID_6, 0), + [2] = FLUF_MAKE_RESOURCE_PATH(OID_6, 0, 0), + [3] = FLUF_MAKE_RESOURCE_PATH(OID_6, 0, 1), + [4] = FLUF_MAKE_RESOURCE_PATH(OID_6, 0, 2), + [5] = FLUF_MAKE_INSTANCE_PATH(OID_6, 1), + [6] = FLUF_MAKE_RESOURCE_PATH(OID_6, 1, 0), + [7] = FLUF_MAKE_RESOURCE_PATH(OID_6, 1, 1), + [8] = FLUF_MAKE_RESOURCE_PATH(OID_6, 1, 2), + }; + AVS_UNIT_ASSERT_EQUAL(g_test_buffer.buff_len, 9); + for (size_t i = 0; i < AVS_ARRAY_SIZE(expected); ++i) { + fluf_uri_path_t_compare(&g_test_buffer.buff[i], &expected[i]); + } +} + +AVS_UNIT_TEST(DataModelRegisterDiscover, + CorePrepareDiscoverObjectWithDepthZero) { + SET_UP(); + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &DEF_TEST_OBJ_6_PTR)); + + fluf_uri_path_t uri = FLUF_MAKE_OBJECT_PATH(OID_6); + dm_discover_ctx_t ctx = { + .callback = discover_clb, + .arg = &g_test_buffer + }; + uint8_t depth = 0; + AVS_UNIT_ASSERT_SUCCESS(dm_discover_resp_prepare(&dm, &uri, &depth, &ctx)); + + fluf_uri_path_t expected[3] = { + [0] = FLUF_MAKE_OBJECT_PATH(OID_6), + }; + AVS_UNIT_ASSERT_EQUAL(g_test_buffer.buff_len, 1); + for (size_t i = 0; i < AVS_ARRAY_SIZE(expected); ++i) { + fluf_uri_path_t_compare(&g_test_buffer.buff[i], &expected[i]); + } +} + +AVS_UNIT_TEST(DataModelRegisterDiscover, + CorePrepareDiscoverObjectWithDepthOne) { + SET_UP(); + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &DEF_TEST_OBJ_6_PTR)); + + fluf_uri_path_t uri = FLUF_MAKE_OBJECT_PATH(OID_6); + dm_discover_ctx_t ctx = { + .callback = discover_clb, + .arg = &g_test_buffer + }; + uint8_t depth = 1; + AVS_UNIT_ASSERT_SUCCESS(dm_discover_resp_prepare(&dm, &uri, &depth, &ctx)); + + fluf_uri_path_t expected[5] = { + [0] = FLUF_MAKE_OBJECT_PATH(OID_6), + [1] = FLUF_MAKE_INSTANCE_PATH(OID_6, 0), + [2] = FLUF_MAKE_INSTANCE_PATH(OID_6, 1), + }; + AVS_UNIT_ASSERT_EQUAL(g_test_buffer.buff_len, 3); + for (size_t i = 0; i < AVS_ARRAY_SIZE(expected); ++i) { + fluf_uri_path_t_compare(&g_test_buffer.buff[i], &expected[i]); + } +} + +AVS_UNIT_TEST(DataModelRegisterDiscover, + CorePrepareDiscoverObjectWithDepthTwo) { + SET_UP(); + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &DEF_TEST_OBJ_6_PTR)); + + fluf_uri_path_t uri = FLUF_MAKE_OBJECT_PATH(OID_6); + dm_discover_ctx_t ctx = { + .callback = discover_clb, + .arg = &g_test_buffer + }; + uint8_t depth = 2; + AVS_UNIT_ASSERT_SUCCESS(dm_discover_resp_prepare(&dm, &uri, &depth, &ctx)); + + fluf_uri_path_t expected[10] = { + [0] = FLUF_MAKE_OBJECT_PATH(OID_6), + [1] = FLUF_MAKE_INSTANCE_PATH(OID_6, 0), + [2] = FLUF_MAKE_RESOURCE_PATH(OID_6, 0, 0), + [3] = FLUF_MAKE_RESOURCE_PATH(OID_6, 0, 1), + [4] = FLUF_MAKE_RESOURCE_PATH(OID_6, 0, 2), + [5] = FLUF_MAKE_INSTANCE_PATH(OID_6, 1), + [6] = FLUF_MAKE_RESOURCE_PATH(OID_6, 1, 0), + [7] = FLUF_MAKE_RESOURCE_PATH(OID_6, 1, 1), + [8] = FLUF_MAKE_RESOURCE_PATH(OID_6, 1, 2), + }; + AVS_UNIT_ASSERT_EQUAL(g_test_buffer.buff_len, 9); + for (size_t i = 0; i < AVS_ARRAY_SIZE(expected); ++i) { + fluf_uri_path_t_compare(&g_test_buffer.buff[i], &expected[i]); + } +} + +AVS_UNIT_TEST(DataModelRegisterDiscover, + CorePrepareDiscoverObjectWithDepthThree) { + SET_UP(); + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &DEF_TEST_OBJ_6_PTR)); + + fluf_uri_path_t uri = FLUF_MAKE_OBJECT_PATH(OID_6); + dm_discover_ctx_t ctx = { + .callback = discover_clb, + .arg = &g_test_buffer + }; + uint8_t depth = 3; + AVS_UNIT_ASSERT_SUCCESS(dm_discover_resp_prepare(&dm, &uri, &depth, &ctx)); + + fluf_uri_path_t expected[20] = { + [0] = FLUF_MAKE_OBJECT_PATH(OID_6), + [1] = FLUF_MAKE_INSTANCE_PATH(OID_6, 0), + [2] = FLUF_MAKE_RESOURCE_PATH(OID_6, 0, 0), + [3] = FLUF_MAKE_RESOURCE_PATH(OID_6, 0, 1), + [4] = FLUF_MAKE_RESOURCE_INSTANCE_PATH(OID_6, 0, 1, 0), + [5] = FLUF_MAKE_RESOURCE_INSTANCE_PATH(OID_6, 0, 1, 1), + [6] = FLUF_MAKE_RESOURCE_INSTANCE_PATH(OID_6, 0, 1, 2), + [7] = FLUF_MAKE_RESOURCE_INSTANCE_PATH(OID_6, 0, 1, 3), + [8] = FLUF_MAKE_RESOURCE_INSTANCE_PATH(OID_6, 0, 1, 4), + [9] = FLUF_MAKE_RESOURCE_PATH(OID_6, 0, 2), + [10] = FLUF_MAKE_INSTANCE_PATH(OID_6, 1), + [11] = FLUF_MAKE_RESOURCE_PATH(OID_6, 1, 0), + [12] = FLUF_MAKE_RESOURCE_PATH(OID_6, 1, 1), + [13] = FLUF_MAKE_RESOURCE_INSTANCE_PATH(OID_6, 1, 1, 0), + [14] = FLUF_MAKE_RESOURCE_INSTANCE_PATH(OID_6, 1, 1, 1), + [15] = FLUF_MAKE_RESOURCE_INSTANCE_PATH(OID_6, 1, 1, 2), + [16] = FLUF_MAKE_RESOURCE_INSTANCE_PATH(OID_6, 1, 1, 3), + [17] = FLUF_MAKE_RESOURCE_INSTANCE_PATH(OID_6, 1, 1, 4), + [18] = FLUF_MAKE_RESOURCE_PATH(OID_6, 1, 2), + }; + AVS_UNIT_ASSERT_EQUAL(g_test_buffer.buff_len, 19); + for (size_t i = 0; i < AVS_ARRAY_SIZE(expected); ++i) { + fluf_uri_path_t_compare(&g_test_buffer.buff[i], &expected[i]); + } +} + +AVS_UNIT_TEST(DataModelRegisterDiscover, CorePrepareDiscoverObjectInstance) { + SET_UP(); + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &DEF_TEST_OBJ_6_PTR)); + + fluf_uri_path_t uri = FLUF_MAKE_INSTANCE_PATH(OID_6, 0); + dm_discover_ctx_t ctx = { + .callback = discover_clb, + .arg = &g_test_buffer + }; + AVS_UNIT_ASSERT_SUCCESS(dm_discover_resp_prepare(&dm, &uri, NULL, &ctx)); + + fluf_uri_path_t expected[5] = { + [0] = FLUF_MAKE_INSTANCE_PATH(OID_6, 0), + [1] = FLUF_MAKE_RESOURCE_PATH(OID_6, 0, 0), + [2] = FLUF_MAKE_RESOURCE_PATH(OID_6, 0, 1), + [3] = FLUF_MAKE_RESOURCE_PATH(OID_6, 0, 2), + }; + AVS_UNIT_ASSERT_EQUAL(g_test_buffer.buff_len, 4); + for (size_t i = 0; i < AVS_ARRAY_SIZE(expected); ++i) { + fluf_uri_path_t_compare(&g_test_buffer.buff[i], &expected[i]); + } +} + +AVS_UNIT_TEST(DataModelRegisterDiscover, + CorePrepareDiscoverObjectInstanceDepthZero) { + SET_UP(); + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &DEF_TEST_OBJ_6_PTR)); + + fluf_uri_path_t uri = FLUF_MAKE_INSTANCE_PATH(OID_6, 0); + dm_discover_ctx_t ctx = { + .callback = discover_clb, + .arg = &g_test_buffer + }; + uint8_t depth = 0; + AVS_UNIT_ASSERT_SUCCESS(dm_discover_resp_prepare(&dm, &uri, &depth, &ctx)); + + fluf_uri_path_t expected[5] = { + [0] = FLUF_MAKE_INSTANCE_PATH(OID_6, 0), + }; + AVS_UNIT_ASSERT_EQUAL(g_test_buffer.buff_len, 1); + for (size_t i = 0; i < AVS_ARRAY_SIZE(expected); ++i) { + fluf_uri_path_t_compare(&g_test_buffer.buff[i], &expected[i]); + } +} + +AVS_UNIT_TEST(DataModelRegisterDiscover, + CorePrepareDiscoverObjectInstanceDepthOne) { + SET_UP(); + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &DEF_TEST_OBJ_6_PTR)); + + fluf_uri_path_t uri = FLUF_MAKE_INSTANCE_PATH(OID_6, 0); + dm_discover_ctx_t ctx = { + .callback = discover_clb, + .arg = &g_test_buffer + }; + uint8_t depth = 1; + AVS_UNIT_ASSERT_SUCCESS(dm_discover_resp_prepare(&dm, &uri, &depth, &ctx)); + + fluf_uri_path_t expected[5] = { + [0] = FLUF_MAKE_INSTANCE_PATH(OID_6, 0), + [1] = FLUF_MAKE_RESOURCE_PATH(OID_6, 0, 0), + [2] = FLUF_MAKE_RESOURCE_PATH(OID_6, 0, 1), + [3] = FLUF_MAKE_RESOURCE_PATH(OID_6, 0, 2), + }; + AVS_UNIT_ASSERT_EQUAL(g_test_buffer.buff_len, 4); + for (size_t i = 0; i < AVS_ARRAY_SIZE(expected); ++i) { + fluf_uri_path_t_compare(&g_test_buffer.buff[i], &expected[i]); + } +} + +AVS_UNIT_TEST(DataModelRegisterDiscover, + CorePrepareDiscoverObjectInstanceDepthTwo) { + SET_UP(); + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &DEF_TEST_OBJ_6_PTR)); + + fluf_uri_path_t uri = FLUF_MAKE_INSTANCE_PATH(OID_6, 0); + dm_discover_ctx_t ctx = { + .callback = discover_clb, + .arg = &g_test_buffer + }; + uint8_t depth = 2; + AVS_UNIT_ASSERT_SUCCESS(dm_discover_resp_prepare(&dm, &uri, &depth, &ctx)); + + fluf_uri_path_t expected[10] = { + [0] = FLUF_MAKE_INSTANCE_PATH(OID_6, 0), + [1] = FLUF_MAKE_RESOURCE_PATH(OID_6, 0, 0), + [2] = FLUF_MAKE_RESOURCE_PATH(OID_6, 0, 1), + [3] = FLUF_MAKE_RESOURCE_INSTANCE_PATH(OID_6, 0, 1, 0), + [4] = FLUF_MAKE_RESOURCE_INSTANCE_PATH(OID_6, 0, 1, 1), + [5] = FLUF_MAKE_RESOURCE_INSTANCE_PATH(OID_6, 0, 1, 2), + [6] = FLUF_MAKE_RESOURCE_INSTANCE_PATH(OID_6, 0, 1, 3), + [7] = FLUF_MAKE_RESOURCE_INSTANCE_PATH(OID_6, 0, 1, 4), + [8] = FLUF_MAKE_RESOURCE_PATH(OID_6, 0, 2), + }; + AVS_UNIT_ASSERT_EQUAL(g_test_buffer.buff_len, 9); + for (size_t i = 0; i < AVS_ARRAY_SIZE(expected); ++i) { + fluf_uri_path_t_compare(&g_test_buffer.buff[i], &expected[i]); + } +} + +AVS_UNIT_TEST(DataModelRegisterDiscover, + CorePrepareDiscoverSingleInstanceResource) { + SET_UP(); + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &DEF_TEST_OBJ_6_PTR)); + + fluf_uri_path_t uri = FLUF_MAKE_RESOURCE_PATH(OID_6, 0, 0); + dm_discover_ctx_t ctx = { + .callback = discover_clb, + .arg = &g_test_buffer + }; + AVS_UNIT_ASSERT_SUCCESS(dm_discover_resp_prepare(&dm, &uri, NULL, &ctx)); + + fluf_uri_path_t expected[2] = { + [0] = FLUF_MAKE_RESOURCE_PATH(OID_6, 0, 0), + }; + AVS_UNIT_ASSERT_EQUAL(g_test_buffer.buff_len, 1); + for (size_t i = 0; i < AVS_ARRAY_SIZE(expected); ++i) { + fluf_uri_path_t_compare(&g_test_buffer.buff[i], &expected[i]); + } +} + +AVS_UNIT_TEST(DataModelRegisterDiscover, + CorePrepareDiscoverSingleInstanceResourceDepthZero) { + SET_UP(); + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &DEF_TEST_OBJ_6_PTR)); + + fluf_uri_path_t uri = FLUF_MAKE_RESOURCE_PATH(OID_6, 0, 0); + dm_discover_ctx_t ctx = { + .callback = discover_clb, + .arg = &g_test_buffer + }; + uint8_t depth = 0; + AVS_UNIT_ASSERT_SUCCESS(dm_discover_resp_prepare(&dm, &uri, &depth, &ctx)); + + fluf_uri_path_t expected[2] = { + [0] = FLUF_MAKE_RESOURCE_PATH(OID_6, 0, 0), + }; + AVS_UNIT_ASSERT_EQUAL(g_test_buffer.buff_len, 1); + for (size_t i = 0; i < AVS_ARRAY_SIZE(expected); ++i) { + fluf_uri_path_t_compare(&g_test_buffer.buff[i], &expected[i]); + } +} + +AVS_UNIT_TEST(DataModelRegisterDiscover, + CorePrepareDiscoverSingleInstanceResourceDepthOne) { + SET_UP(); + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &DEF_TEST_OBJ_6_PTR)); + + fluf_uri_path_t uri = FLUF_MAKE_RESOURCE_PATH(OID_6, 0, 0); + dm_discover_ctx_t ctx = { + .callback = discover_clb, + .arg = &g_test_buffer + }; + uint8_t depth = 1; + AVS_UNIT_ASSERT_SUCCESS(dm_discover_resp_prepare(&dm, &uri, &depth, &ctx)); + + fluf_uri_path_t expected[2] = { + [0] = FLUF_MAKE_RESOURCE_PATH(OID_6, 0, 0), + }; + AVS_UNIT_ASSERT_EQUAL(g_test_buffer.buff_len, 1); + for (size_t i = 0; i < AVS_ARRAY_SIZE(expected); ++i) { + fluf_uri_path_t_compare(&g_test_buffer.buff[i], &expected[i]); + } +} + +AVS_UNIT_TEST(DataModelRegisterDiscover, + CorePrepareDiscoverMultiInstanceResource) { + SET_UP(); + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &DEF_TEST_OBJ_6_PTR)); + + fluf_uri_path_t uri = FLUF_MAKE_RESOURCE_PATH(OID_6, 0, 1); + dm_discover_ctx_t ctx = { + .callback = discover_clb, + .arg = &g_test_buffer + }; + AVS_UNIT_ASSERT_SUCCESS(dm_discover_resp_prepare(&dm, &uri, NULL, &ctx)); + + fluf_uri_path_t expected[7] = { + [0] = FLUF_MAKE_RESOURCE_PATH(OID_6, 0, 1), + [1] = FLUF_MAKE_RESOURCE_INSTANCE_PATH(OID_6, 0, 1, 0), + [2] = FLUF_MAKE_RESOURCE_INSTANCE_PATH(OID_6, 0, 1, 1), + [3] = FLUF_MAKE_RESOURCE_INSTANCE_PATH(OID_6, 0, 1, 2), + [4] = FLUF_MAKE_RESOURCE_INSTANCE_PATH(OID_6, 0, 1, 3), + [5] = FLUF_MAKE_RESOURCE_INSTANCE_PATH(OID_6, 0, 1, 4), + }; + AVS_UNIT_ASSERT_EQUAL(g_test_buffer.buff_len, 6); + for (size_t i = 0; i < AVS_ARRAY_SIZE(expected); ++i) { + fluf_uri_path_t_compare(&g_test_buffer.buff[i], &expected[i]); + } +} + +AVS_UNIT_TEST(DataModelRegisterDiscover, + CorePrepareDiscoverMultiInstanceResourceDepthZero) { + SET_UP(); + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &DEF_TEST_OBJ_6_PTR)); + + fluf_uri_path_t uri = FLUF_MAKE_RESOURCE_PATH(OID_6, 0, 1); + dm_discover_ctx_t ctx = { + .callback = discover_clb, + .arg = &g_test_buffer + }; + uint8_t depth = 0; + AVS_UNIT_ASSERT_SUCCESS(dm_discover_resp_prepare(&dm, &uri, &depth, &ctx)); + + fluf_uri_path_t expected[2] = { + [0] = FLUF_MAKE_RESOURCE_PATH(OID_6, 0, 1), + }; + AVS_UNIT_ASSERT_EQUAL(g_test_buffer.buff_len, 1); + for (size_t i = 0; i < AVS_ARRAY_SIZE(expected); ++i) { + fluf_uri_path_t_compare(&g_test_buffer.buff[i], &expected[i]); + } +} + +AVS_UNIT_TEST(DataModelRegisterDiscover, + CorePrepareDiscoverMultiInstanceResourceDepthOne) { + SET_UP(); + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &DEF_TEST_OBJ_6_PTR)); + + fluf_uri_path_t uri = FLUF_MAKE_RESOURCE_PATH(OID_6, 0, 1); + dm_discover_ctx_t ctx = { + .callback = discover_clb, + .arg = &g_test_buffer + }; + uint8_t depth = 1; + AVS_UNIT_ASSERT_SUCCESS(dm_discover_resp_prepare(&dm, &uri, &depth, &ctx)); + + fluf_uri_path_t expected[7] = { + [0] = FLUF_MAKE_RESOURCE_PATH(OID_6, 0, 1), + [1] = FLUF_MAKE_RESOURCE_INSTANCE_PATH(OID_6, 0, 1, 0), + [2] = FLUF_MAKE_RESOURCE_INSTANCE_PATH(OID_6, 0, 1, 1), + [3] = FLUF_MAKE_RESOURCE_INSTANCE_PATH(OID_6, 0, 1, 2), + [4] = FLUF_MAKE_RESOURCE_INSTANCE_PATH(OID_6, 0, 1, 3), + [5] = FLUF_MAKE_RESOURCE_INSTANCE_PATH(OID_6, 0, 1, 4), + }; + AVS_UNIT_ASSERT_EQUAL(g_test_buffer.buff_len, 6); + for (size_t i = 0; i < AVS_ARRAY_SIZE(expected); ++i) { + fluf_uri_path_t_compare(&g_test_buffer.buff[i], &expected[i]); + } +} + +AVS_UNIT_TEST(DataModelRegisterDiscover, TryDiscoverNotRegisteredObject) { + SET_UP(); + + fluf_uri_path_t uri = FLUF_MAKE_OBJECT_PATH(OID_6); + dm_discover_ctx_t ctx = { + .callback = discover_clb, + .arg = &g_test_buffer + }; + AVS_UNIT_ASSERT_EQUAL(dm_discover_resp_prepare(&dm, &uri, NULL, &ctx), + FLUF_COAP_CODE_NOT_FOUND); +} + +AVS_UNIT_TEST(DataModelRegisterDiscover, TryDiscoverNotExistingInstance) { + SET_UP(); + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &DEF_TEST_OBJ_6_PTR)); + + fluf_uri_path_t uri = FLUF_MAKE_INSTANCE_PATH(OID_6, 2137); + dm_discover_ctx_t ctx = { + .callback = discover_clb, + .arg = &g_test_buffer + }; + AVS_UNIT_ASSERT_EQUAL(dm_discover_resp_prepare(&dm, &uri, NULL, &ctx), + FLUF_COAP_CODE_NOT_FOUND); +} + +AVS_UNIT_TEST(DataModelRegisterDiscover, TryDiscoverNotExistingResource) { + SET_UP(); + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &DEF_TEST_OBJ_6_PTR)); + + fluf_uri_path_t uri = FLUF_MAKE_RESOURCE_PATH(OID_6, 0, 2137); + dm_discover_ctx_t ctx = { + .callback = discover_clb, + .arg = &g_test_buffer + }; + AVS_UNIT_ASSERT_EQUAL(dm_discover_resp_prepare(&dm, &uri, NULL, &ctx), + FLUF_COAP_CODE_NOT_FOUND); +} diff --git a/tests/anj/dm/dm_execute.c b/tests/anj/dm/dm_execute.c new file mode 100644 index 00000000..f39f1098 --- /dev/null +++ b/tests/anj/dm/dm_execute.c @@ -0,0 +1,164 @@ +/* + * Copyright 2023-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 "../../../src/anj/dm_core.h" + +#define OID_4 4 // test object + +#define IID_0 0 + +#define RID_11_EXECUTABLE 11 +#define RID_12_NON_EXECUTABLE 12 +#define RID_13_NOT_PRESENT 13 + +typedef struct test_object_instance_struct { + fluf_iid_t iid; + bool rid_11_execute_flag; +} test_object_instance_t; + +typedef struct test_object_struct { + const dm_object_def_t *def; + test_object_instance_t instances[1]; +} test_object_t; + +static int resource_execute(dm_t *dm, + const dm_object_def_t *const *obj_ptr, + fluf_iid_t iid, + fluf_iid_t rid, + dm_execute_ctx_t *ctx); +static int list_resources(dm_t *dm, + const dm_object_def_t *const *obj_ptr, + fluf_iid_t iid, + dm_resource_list_ctx_t *ctx); + +static const dm_object_def_t DEF = { + .oid = OID_4, + .handlers.resource_execute = resource_execute, + .handlers.list_instances = dm_list_instances_SINGLE, + .handlers.list_resources = list_resources, +}; + +static test_object_t TEST_OBJECT = { + .def = &DEF, + .instances = + { + { + .iid = IID_0 + } + } +}; + +static int resource_execute(dm_t *dm, + const dm_object_def_t *const *obj_ptr, + fluf_iid_t iid, + fluf_iid_t rid, + dm_execute_ctx_t *ctx) { + (void) dm; + (void) obj_ptr; + (void) iid; + (void) rid; + (void) ctx; + switch (rid) { + case RID_11_EXECUTABLE: + TEST_OBJECT.instances[iid].rid_11_execute_flag = true; + return 0; + } + return -1; +} + +static int list_resources(dm_t *dm, + const dm_object_def_t *const *obj_ptr, + fluf_iid_t iid, + dm_resource_list_ctx_t *ctx) { + (void) dm; + (void) obj_ptr; + (void) iid; + (void) ctx; + dm_emit_res(ctx, RID_11_EXECUTABLE, DM_RES_E, DM_RES_PRESENT); + dm_emit_res(ctx, RID_12_NON_EXECUTABLE, DM_RES_R, DM_RES_PRESENT); + dm_emit_res(ctx, RID_13_NOT_PRESENT, DM_RES_E, DM_RES_ABSENT); + return 0; +} + +#define OBJ_MAX 3 +static dm_t dm; +static dm_installed_object_t objects[OBJ_MAX]; + +#define SET_UP() \ + dm_initialize(&dm, objects, OBJ_MAX); \ + TEST_OBJECT.instances[IID_0].rid_11_execute_flag = false + +AVS_UNIT_TEST(DataModelExecute, ExecuteResource) { + SET_UP(); + fluf_uri_path_t uri = + FLUF_MAKE_RESOURCE_PATH(OID_4, IID_0, RID_11_EXECUTABLE); + + AVS_UNIT_ASSERT_FALSE(TEST_OBJECT.instances[IID_0].rid_11_execute_flag); + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_SUCCESS(dm_execute(&dm, &uri)); + AVS_UNIT_ASSERT_SUCCESS(dm_unregister_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_TRUE(TEST_OBJECT.instances[IID_0].rid_11_execute_flag); +} + +AVS_UNIT_TEST(DataModelExecute, ExecuteTryExecuteNotExecutable) { + SET_UP(); + fluf_uri_path_t uri = + FLUF_MAKE_RESOURCE_PATH(OID_4, IID_0, RID_12_NON_EXECUTABLE); + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_EQUAL(dm_execute(&dm, &uri), + FLUF_COAP_CODE_METHOD_NOT_ALLOWED); + AVS_UNIT_ASSERT_SUCCESS(dm_unregister_object(&dm, &TEST_OBJECT.def)); +} + +AVS_UNIT_TEST(DataModelExecute, ExecuteWithRIID) { + SET_UP(); + fluf_uri_path_t uri = FLUF_MAKE_RESOURCE_INSTANCE_PATH( + OID_4, IID_0, RID_11_EXECUTABLE, 0); + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_EQUAL(dm_execute(&dm, &uri), + FLUF_COAP_CODE_METHOD_NOT_ALLOWED); + AVS_UNIT_ASSERT_SUCCESS(dm_unregister_object(&dm, &TEST_OBJECT.def)); +} + +AVS_UNIT_TEST(DataModelExecute, ExecuteWithNoRID) { + SET_UP(); + fluf_uri_path_t uri = FLUF_MAKE_INSTANCE_PATH(OID_4, IID_0); + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_EQUAL(dm_execute(&dm, &uri), + FLUF_COAP_CODE_METHOD_NOT_ALLOWED); + AVS_UNIT_ASSERT_SUCCESS(dm_unregister_object(&dm, &TEST_OBJECT.def)); +} + +AVS_UNIT_TEST(DataModelExecute, ExecuteWithOnlyOID) { + SET_UP(); + fluf_uri_path_t uri = FLUF_MAKE_OBJECT_PATH(OID_4); + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_EQUAL(dm_execute(&dm, &uri), + FLUF_COAP_CODE_METHOD_NOT_ALLOWED); + AVS_UNIT_ASSERT_SUCCESS(dm_unregister_object(&dm, &TEST_OBJECT.def)); +} + +AVS_UNIT_TEST(DataModelExecute, ExecuteResourceWhichDoesNotExist) { + SET_UP(); + fluf_uri_path_t uri = + FLUF_MAKE_RESOURCE_PATH(OID_4, IID_0, RID_13_NOT_PRESENT); + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_EQUAL(dm_execute(&dm, &uri), FLUF_COAP_CODE_NOT_FOUND); + AVS_UNIT_ASSERT_SUCCESS(dm_unregister_object(&dm, &TEST_OBJECT.def)); +} diff --git a/tests/anj/dm/dm_get_readable_res_count.c b/tests/anj/dm/dm_get_readable_res_count.c new file mode 100644 index 00000000..77a9c685 --- /dev/null +++ b/tests/anj/dm/dm_get_readable_res_count.c @@ -0,0 +1,174 @@ +/* + * Copyright 2023-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 + +#define OID_5 5 // test object 5 +#define OID_6 6 // test object 6 +#define OID_7 7 // test object 7 + +static int list_instances(dm_t *dm, + const dm_object_def_t *const *obj_ptr, + dm_list_ctx_t *ctx) { + (void) dm; + (void) obj_ptr; + dm_emit(ctx, 0); + dm_emit(ctx, 1); + return 0; +} + +static int list_resources(dm_t *dm, + const dm_object_def_t *const *obj_ptr, + fluf_iid_t iid, + dm_resource_list_ctx_t *ctx) { + (void) dm; + (void) obj_ptr; + (void) iid; + dm_emit_res(ctx, 0, DM_RES_R, DM_RES_PRESENT); + dm_emit_res(ctx, 1, DM_RES_R, DM_RES_PRESENT); + return 0; +} + +static int list_resources_OID_7(dm_t *dm, + const dm_object_def_t *const *obj_ptr, + fluf_iid_t iid, + dm_resource_list_ctx_t *ctx) { + (void) dm; + (void) obj_ptr; + (void) iid; + dm_emit_res(ctx, 0, DM_RES_R, DM_RES_PRESENT); + dm_emit_res(ctx, 1, DM_RES_RM, DM_RES_PRESENT); + dm_emit_res(ctx, 2, DM_RES_E, DM_RES_PRESENT); + return 0; +} + +static int list_resource_instances(dm_t *dm, + const dm_object_def_t *const *obj_ptr, + fluf_iid_t iid, + fluf_rid_t rid, + dm_list_ctx_t *ctx) { + (void) dm; + (void) obj_ptr; + (void) iid; + + switch (rid) { + case 1: { + for (fluf_riid_t i = 0; i < 5; ++i) { + dm_emit(ctx, i); + } + return 0; + } + default: + AVS_UNREACHABLE( + "Attempted to list instances in a single-instance resource"); + return FLUF_COAP_CODE_INTERNAL_SERVER_ERROR; + } +} + +static const dm_object_def_t DEF_TEST_OBJ_5 = { + .oid = OID_5, + .handlers.list_resources = list_resources, + .handlers.list_instances = dm_list_instances_SINGLE +}; +static const dm_object_def_t *const DEF_TEST_OBJ_5_PTR = &DEF_TEST_OBJ_5; + +static const dm_object_def_t DEF_TEST_OBJ_6 = { + .oid = OID_6, + .handlers.list_resources = list_resources, + .handlers.list_instances = dm_list_instances_SINGLE +}; +static const dm_object_def_t *const DEF_TEST_OBJ_6_PTR = &DEF_TEST_OBJ_6; + +const dm_object_def_t DEF_TEST_OBJ_7 = { + .oid = OID_7, + .handlers.list_resource_instances = list_resource_instances, + .handlers.list_resources = list_resources_OID_7, + .handlers.list_instances = list_instances +}; +const dm_object_def_t *const DEF_TEST_OBJ_7_PTR = &DEF_TEST_OBJ_7; + +#define OBJ_MAX 4 +static dm_t dm; +static dm_installed_object_t objects[OBJ_MAX]; + +#define SET_UP() dm_initialize(&dm, objects, OBJ_MAX) + +AVS_UNIT_TEST(DataModelCoreGetReadable, CoreGetReadResourceNumberRoot) { + SET_UP(); + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &DEF_TEST_OBJ_5_PTR)); + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &DEF_TEST_OBJ_6_PTR)); + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &DEF_TEST_OBJ_7_PTR)); + + fluf_uri_path_t uri = FLUF_MAKE_ROOT_PATH(); + size_t count = 0; + AVS_UNIT_ASSERT_SUCCESS(dm_get_readable_res_count(&dm, &uri, &count)); + AVS_UNIT_ASSERT_EQUAL(count, 16); +} + +AVS_UNIT_TEST(DataModelCoreGetReadable, CoreGetReadResourceNumberObject) { + SET_UP(); + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &DEF_TEST_OBJ_7_PTR)); + + fluf_uri_path_t uri = FLUF_MAKE_OBJECT_PATH(OID_7); + size_t count = 0; + AVS_UNIT_ASSERT_SUCCESS(dm_get_readable_res_count(&dm, &uri, &count)); + AVS_UNIT_ASSERT_EQUAL(count, 12); +} + +AVS_UNIT_TEST(DataModelCoreGetReadable, + CoreGetReadResourceNumberObjectInstance) { + SET_UP(); + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &DEF_TEST_OBJ_7_PTR)); + + fluf_uri_path_t uri = FLUF_MAKE_INSTANCE_PATH(OID_7, 0); + size_t count = 0; + AVS_UNIT_ASSERT_SUCCESS(dm_get_readable_res_count(&dm, &uri, &count)); + AVS_UNIT_ASSERT_EQUAL(count, 6); +} + +AVS_UNIT_TEST(DataModelCoreGetReadable, + CoreGetReadResourceNumberSingleResource) { + SET_UP(); + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &DEF_TEST_OBJ_7_PTR)); + + fluf_uri_path_t uri = FLUF_MAKE_RESOURCE_PATH(OID_7, 0, 0); + size_t count = 0; + AVS_UNIT_ASSERT_SUCCESS(dm_get_readable_res_count(&dm, &uri, &count)); + AVS_UNIT_ASSERT_EQUAL(count, 1); +} + +AVS_UNIT_TEST(DataModelCoreGetReadable, + CoreGetReadResourceNumberMultipleResource) { + SET_UP(); + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &DEF_TEST_OBJ_7_PTR)); + + fluf_uri_path_t uri = FLUF_MAKE_RESOURCE_PATH(OID_7, 0, 1); + size_t count = 0; + AVS_UNIT_ASSERT_SUCCESS(dm_get_readable_res_count(&dm, &uri, &count)); + AVS_UNIT_ASSERT_EQUAL(count, 5); +} + +AVS_UNIT_TEST(DataModelCoreGetReadable, + CoreGetReadResourceNumberResourceInstance) { + SET_UP(); + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &DEF_TEST_OBJ_7_PTR)); + + fluf_uri_path_t uri = FLUF_MAKE_RESOURCE_INSTANCE_PATH(OID_7, 0, 1, 0); + size_t count = 0; + AVS_UNIT_ASSERT_SUCCESS(dm_get_readable_res_count(&dm, &uri, &count)); + AVS_UNIT_ASSERT_EQUAL(count, 1); +} diff --git a/tests/anj/dm/dm_read.c b/tests/anj/dm/dm_read.c new file mode 100644 index 00000000..62d21272 --- /dev/null +++ b/tests/anj/dm/dm_read.c @@ -0,0 +1,632 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +/** + * @file tests/dm_read.c + * @brief Tests for data model Read API + * @note Note that all strings and values read from data model in this file have + * no special meaning, they are used only for testing purposes. + */ + +#include +#include + +#include + +#include +#include + +#include "../../../src/anj/dm_core.h" + +#define OID_4 4 // test object + +#define IID_0 0 +#define IID_1 1 + +#define RID_0_BYTES 0 +#define RID_1_STRING 1 +#define RID_2_EXT_BYTES 2 +#define RID_3_EXT_STRING 3 +#define RID_4_INT 4 +#define RID_5_DOUBLE 5 +#define RID_6_BOOL 6 +#define RID_7_OBJLNK 7 +#define RID_8_UINT 8 +#define RID_9_TIME 9 +#define RID_10_STRING_M 10 // string (multiple - 4 instances) +#define RID_11_STRING_W 11 // string (write only) + +// number of readable resource instances in a test object +#define TOTAL_READABLE_RES_INST_COUNT 14 +#define OBJECT_INSTANCES 2 + +typedef struct test_object_instance_struct { + fluf_iid_t iid; +} test_object_instance_t; + +typedef struct test_object_struct { + const dm_object_def_t *def; + test_object_instance_t instances[OBJECT_INSTANCES]; +} test_object_t; + +static char *RESOURCE_INSTANCES_STRINGS[] = { "coap", "coaps", "tcp", "tls" }; +static uint8_t BYTES[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0xFE, 0xFF }; +static int read_external_data(void *buffer, + size_t bytes_to_copy, + size_t offset, + void *user_args) { + (void) user_args; + memcpy(buffer, BYTES + offset, bytes_to_copy); + return 0; +} +static char REALLY_LONG_STRING[] = "really_long_string"; +static int read_external_string(void *buffer, + size_t bytes_to_copy, + size_t offset, + void *user_args) { + (void) user_args; + memcpy(buffer, REALLY_LONG_STRING + offset, bytes_to_copy); + return 0; +} +static int resource_read(dm_t *dm, + const dm_object_def_t *const *obj_ptr, + fluf_iid_t iid, + fluf_rid_t rid, + fluf_riid_t riid, + dm_output_ctx_t *ctx) { + (void) dm; + (void) obj_ptr; + (void) iid; + switch (rid) { + case RID_0_BYTES: { + return dm_ret_bytes(ctx, BYTES, sizeof(BYTES)); + } + case RID_1_STRING: { + return dm_ret_string(ctx, "read_resource_0"); + } + case RID_2_EXT_BYTES: { + return dm_ret_external_bytes( + ctx, read_external_data, NULL, sizeof(BYTES)); + } + case RID_3_EXT_STRING: { + return dm_ret_external_string( + ctx, read_external_string, NULL, sizeof(REALLY_LONG_STRING)); + } + case RID_4_INT: { + return dm_ret_i64(ctx, (int64_t) INT32_MAX + 1); + } + case RID_5_DOUBLE: { + return dm_ret_double(ctx, 3.14); + } + case RID_6_BOOL: { + return dm_ret_bool(ctx, true); + } + case RID_7_OBJLNK: { + return dm_ret_objlnk(ctx, OID_4, IID_0); + } + case RID_8_UINT: { + return dm_ret_u64(ctx, UINT64_MAX); + } + case RID_9_TIME: { + return dm_ret_time(ctx, 1112470620000); + } + case RID_10_STRING_M: { + return dm_ret_string(ctx, RESOURCE_INSTANCES_STRINGS[riid]); + } + + default: { return FLUF_COAP_CODE_METHOD_NOT_ALLOWED; } + } +} + +static int list_resource_instances(dm_t *dm, + const dm_object_def_t *const *obj_ptr, + fluf_iid_t iid, + fluf_rid_t rid, + dm_list_ctx_t *ctx) { + (void) dm; + (void) obj_ptr; + (void) iid; + + switch (rid) { + case RID_10_STRING_M: { + for (fluf_riid_t i = 0; i < AVS_ARRAY_SIZE(RESOURCE_INSTANCES_STRINGS); + ++i) { + dm_emit(ctx, i); + } + return 0; + } + default: + AVS_UNREACHABLE( + "Attempted to list instances in a single-instance resource"); + return FLUF_COAP_CODE_INTERNAL_SERVER_ERROR; + } +} + +static int list_resources(dm_t *dm, + const dm_object_def_t *const *obj_ptr, + fluf_iid_t iid, + dm_resource_list_ctx_t *ctx) { + (void) dm; + (void) obj_ptr; + (void) iid; + dm_emit_res(ctx, RID_0_BYTES, DM_RES_R, DM_RES_PRESENT); + dm_emit_res(ctx, RID_1_STRING, DM_RES_R, DM_RES_PRESENT); + dm_emit_res(ctx, RID_2_EXT_BYTES, DM_RES_R, DM_RES_PRESENT); + dm_emit_res(ctx, RID_3_EXT_STRING, DM_RES_R, DM_RES_PRESENT); + dm_emit_res(ctx, RID_4_INT, DM_RES_R, DM_RES_PRESENT); + dm_emit_res(ctx, RID_5_DOUBLE, DM_RES_R, DM_RES_PRESENT); + dm_emit_res(ctx, RID_6_BOOL, DM_RES_R, DM_RES_PRESENT); + dm_emit_res(ctx, RID_7_OBJLNK, DM_RES_R, DM_RES_PRESENT); + dm_emit_res(ctx, RID_8_UINT, DM_RES_R, DM_RES_PRESENT); + dm_emit_res(ctx, RID_9_TIME, DM_RES_R, DM_RES_PRESENT); + dm_emit_res(ctx, RID_10_STRING_M, DM_RES_RM, DM_RES_PRESENT); + dm_emit_res(ctx, RID_11_STRING_W, DM_RES_W, DM_RES_PRESENT); + + return 0; +} + +static inline test_object_t *get_obj(const dm_object_def_t *const *obj_ptr) { + assert(obj_ptr); + return AVS_CONTAINER_OF(obj_ptr, test_object_t, def); +} + +static int list_instances(dm_t *dm, + const dm_object_def_t *const *obj_ptr, + dm_list_ctx_t *ctx) { + (void) dm; + + test_object_t *test_object = get_obj(obj_ptr); + + for (uint16_t i = 0; i < (uint16_t) AVS_ARRAY_SIZE(test_object->instances); + ++i) { + dm_emit(ctx, i); + } + return 0; +} + +static const dm_object_def_t DEF = { + .oid = OID_4, + .handlers.resource_read = resource_read, + .handlers.list_resource_instances = list_resource_instances, + .handlers.list_resources = list_resources, + .handlers.list_instances = list_instances +}; + +static const test_object_t TEST_OBJECT = { + .def = &DEF, + .instances = + { + { + .iid = IID_0 + }, + { + .iid = IID_1 + } + } +}; + +// Global buffer used to store entries retrieved from data model. +static fluf_io_out_entry_t + USER_BUFFER[OBJECT_INSTANCES * TOTAL_READABLE_RES_INST_COUNT]; +static struct user_buffer_struct { + fluf_io_out_entry_t *buffer; + size_t buffer_size; + size_t count; +} USER_BUFFER_STRUCT; + +static dm_output_ctx_t OUT_CTX; + +static int callback_fnc(void *arg, fluf_io_out_entry_t *out_entry) { + struct user_buffer_struct *user_buffer_struct = + (struct user_buffer_struct *) arg; + if (user_buffer_struct->count >= user_buffer_struct->buffer_size) { + printf("Buffer overflow\n"); + return -1; + } + fluf_io_out_entry_t *user_buffer = + &user_buffer_struct->buffer[user_buffer_struct->count++]; + + if (out_entry->type == FLUF_DATA_TYPE_STRING + || out_entry->type == FLUF_DATA_TYPE_INT + || out_entry->type == FLUF_DATA_TYPE_BYTES + || out_entry->type == FLUF_DATA_TYPE_DOUBLE + || out_entry->type == FLUF_DATA_TYPE_BOOL + || out_entry->type == FLUF_DATA_TYPE_OBJLNK + || out_entry->type == FLUF_DATA_TYPE_UINT + || out_entry->type == FLUF_DATA_TYPE_TIME + || out_entry->type == FLUF_DATA_TYPE_EXTERNAL_BYTES + || out_entry->type == FLUF_DATA_TYPE_EXTERNAL_STRING) { + memcpy(user_buffer, out_entry, sizeof(fluf_io_out_entry_t)); + return 0; + } else { + printf("Unknown data type\n"); + return -1; + } +} + +#define OBJ_MAX 3 +static dm_t dm; +static dm_installed_object_t objects[OBJ_MAX]; + +#define SET_UP() \ + dm_initialize(&dm, objects, OBJ_MAX); \ + USER_BUFFER_STRUCT = (struct user_buffer_struct) { \ + .buffer = USER_BUFFER, \ + .buffer_size = AVS_ARRAY_SIZE(USER_BUFFER), \ + .count = 0 \ + }; \ + memset(USER_BUFFER, 0x00, sizeof(USER_BUFFER)); \ + OUT_CTX.callback = callback_fnc; \ + OUT_CTX.arg = &USER_BUFFER_STRUCT + +AVS_UNIT_TEST(DataModelRead, ReadResourceInstance) { + SET_UP(); + fluf_riid_t riid = 3; + fluf_uri_path_t uri = FLUF_MAKE_RESOURCE_INSTANCE_PATH( + OID_4, IID_0, RID_10_STRING_M, riid); + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_SUCCESS(dm_read(&dm, &uri, &OUT_CTX)); + AVS_UNIT_ASSERT_SUCCESS(dm_unregister_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER[0].type, FLUF_DATA_TYPE_STRING); + AVS_UNIT_ASSERT_EQUAL_STRING( + (const char *) USER_BUFFER[0].value.bytes_or_string.data, + RESOURCE_INSTANCES_STRINGS[riid]); + AVS_UNIT_ASSERT_TRUE(fluf_uri_path_equal(&uri, &USER_BUFFER[0].path)); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER_STRUCT.count, 1); +} + +AVS_UNIT_TEST(DataModelRead, ReadSingleInstanceResource) { + SET_UP(); + fluf_uri_path_t uri = FLUF_MAKE_RESOURCE_PATH(OID_4, IID_0, RID_1_STRING); + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_SUCCESS(dm_read(&dm, &uri, &OUT_CTX)); + AVS_UNIT_ASSERT_SUCCESS(dm_unregister_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER[0].type, FLUF_DATA_TYPE_STRING); + AVS_UNIT_ASSERT_EQUAL_STRING( + (const char *) USER_BUFFER[0].value.bytes_or_string.data, + "read_resource_0"); + AVS_UNIT_ASSERT_TRUE(fluf_uri_path_equal(&uri, &USER_BUFFER[0].path)); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER_STRUCT.count, 1); +} + +AVS_UNIT_TEST(DataModelRead, ReadMultiInstanceResource) { + SET_UP(); + fluf_uri_path_t uri = + FLUF_MAKE_RESOURCE_PATH(OID_4, IID_0, RID_10_STRING_M); + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_SUCCESS(dm_read(&dm, &uri, &OUT_CTX)); + AVS_UNIT_ASSERT_SUCCESS(dm_unregister_object(&dm, &TEST_OBJECT.def)); + for (uint16_t i = 0; i < AVS_ARRAY_SIZE(RESOURCE_INSTANCES_STRINGS); ++i) { + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER[i].type, FLUF_DATA_TYPE_STRING); + AVS_UNIT_ASSERT_EQUAL_STRING( + (const char *) USER_BUFFER[i].value.bytes_or_string.data, + RESOURCE_INSTANCES_STRINGS[i]); + uri.ids[FLUF_ID_RIID] = i; + AVS_UNIT_ASSERT_SUCCESS( + fluf_uri_path_equal(&uri, &USER_BUFFER[i].path)); + } + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER_STRUCT.count, + AVS_ARRAY_SIZE(RESOURCE_INSTANCES_STRINGS)); +} + +static size_t test_OID_4(fluf_iid_t iid, size_t iterator) { + // /OID_4/iid/RID_0_BYTES + fluf_uri_path_t expected_uri = + FLUF_MAKE_RESOURCE_PATH(OID_4, iid, RID_0_BYTES); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER[iterator].type, FLUF_DATA_TYPE_BYTES); + AVS_UNIT_ASSERT_NOT_NULL(USER_BUFFER[iterator].value.bytes_or_string.data); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER[iterator].value.bytes_or_string.offset, + 0); + AVS_UNIT_ASSERT_EQUAL( + USER_BUFFER[iterator].value.bytes_or_string.chunk_length, + sizeof(BYTES)); + AVS_UNIT_ASSERT_EQUAL( + USER_BUFFER[iterator].value.bytes_or_string.full_length_hint, + sizeof(BYTES)); + AVS_UNIT_ASSERT_SUCCESS( + memcmp(USER_BUFFER[iterator].value.bytes_or_string.data, + BYTES, + USER_BUFFER[iterator].value.bytes_or_string.chunk_length)); + AVS_UNIT_ASSERT_TRUE( + fluf_uri_path_equal(&expected_uri, &USER_BUFFER[iterator++].path)); + // /OID_4/iid/RID_1_STRING + expected_uri = FLUF_MAKE_RESOURCE_PATH(OID_4, iid, RID_1_STRING); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER[iterator].type, FLUF_DATA_TYPE_STRING); + AVS_UNIT_ASSERT_EQUAL_STRING( + (const char *) USER_BUFFER[iterator].value.bytes_or_string.data, + "read_resource_0"); + AVS_UNIT_ASSERT_TRUE( + fluf_uri_path_equal(&expected_uri, &USER_BUFFER[iterator++].path)); + // /OID_4/iid/RID_2_EXT_BYTES + uint8_t read_buff[sizeof(REALLY_LONG_STRING)]; + memset(read_buff, 0xFF, sizeof(read_buff)); + const size_t chunk_size = 2; + expected_uri = FLUF_MAKE_RESOURCE_PATH(OID_4, iid, RID_2_EXT_BYTES); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER[iterator].type, + FLUF_DATA_TYPE_EXTERNAL_BYTES); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER[iterator].value.external_data.length, + sizeof(BYTES)); + AVS_UNIT_ASSERT_NOT_NULL( + USER_BUFFER[iterator].value.external_data.get_external_data); + size_t bytes_to_copy = USER_BUFFER[iterator].value.external_data.length; + size_t remaining_bytes = USER_BUFFER[iterator].value.external_data.length; + while (remaining_bytes >= chunk_size) { + AVS_UNIT_ASSERT_SUCCESS( + USER_BUFFER[iterator].value.external_data.get_external_data( + read_buff + bytes_to_copy - remaining_bytes, + chunk_size, + bytes_to_copy - remaining_bytes, + NULL)); + remaining_bytes -= chunk_size; + } + if (remaining_bytes) { + AVS_UNIT_ASSERT_SUCCESS( + USER_BUFFER[iterator].value.external_data.get_external_data( + read_buff + bytes_to_copy - remaining_bytes, + remaining_bytes, + bytes_to_copy - remaining_bytes, + NULL)); + } + AVS_UNIT_ASSERT_SUCCESS(memcmp(read_buff, BYTES, sizeof(BYTES))); + AVS_UNIT_ASSERT_TRUE( + fluf_uri_path_equal(&expected_uri, &USER_BUFFER[iterator++].path)); + // /OID_4/iid/RID_3_EXT_STRING + memset(read_buff, 0xFF, sizeof(read_buff)); + expected_uri = FLUF_MAKE_RESOURCE_PATH(OID_4, iid, RID_3_EXT_STRING); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER[iterator].type, + FLUF_DATA_TYPE_EXTERNAL_STRING); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER[iterator].value.external_data.length, + sizeof(REALLY_LONG_STRING)); + AVS_UNIT_ASSERT_NOT_NULL( + USER_BUFFER[iterator].value.external_data.get_external_data); + bytes_to_copy = USER_BUFFER[iterator].value.external_data.length; + remaining_bytes = USER_BUFFER[iterator].value.external_data.length; + while (remaining_bytes >= chunk_size) { + AVS_UNIT_ASSERT_SUCCESS( + USER_BUFFER[iterator].value.external_data.get_external_data( + read_buff + bytes_to_copy - remaining_bytes, + chunk_size, + bytes_to_copy - remaining_bytes, + NULL)); + remaining_bytes -= chunk_size; + } + if (remaining_bytes) { + AVS_UNIT_ASSERT_SUCCESS( + USER_BUFFER[iterator].value.external_data.get_external_data( + read_buff + bytes_to_copy - remaining_bytes, + remaining_bytes, + bytes_to_copy - remaining_bytes, + NULL)); + } + AVS_UNIT_ASSERT_SUCCESS( + memcmp(read_buff, REALLY_LONG_STRING, sizeof(REALLY_LONG_STRING))); + AVS_UNIT_ASSERT_TRUE( + fluf_uri_path_equal(&expected_uri, &USER_BUFFER[iterator++].path)); + // /OID_4/iid/RID_4_INT + expected_uri = FLUF_MAKE_RESOURCE_PATH(OID_4, iid, RID_4_INT); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER[iterator].type, FLUF_DATA_TYPE_INT); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER[iterator].value.int_value, + (int64_t) INT32_MAX + 1); + AVS_UNIT_ASSERT_TRUE( + fluf_uri_path_equal(&expected_uri, &USER_BUFFER[iterator++].path)); + // /OID_4/iid/RID_5_DOUBLE + expected_uri = FLUF_MAKE_RESOURCE_PATH(OID_4, iid, RID_5_DOUBLE); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER[iterator].type, FLUF_DATA_TYPE_DOUBLE); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER[iterator].value.double_value, 3.14); + AVS_UNIT_ASSERT_TRUE( + fluf_uri_path_equal(&expected_uri, &USER_BUFFER[iterator++].path)); + // /OID_4/iid/RID_6_BOOL + expected_uri = FLUF_MAKE_RESOURCE_PATH(OID_4, iid, RID_6_BOOL); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER[iterator].type, FLUF_DATA_TYPE_BOOL); + AVS_UNIT_ASSERT_TRUE(USER_BUFFER[iterator].value.bool_value); + AVS_UNIT_ASSERT_TRUE( + fluf_uri_path_equal(&expected_uri, &USER_BUFFER[iterator++].path)); + // /OID_4/iid/RID_7_OBJLNK + expected_uri = FLUF_MAKE_RESOURCE_PATH(OID_4, iid, RID_7_OBJLNK); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER[iterator].type, FLUF_DATA_TYPE_OBJLNK); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER[iterator].value.objlnk.oid, OID_4); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER[iterator].value.objlnk.iid, IID_0); + AVS_UNIT_ASSERT_TRUE( + fluf_uri_path_equal(&expected_uri, &USER_BUFFER[iterator++].path)); + // /OID_4/iid/RID_8_UINT + expected_uri = FLUF_MAKE_RESOURCE_PATH(OID_4, iid, RID_8_UINT); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER[iterator].type, FLUF_DATA_TYPE_UINT); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER[iterator].value.int_value, UINT64_MAX); + AVS_UNIT_ASSERT_TRUE( + fluf_uri_path_equal(&expected_uri, &USER_BUFFER[iterator++].path)); + // /OID_4/iid/RID_9_TIME + expected_uri = FLUF_MAKE_RESOURCE_PATH(OID_4, iid, RID_9_TIME); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER[iterator].type, FLUF_DATA_TYPE_TIME); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER[iterator].value.time_value, + 1112470620000); + AVS_UNIT_ASSERT_TRUE( + fluf_uri_path_equal(&expected_uri, &USER_BUFFER[iterator++].path)); + // /OID_4/iid/RID_10_STRING_M + fluf_uri_path_t expected_path_2 = + FLUF_MAKE_RESOURCE_PATH(OID_4, iid, RID_10_STRING_M); + for (uint16_t i = 0; i < AVS_ARRAY_SIZE(RESOURCE_INSTANCES_STRINGS); ++i) { + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER[iterator].type, + FLUF_DATA_TYPE_STRING); + AVS_UNIT_ASSERT_EQUAL_STRING( + (const char *) USER_BUFFER[iterator].value.bytes_or_string.data, + RESOURCE_INSTANCES_STRINGS[i]); + expected_path_2.ids[3] = i; + for (uint8_t idx = 0; idx < expected_path_2.uri_len; idx++) { + AVS_UNIT_ASSERT_EQUAL(expected_path_2.ids[idx], + USER_BUFFER[iterator].path.ids[idx]); + } + iterator++; + } + return iterator; +} + +AVS_UNIT_TEST(DataModelRead, ReadObjectInstance) { + SET_UP(); + size_t iterator = 0; + fluf_uri_path_t uri = FLUF_MAKE_INSTANCE_PATH(OID_4, IID_0); + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_SUCCESS(dm_read(&dm, &uri, &OUT_CTX)); + AVS_UNIT_ASSERT_SUCCESS(dm_unregister_object(&dm, &TEST_OBJECT.def)); + iterator = test_OID_4(IID_0, iterator); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER_STRUCT.count, iterator); +} + +AVS_UNIT_TEST(DataModelRead, ReadObject) { + SET_UP(); + size_t iterator = 0; + fluf_uri_path_t uri = FLUF_MAKE_OBJECT_PATH(OID_4); + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_SUCCESS(dm_read(&dm, &uri, &OUT_CTX)); + AVS_UNIT_ASSERT_SUCCESS(dm_unregister_object(&dm, &TEST_OBJECT.def)); + iterator = test_OID_4(IID_0, iterator); + iterator = test_OID_4(IID_1, iterator); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER_STRUCT.count, iterator); +} + +AVS_UNIT_TEST(DataModelRead, HandlerNotSet) { + // Specific setup for this test + USER_BUFFER_STRUCT.count = 0; + const dm_object_def_t SPECIFIC_DEF = { + .oid = OID_4, + .handlers.resource_read = NULL, + .handlers.list_resources = list_resources, + .handlers.list_resource_instances = list_resource_instances + }; + const dm_object_def_t *SPECIFIC_DEF_PTR = &SPECIFIC_DEF; + fluf_uri_path_t uri = + FLUF_MAKE_RESOURCE_INSTANCE_PATH(OID_4, IID_0, RID_10_STRING_M, 1); + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &SPECIFIC_DEF_PTR)); + AVS_UNIT_ASSERT_EQUAL(dm_read(&dm, &uri, &OUT_CTX), + FLUF_COAP_CODE_METHOD_NOT_ALLOWED); + AVS_UNIT_ASSERT_SUCCESS(dm_unregister_object(&dm, &SPECIFIC_DEF_PTR)); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER_STRUCT.count, 0); +} + +AVS_UNIT_TEST(DataModelRead, ReadNotRegistered) { + SET_UP(); + fluf_uri_path_t uri = FLUF_MAKE_OBJECT_PATH(OID_4); + AVS_UNIT_ASSERT_EQUAL(dm_read(&dm, &uri, &OUT_CTX), + FLUF_COAP_CODE_NOT_FOUND); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER_STRUCT.count, 0); +} + +AVS_UNIT_TEST(DataModelRead, ReadNotPresentObject) { + SET_UP(); + fluf_riid_t riid = 4; + fluf_uri_path_t uri = FLUF_MAKE_RESOURCE_INSTANCE_PATH( + OID_4, IID_0, RID_10_STRING_M, riid); + // deliberately missing dm_register here + AVS_UNIT_ASSERT_EQUAL(dm_read(&dm, &uri, &OUT_CTX), + FLUF_COAP_CODE_NOT_FOUND); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER_STRUCT.count, 0); +} + +AVS_UNIT_TEST(DataModelRead, ReadNotPresentObjectInstance) { + SET_UP(); + fluf_uri_path_t uri = FLUF_MAKE_INSTANCE_PATH(OID_4, 2); + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_EQUAL(dm_read(&dm, &uri, &OUT_CTX), + FLUF_COAP_CODE_NOT_FOUND); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER_STRUCT.count, 0); +} + +AVS_UNIT_TEST(DataModelRead, ReadNotPresentResource) { + SET_UP(); + fluf_uri_path_t uri = FLUF_MAKE_RESOURCE_PATH(OID_4, IID_0, 20); + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_EQUAL(dm_read(&dm, &uri, &OUT_CTX), + FLUF_COAP_CODE_NOT_FOUND); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER_STRUCT.count, 0); +} + +AVS_UNIT_TEST(DataModelRead, ReadNotPresentResourceInstance) { + SET_UP(); + fluf_uri_path_t uri = + FLUF_MAKE_RESOURCE_INSTANCE_PATH(OID_4, IID_0, RID_10_STRING_M, 20); + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_EQUAL(dm_read(&dm, &uri, &OUT_CTX), + FLUF_COAP_CODE_METHOD_NOT_ALLOWED); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER_STRUCT.count, 0); +} + +static int error_callback(void *arg, fluf_io_out_entry_t *entry) { + (void) arg; + (void) entry; + return -1; +} + +AVS_UNIT_TEST(DataModelRead, ReadCheckCTXCallbackErrorSingleInstanceResource) { + SET_UP(); + fluf_riid_t riid = 3; + fluf_uri_path_t uri = FLUF_MAKE_RESOURCE_INSTANCE_PATH( + OID_4, IID_0, RID_10_STRING_M, riid); + OUT_CTX.callback = error_callback; + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_EQUAL(dm_read(&dm, &uri, &OUT_CTX), -1); + AVS_UNIT_ASSERT_SUCCESS(dm_unregister_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER_STRUCT.count, 0); +} + +AVS_UNIT_TEST(DataModelRead, ReadCheckCTXCallbackErrorMultiInstanceResource) { + SET_UP(); + fluf_uri_path_t uri = + FLUF_MAKE_RESOURCE_PATH(OID_4, IID_0, RID_10_STRING_M); + OUT_CTX.callback = error_callback; + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_EQUAL(dm_read(&dm, &uri, &OUT_CTX), -1); + AVS_UNIT_ASSERT_SUCCESS(dm_unregister_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER_STRUCT.count, 0); +} + +AVS_UNIT_TEST(DataModelRead, ReadCheckCTXCallbackErrorObjectInstance) { + SET_UP(); + fluf_uri_path_t uri = FLUF_MAKE_INSTANCE_PATH(OID_4, IID_0); + OUT_CTX.callback = error_callback; + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_EQUAL(dm_read(&dm, &uri, &OUT_CTX), -1); + AVS_UNIT_ASSERT_SUCCESS(dm_unregister_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER_STRUCT.count, 0); +} + +AVS_UNIT_TEST(DataModelRead, ReadCheckCTXCallbackErrorObject) { + SET_UP(); + fluf_uri_path_t uri = FLUF_MAKE_OBJECT_PATH(OID_4); + OUT_CTX.callback = error_callback; + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_EQUAL(dm_read(&dm, &uri, &OUT_CTX), -1); + AVS_UNIT_ASSERT_SUCCESS(dm_unregister_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER_STRUCT.count, 0); +} + +AVS_UNIT_TEST(DataModelRead, ReadCheckCTXCallbackErrorRoot) { + SET_UP(); + fluf_uri_path_t uri = FLUF_MAKE_ROOT_PATH(); + OUT_CTX.callback = error_callback; + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_EQUAL(dm_read(&dm, &uri, &OUT_CTX), -1); + AVS_UNIT_ASSERT_SUCCESS(dm_unregister_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER_STRUCT.count, 0); +} + +AVS_UNIT_TEST(DataModelRead, ReadOnlyWritable) { + SET_UP(); + fluf_uri_path_t uri = + FLUF_MAKE_RESOURCE_PATH(OID_4, IID_0, RID_11_STRING_W); + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_EQUAL(dm_read(&dm, &uri, &OUT_CTX), + FLUF_COAP_CODE_METHOD_NOT_ALLOWED); + AVS_UNIT_ASSERT_SUCCESS(dm_unregister_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_EQUAL(USER_BUFFER_STRUCT.count, 0); +} diff --git a/tests/anj/dm/dm_read_root.c b/tests/anj/dm/dm_read_root.c new file mode 100644 index 00000000..9b2e2cbb --- /dev/null +++ b/tests/anj/dm/dm_read_root.c @@ -0,0 +1,141 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +/** + * @file tests/dm_read_root.c + * @brief Tests for data model Read API + * @note Note that all strings and values read from data model in this file have + * no special meaning, they are used only for testing purposes. + */ + +#include +#include + +#include + +#include +#include + +#include "../../../src/anj/dm_core.h" + +#define OID_4 4 // test object 1 +#define OID_5 5 // test object 2 + +#define RES_INST 4 + +static int resource_read(dm_t *dm, + const dm_object_def_t *const *obj_ptr, + fluf_iid_t iid, + fluf_rid_t rid, + fluf_riid_t riid, + dm_output_ctx_t *ctx) { + (void) dm; + (void) obj_ptr; + (void) iid; + (void) riid; + switch (rid) { + case 0: { + return dm_ret_string(ctx, "read_resource_0"); + } + case 1: { + return dm_ret_i64(ctx, (int64_t) INT32_MAX + 1); + } + + default: { return FLUF_COAP_CODE_METHOD_NOT_ALLOWED; } + } +} + +static int list_resources(dm_t *dm, + const dm_object_def_t *const *obj_ptr, + fluf_iid_t iid, + dm_resource_list_ctx_t *ctx) { + (void) dm; + (void) obj_ptr; + (void) iid; + dm_emit_res(ctx, 0, DM_RES_R, DM_RES_PRESENT); + dm_emit_res(ctx, 1, DM_RES_R, DM_RES_PRESENT); + return 0; +} + +static const dm_object_def_t DEF_TEST_OBJ_1 = { + .oid = OID_4, + .handlers.resource_read = resource_read, + .handlers.list_resources = list_resources, + .handlers.list_instances = dm_list_instances_SINGLE +}; +static const dm_object_def_t *const DEF_TEST_OBJ_1_PTR = &DEF_TEST_OBJ_1; + +static const dm_object_def_t DEF_TEST_OBJ_2 = { + .oid = OID_5, + .handlers.resource_read = resource_read, + .handlers.list_resources = list_resources, + .handlers.list_instances = dm_list_instances_SINGLE +}; +static const dm_object_def_t *const DEF_TEST_OBJ_2_PTR = &DEF_TEST_OBJ_2; + +// Global buffer used to store entries retrieved from data model. +static fluf_io_out_entry_t USER_BUFFER[RES_INST]; +static struct user_buffer_struct { + fluf_io_out_entry_t *buffer; + size_t buffer_size; + size_t count; +} USER_BUFFER_STRUCT; + +static int callback_fnc(void *arg, fluf_io_out_entry_t *out_entry) { + struct user_buffer_struct *user_buffer_struct = + (struct user_buffer_struct *) arg; + if (user_buffer_struct->count >= user_buffer_struct->buffer_size) { + printf("Buffer overflow\n"); + return -1; + } + fluf_io_out_entry_t *user_buffer = + &user_buffer_struct->buffer[user_buffer_struct->count++]; + if (out_entry->type == FLUF_DATA_TYPE_STRING + || out_entry->type == FLUF_DATA_TYPE_INT) { + memcpy(user_buffer, out_entry, sizeof(*user_buffer)); + return 0; + } else { + printf("Unknown data type\n"); + return -1; + } +} + +static dm_output_ctx_t OUT_CTX; + +#define OBJ_MAX 3 +static dm_t dm; +static dm_installed_object_t objects[OBJ_MAX]; + +#define SET_UP() \ + dm_initialize(&dm, objects, OBJ_MAX); \ + USER_BUFFER_STRUCT = (struct user_buffer_struct) { \ + .buffer = USER_BUFFER, \ + .buffer_size = AVS_ARRAY_SIZE(USER_BUFFER), \ + .count = 0 \ + }; \ + OUT_CTX.callback = callback_fnc; \ + OUT_CTX.arg = &USER_BUFFER_STRUCT; \ + memset(USER_BUFFER, 0x00, sizeof(USER_BUFFER)) + +AVS_UNIT_TEST(DataModelReadRoot, ReadRootPath) { + SET_UP(); + fluf_uri_path_t uri = FLUF_MAKE_ROOT_PATH(); + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &DEF_TEST_OBJ_1_PTR)); + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &DEF_TEST_OBJ_2_PTR)); + AVS_UNIT_ASSERT_SUCCESS(dm_read(&dm, &uri, &OUT_CTX)); + AVS_UNIT_ASSERT_SUCCESS(dm_unregister_object(&dm, &DEF_TEST_OBJ_1_PTR)); + AVS_UNIT_ASSERT_SUCCESS(dm_unregister_object(&dm, &DEF_TEST_OBJ_2_PTR)); +} + +AVS_UNIT_TEST(DataModelReadRoot, ReadNotRegisteredRoot) { + SET_UP(); + fluf_uri_path_t uri = FLUF_MAKE_ROOT_PATH(); + AVS_UNIT_ASSERT_EQUAL(dm_read(&dm, &uri, &OUT_CTX), + FLUF_COAP_CODE_NOT_FOUND); +} diff --git a/tests/anj/dm/dm_write.c b/tests/anj/dm/dm_write.c new file mode 100644 index 00000000..9e25768a --- /dev/null +++ b/tests/anj/dm/dm_write.c @@ -0,0 +1,583 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +/** + * @file tests/dm_write.c + * @brief Tests for data model Write API + * @note Note that all strings and values written to data model in this file + * have no special meaning, they are used only for testing purposes. + */ + +#include +#include + +#include + +#include +#include + +#include "../../../src/anj/dm_core.h" + +#define OID_4 4 // test object + +#define IID_0 0 + +#define RID_0_BYTES 0 +#define RID_1_STRING 1 +#define RID_2_EXT_BYTES 2 +#define RID_3_EXT_STRING 3 +#define RID_4_INT 4 +#define RID_5_DOUBLE 5 +#define RID_6_BOOL 6 +#define RID_7_OBJLNK 7 +#define RID_8_UINT 8 +#define RID_9_TIME 9 +#define RID_10_STRING_M 10 // string (multiple - 4 instances) +#define RID_11_STRING_W 11 // string (write only) +#define RID_12_ABSENT 12 + +// number of writable resource instances in a test object +#define TOTAL_WRITABLE_RES_INST_COUNT 14 + +typedef struct test_object_instance_struct { + fluf_iid_t iid; +} test_object_instance_t; + +typedef struct test_object_struct { + const dm_object_def_t *def; +} test_object_t; + +#define MAX_RID_BYTES_SIZE 10 +static uint8_t RESOURCE_0[MAX_RID_BYTES_SIZE]; +#define MAX_RID_STR_SIZE 10 +static char RESOURCE_1[MAX_RID_STR_SIZE]; +static uint8_t RESOURCE_2[MAX_RID_BYTES_SIZE]; +static char RESOURCE_3[MAX_RID_STR_SIZE]; +static int64_t RESOURCE_4; +static double RESOURCE_5; +static bool RESOURCE_6; +static struct { + fluf_oid_t oid; + fluf_iid_t iid; +} RESOURCE_7; +static uint64_t RESOURCE_8; +static uint64_t RESOURCE_9; +static char RESOURCE_10[4][MAX_RID_STR_SIZE] = {}; +static void clear_resources(void) { + memset(RESOURCE_0, 0x00, sizeof(RESOURCE_0)); + memset(RESOURCE_1, 0x00, sizeof(RESOURCE_1)); + memset(RESOURCE_2, 0x00, sizeof(RESOURCE_2)); + memset(RESOURCE_3, 0x00, sizeof(RESOURCE_3)); + RESOURCE_4 = 0; + RESOURCE_5 = 0; + RESOURCE_6 = false; + memset(&RESOURCE_7, 0x00, sizeof(RESOURCE_7)); + RESOURCE_8 = 0; + RESOURCE_9 = 0; + memset(RESOURCE_10, 0x00, sizeof(RESOURCE_10)); +} + +static int resource_write(dm_t *dm, + const dm_object_def_t *const *obj_ptr, + fluf_iid_t iid, + fluf_rid_t rid, + fluf_riid_t riid, + dm_input_ctx_t *ctx) { + (void) dm; + (void) obj_ptr; + (void) iid; + (void) riid; + switch (rid) { + case RID_0_BYTES: { + // The idea of this is to enforce of multiple calls to + // dm_get_bytes(). + bool finished = false; + size_t bytes_read = 0; + size_t bytes_written = 0; + char buf[3]; + do { + if (dm_get_bytes(ctx, &bytes_read, &finished, buf, sizeof(buf))) { + return -1; + } + if (bytes_written + bytes_read > sizeof(RESOURCE_0)) { + return -1; + } + memcpy(RESOURCE_0 + bytes_written, buf, bytes_read); + bytes_written += sizeof(buf); + } while (!finished); + return 0; + } + case RID_1_STRING: { + // The idea of this is to enforce of multiple calls to + // dm_get_string(). + char buff[5]; + int retval = DM_BUFFER_TOO_SHORT; + memset(RESOURCE_1, 0x00, sizeof(RESOURCE_1)); + size_t chars_written = 0; + do { + retval = dm_get_string(ctx, buff, sizeof(buff)); + chars_written += strlen(buff); + if (chars_written > sizeof(RESOURCE_1)) { + return -1; + } + strcpy(RESOURCE_1 + strlen(RESOURCE_1), buff); + } while (retval == DM_BUFFER_TOO_SHORT); + return 0; + } + case RID_2_EXT_BYTES: { + fluf_get_external_data_t *get_external_data = NULL; + size_t len = 0; + void *args = NULL; + dm_get_external_bytes(ctx, &get_external_data, &args, &len); + int result = get_external_data(RESOURCE_2, len, 0, args); + if (result) { + return result; + } + return 0; + } + case RID_3_EXT_STRING: { + fluf_get_external_data_t *get_external_data = NULL; + size_t len = 0; + void *args = NULL; + dm_get_external_string(ctx, &get_external_data, &args, &len); + int result = get_external_data(RESOURCE_3, len, 0, args); + if (result) { + return result; + } + return 0; + } + case RID_4_INT: { + int64_t value; + int retval = dm_get_i64(ctx, &value); + if (retval) { + return retval; + } + RESOURCE_4 = value; + return 0; + } + case RID_5_DOUBLE: { + double value; + int retval = dm_get_double(ctx, &value); + if (retval) { + return retval; + } + RESOURCE_5 = value; + return 0; + } + case RID_6_BOOL: { + bool value; + int retval = dm_get_bool(ctx, &value); + if (retval) { + return retval; + } + RESOURCE_6 = value; + return 0; + } + case RID_7_OBJLNK: { + fluf_oid_t out_oid; + fluf_iid_t out_iid; + int retval = dm_get_objlnk(ctx, &out_oid, &out_iid); + if (retval) { + return retval; + } + RESOURCE_7.oid = out_oid; + RESOURCE_7.iid = out_iid; + return 0; + } + case RID_8_UINT: { + uint64_t value; + int retval = dm_get_u64(ctx, &value); + if (retval) { + return retval; + } + RESOURCE_8 = value; + return 0; + } + case RID_9_TIME: { + uint64_t time; + int retval = dm_get_time(ctx, &time); + if (retval) { + return retval; + } + RESOURCE_9 = time; + return 0; + } + case RID_10_STRING_M: { + char buff[MAX_RID_STR_SIZE]; + int retval = dm_get_string(ctx, buff, sizeof(buff)); + if (retval) { + return retval; + } + if (strlen(buff) < MAX_RID_STR_SIZE) { + strcpy(RESOURCE_10[riid], buff); + return 0; + } + default: + return -1; + } + } +} + +static int list_resource_instances(dm_t *dm, + const dm_object_def_t *const *obj_ptr, + fluf_iid_t iid, + fluf_rid_t rid, + dm_list_ctx_t *ctx) { + (void) dm; + (void) obj_ptr; + (void) iid; + + switch (rid) { + case RID_10_STRING_M: { + for (fluf_riid_t i = 0; i < AVS_ARRAY_SIZE(RESOURCE_10); ++i) { + dm_emit(ctx, i); + } + return 0; + } + default: + AVS_UNREACHABLE( + "Attempted to list instances in a single-instance resource"); + return FLUF_COAP_CODE_INTERNAL_SERVER_ERROR; + } +} + +static int list_resources(dm_t *dm, + const dm_object_def_t *const *obj_ptr, + fluf_iid_t iid, + dm_resource_list_ctx_t *ctx) { + (void) dm; + (void) obj_ptr; + (void) iid; + dm_emit_res(ctx, RID_0_BYTES, DM_RES_RW, DM_RES_PRESENT); + dm_emit_res(ctx, RID_1_STRING, DM_RES_RW, DM_RES_PRESENT); + dm_emit_res(ctx, RID_2_EXT_BYTES, DM_RES_RW, DM_RES_PRESENT); + dm_emit_res(ctx, RID_3_EXT_STRING, DM_RES_RW, DM_RES_PRESENT); + dm_emit_res(ctx, RID_4_INT, DM_RES_RW, DM_RES_PRESENT); + dm_emit_res(ctx, RID_5_DOUBLE, DM_RES_RW, DM_RES_PRESENT); + dm_emit_res(ctx, RID_6_BOOL, DM_RES_RW, DM_RES_PRESENT); + dm_emit_res(ctx, RID_7_OBJLNK, DM_RES_RW, DM_RES_PRESENT); + dm_emit_res(ctx, RID_8_UINT, DM_RES_RW, DM_RES_PRESENT); + dm_emit_res(ctx, RID_9_TIME, DM_RES_RW, DM_RES_PRESENT); + dm_emit_res(ctx, RID_10_STRING_M, DM_RES_RWM, DM_RES_PRESENT); + dm_emit_res(ctx, RID_11_STRING_W, DM_RES_R, DM_RES_PRESENT); + dm_emit_res(ctx, RID_12_ABSENT, DM_RES_RW, DM_RES_ABSENT); + return 0; +} + +static int list_instances(dm_t *dm, + const dm_object_def_t *const *obj_ptr, + dm_list_ctx_t *ctx) { + (void) dm; + (void) obj_ptr; + dm_emit(ctx, 0); + return 0; +} + +static const dm_object_def_t DEF = { + .oid = OID_4, + .handlers.resource_write = resource_write, + .handlers.list_resource_instances = list_resource_instances, + .handlers.list_resources = list_resources, + .handlers.list_instances = list_instances +}; + +static const test_object_t TEST_OBJECT = { + .def = &DEF +}; + +// Global buffer used to store entries that should be written to data model. +static fluf_io_out_entry_t USER_BUFFER[TOTAL_WRITABLE_RES_INST_COUNT]; +static struct user_buffer_struct { + fluf_io_out_entry_t *buffer; + size_t buffer_size; + size_t count; +} USER_BUFFER_STRUCT; + +static int callback_fnc(void *arg, + fluf_data_type_t expected_type, + fluf_io_out_entry_t *in_entry) { + struct user_buffer_struct *user_buffer_struct = + (struct user_buffer_struct *) arg; + if (user_buffer_struct->count >= user_buffer_struct->buffer_size) { + printf("Buffer overflow\n"); + return -1; + } + fluf_io_out_entry_t *user_buffer = + &user_buffer_struct->buffer[user_buffer_struct->count++]; + + if (user_buffer->type == expected_type) { + *in_entry = *user_buffer; + return 0; + } + return -1; +} + +static dm_input_ctx_t IN_CTX; + +#define OBJ_MAX 3 +static dm_t dm; +static dm_installed_object_t objects[OBJ_MAX]; + +#define SET_UP() \ + dm_initialize(&dm, objects, OBJ_MAX); \ + clear_resources(); \ + USER_BUFFER_STRUCT = (struct user_buffer_struct) { \ + .buffer = USER_BUFFER, \ + .buffer_size = AVS_ARRAY_SIZE(USER_BUFFER), \ + .count = 0 \ + }; \ + memset(USER_BUFFER, 0x00, sizeof(USER_BUFFER)); \ + IN_CTX.callback = callback_fnc; \ + IN_CTX.arg = &USER_BUFFER_STRUCT + +AVS_UNIT_TEST(DataModelWrite, WriteResourceInstance) { + SET_UP(); + fluf_riid_t riid = 3; + fluf_uri_path_t uri = FLUF_MAKE_RESOURCE_INSTANCE_PATH( + OID_4, IID_0, RID_10_STRING_M, riid); + + // prepare data to be written + USER_BUFFER[0].type = FLUF_DATA_TYPE_STRING; + USER_BUFFER[0].value.bytes_or_string.data = "protocol"; + USER_BUFFER[0].value.bytes_or_string.chunk_length = + strlen((const char *) USER_BUFFER[0].value.bytes_or_string.data); + USER_BUFFER[0].value.bytes_or_string.full_length_hint = + USER_BUFFER[0].value.bytes_or_string.chunk_length; + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_SUCCESS(dm_write(&dm, &uri, &IN_CTX)); + AVS_UNIT_ASSERT_SUCCESS(dm_unregister_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_EQUAL_STRING( + (const char *) USER_BUFFER[0].value.bytes_or_string.data, + RESOURCE_10[riid]); +} + +AVS_UNIT_TEST(DataModelWrite, WriteStringWithUseOfMultipleGetStringCalls) { + SET_UP(); + fluf_uri_path_t uri = FLUF_MAKE_RESOURCE_PATH(OID_4, IID_0, RID_1_STRING); + + // prepare data to be written + USER_BUFFER[0].type = FLUF_DATA_TYPE_STRING; + USER_BUFFER[0].value.bytes_or_string.data = "123456789"; + USER_BUFFER[0].value.bytes_or_string.chunk_length = + strlen((const char *) USER_BUFFER[0].value.bytes_or_string.data); + USER_BUFFER[0].value.bytes_or_string.full_length_hint = + USER_BUFFER[0].value.bytes_or_string.chunk_length; + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_SUCCESS(dm_write(&dm, &uri, &IN_CTX)); + AVS_UNIT_ASSERT_SUCCESS(dm_unregister_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_EQUAL_STRING( + (const char *) USER_BUFFER[0].value.bytes_or_string.data, + RESOURCE_1); +} + +AVS_UNIT_TEST(DataModelWrite, WriteSingleInstanceResource) { + SET_UP(); + fluf_uri_path_t uri = FLUF_MAKE_RESOURCE_PATH(OID_4, IID_0, RID_4_INT); + + // prepare data to be written + USER_BUFFER[0].type = FLUF_DATA_TYPE_INT; + USER_BUFFER[0].value.int_value = 2137; + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_SUCCESS(dm_write(&dm, &uri, &IN_CTX)); + AVS_UNIT_ASSERT_SUCCESS(dm_unregister_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_EQUAL(2137, RESOURCE_4); +} + +AVS_UNIT_TEST(DataModelWrite, WriteMultiInstanceResource) { + SET_UP(); + fluf_uri_path_t uri = + FLUF_MAKE_RESOURCE_PATH(OID_4, IID_0, RID_10_STRING_M); + + // prepare data to be written + USER_BUFFER[0].value.bytes_or_string.data = "HTTP"; + USER_BUFFER[1].value.bytes_or_string.data = "UDP"; + USER_BUFFER[2].value.bytes_or_string.data = "TCP"; + USER_BUFFER[3].value.bytes_or_string.data = "IP"; + + for (size_t i = 0; i < 4; ++i) { + USER_BUFFER[i].type = FLUF_DATA_TYPE_STRING; + USER_BUFFER[i].value.bytes_or_string.chunk_length = strlen( + (const char *) USER_BUFFER[i].value.bytes_or_string.data); + USER_BUFFER[i].value.bytes_or_string.full_length_hint = + USER_BUFFER[i].value.bytes_or_string.chunk_length; + } + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_SUCCESS(dm_write(&dm, &uri, &IN_CTX)); + AVS_UNIT_ASSERT_SUCCESS(dm_unregister_object(&dm, &TEST_OBJECT.def)); + + AVS_UNIT_ASSERT_EQUAL_STRING(RESOURCE_10[0], "HTTP"); + AVS_UNIT_ASSERT_EQUAL_STRING(RESOURCE_10[1], "UDP"); + AVS_UNIT_ASSERT_EQUAL_STRING(RESOURCE_10[2], "TCP"); + AVS_UNIT_ASSERT_EQUAL_STRING(RESOURCE_10[3], "IP"); +} + +static int get_external_data_clb(void *buffer, + size_t bytes_to_copy, + size_t offset, + void *user_args) { + uint8_t *data = (uint8_t *) user_args; + memcpy(buffer, data + offset, bytes_to_copy); + return 0; +} + +AVS_UNIT_TEST(DataModelWrite, WriteObjectInstance) { + SET_UP(); + fluf_uri_path_t uri = FLUF_MAKE_INSTANCE_PATH(OID_4, IID_0); + + // prepare data to be written + USER_BUFFER[0].type = FLUF_DATA_TYPE_BYTES; + uint8_t bytes[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }; + USER_BUFFER[0].value.bytes_or_string.data = bytes; + USER_BUFFER[0].value.bytes_or_string.offset = 0; + USER_BUFFER[0].value.bytes_or_string.chunk_length = sizeof(bytes); + USER_BUFFER[0].value.bytes_or_string.full_length_hint = sizeof(bytes); + char str[] = "AVSystem"; + USER_BUFFER[1].type = FLUF_DATA_TYPE_STRING; + USER_BUFFER[1].value.bytes_or_string.data = str; + USER_BUFFER[1].value.bytes_or_string.offset = 0; + USER_BUFFER[1].value.bytes_or_string.chunk_length = strlen(str); + USER_BUFFER[1].value.bytes_or_string.full_length_hint = strlen(str); + USER_BUFFER[2].type = FLUF_DATA_TYPE_EXTERNAL_BYTES; + USER_BUFFER[2].value.external_data.get_external_data = + get_external_data_clb; + USER_BUFFER[2].value.external_data.user_args = bytes; + USER_BUFFER[2].value.external_data.length = sizeof(bytes); + USER_BUFFER[3].type = FLUF_DATA_TYPE_EXTERNAL_BYTES; + USER_BUFFER[3].value.external_data.get_external_data = + get_external_data_clb; + USER_BUFFER[3].value.external_data.user_args = str; + USER_BUFFER[3].value.external_data.length = sizeof(str); + USER_BUFFER[4].type = FLUF_DATA_TYPE_INT; + USER_BUFFER[4].value.int_value = -2137; + USER_BUFFER[5].type = FLUF_DATA_TYPE_DOUBLE; + USER_BUFFER[5].value.double_value = 3.14; + USER_BUFFER[6].type = FLUF_DATA_TYPE_BOOL; + USER_BUFFER[6].value.bool_value = true; + USER_BUFFER[7].type = FLUF_DATA_TYPE_OBJLNK; + USER_BUFFER[7].value.objlnk.oid = 1; + USER_BUFFER[7].value.objlnk.iid = 2; + USER_BUFFER[8].type = FLUF_DATA_TYPE_UINT; + USER_BUFFER[8].value.uint_value = 2137; + USER_BUFFER[9].type = FLUF_DATA_TYPE_TIME; + USER_BUFFER[9].value.time_value = 1112470620000; + USER_BUFFER[10].type = FLUF_DATA_TYPE_STRING; + USER_BUFFER[10].value.bytes_or_string.data = "HTTP"; + USER_BUFFER[10].value.bytes_or_string.offset = 0; + USER_BUFFER[10].value.bytes_or_string.chunk_length = + strlen((const char *) USER_BUFFER[10].value.bytes_or_string.data); + USER_BUFFER[10].value.bytes_or_string.full_length_hint = + USER_BUFFER[10].value.bytes_or_string.chunk_length; + USER_BUFFER[11].type = FLUF_DATA_TYPE_STRING; + USER_BUFFER[11].value.bytes_or_string.data = "UDP"; + USER_BUFFER[11].value.bytes_or_string.offset = 0; + USER_BUFFER[11].value.bytes_or_string.chunk_length = + strlen((const char *) USER_BUFFER[11].value.bytes_or_string.data); + USER_BUFFER[11].value.bytes_or_string.full_length_hint = + USER_BUFFER[11].value.bytes_or_string.chunk_length; + USER_BUFFER[12].type = FLUF_DATA_TYPE_STRING; + USER_BUFFER[12].value.bytes_or_string.data = "TCP"; + USER_BUFFER[12].value.bytes_or_string.offset = 0; + USER_BUFFER[12].value.bytes_or_string.chunk_length = + strlen((const char *) USER_BUFFER[12].value.bytes_or_string.data); + USER_BUFFER[12].value.bytes_or_string.full_length_hint = + USER_BUFFER[12].value.bytes_or_string.chunk_length; + USER_BUFFER[13].type = FLUF_DATA_TYPE_STRING; + USER_BUFFER[13].value.bytes_or_string.data = "IP"; + USER_BUFFER[13].value.bytes_or_string.offset = 0; + USER_BUFFER[13].value.bytes_or_string.chunk_length = + strlen((const char *) USER_BUFFER[13].value.bytes_or_string.data); + USER_BUFFER[13].value.bytes_or_string.full_length_hint = + USER_BUFFER[13].value.bytes_or_string.chunk_length; + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_SUCCESS(dm_write(&dm, &uri, &IN_CTX)); + AVS_UNIT_ASSERT_SUCCESS(dm_unregister_object(&dm, &TEST_OBJECT.def)); + + AVS_UNIT_ASSERT_TRUE(!memcmp(RESOURCE_0, bytes, sizeof(bytes))); + AVS_UNIT_ASSERT_EQUAL_STRING(RESOURCE_1, str); + AVS_UNIT_ASSERT_TRUE(!memcmp(RESOURCE_2, bytes, sizeof(bytes))); + AVS_UNIT_ASSERT_EQUAL_STRING(RESOURCE_3, str); + AVS_UNIT_ASSERT_EQUAL(RESOURCE_4, -2137); + AVS_UNIT_ASSERT_EQUAL(RESOURCE_5, 3.14); + AVS_UNIT_ASSERT_TRUE(RESOURCE_6); + AVS_UNIT_ASSERT_EQUAL(RESOURCE_7.oid, 1); + AVS_UNIT_ASSERT_EQUAL(RESOURCE_7.iid, 2); + AVS_UNIT_ASSERT_EQUAL(RESOURCE_8, 2137); + AVS_UNIT_ASSERT_EQUAL(RESOURCE_9, 1112470620000); + AVS_UNIT_ASSERT_EQUAL_STRING(RESOURCE_10[0], "HTTP"); + AVS_UNIT_ASSERT_EQUAL_STRING(RESOURCE_10[1], "UDP"); + AVS_UNIT_ASSERT_EQUAL_STRING(RESOURCE_10[2], "TCP"); + AVS_UNIT_ASSERT_EQUAL_STRING(RESOURCE_10[3], "IP"); +} + +AVS_UNIT_TEST(DataModelWrite, WriteNotPresentObjectInstance) { + SET_UP(); + fluf_riid_t riid = 4; + fluf_uri_path_t uri = FLUF_MAKE_RESOURCE_INSTANCE_PATH( + OID_4, IID_0, RID_10_STRING_M, riid); + // deliberately missing dm_register here + AVS_UNIT_ASSERT_EQUAL(dm_write(&dm, &uri, &IN_CTX), + FLUF_COAP_CODE_NOT_FOUND); +} + +static int error_callback(void *arg, + fluf_data_type_t expected_type, + fluf_io_out_entry_t *entry) { + (void) arg; + (void) expected_type; + (void) entry; + return -1; +} + +AVS_UNIT_TEST(DataModelWrite, WriteCheckCTXCallbackError) { + SET_UP(); + fluf_riid_t riid = 3; + fluf_uri_path_t uri = FLUF_MAKE_RESOURCE_INSTANCE_PATH( + OID_4, IID_0, RID_10_STRING_M, riid); + IN_CTX.callback = error_callback; + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_EQUAL(dm_write(&dm, &uri, &IN_CTX), -1); + AVS_UNIT_ASSERT_SUCCESS(dm_unregister_object(&dm, &TEST_OBJECT.def)); +} + +AVS_UNIT_TEST(DataModelWrite, WriteWithUriWithNoIID) { + SET_UP(); + fluf_uri_path_t uri = FLUF_MAKE_OBJECT_PATH(OID_4); + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_EQUAL(dm_write(&dm, &uri, &IN_CTX), + FLUF_COAP_CODE_METHOD_NOT_ALLOWED); + AVS_UNIT_ASSERT_SUCCESS(dm_unregister_object(&dm, &TEST_OBJECT.def)); +} + +AVS_UNIT_TEST(DataModelWrite, WriteReadOnly) { + SET_UP(); + fluf_uri_path_t uri = + FLUF_MAKE_RESOURCE_PATH(OID_4, IID_0, RID_11_STRING_W); + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_EQUAL(dm_write(&dm, &uri, &IN_CTX), + FLUF_COAP_CODE_METHOD_NOT_ALLOWED); + AVS_UNIT_ASSERT_SUCCESS(dm_unregister_object(&dm, &TEST_OBJECT.def)); +} + +AVS_UNIT_TEST(DataModelWrite, WriteNotPresent) { + SET_UP(); + fluf_uri_path_t uri = FLUF_MAKE_RESOURCE_PATH(OID_4, IID_0, RID_12_ABSENT); + + AVS_UNIT_ASSERT_SUCCESS(dm_register_object(&dm, &TEST_OBJECT.def)); + AVS_UNIT_ASSERT_EQUAL(dm_write(&dm, &uri, &IN_CTX), + FLUF_COAP_CODE_NOT_FOUND); + AVS_UNIT_ASSERT_SUCCESS(dm_unregister_object(&dm, &TEST_OBJECT.def)); +} diff --git a/tests/anj/dm/static_functions_tests/core_statics.c b/tests/anj/dm/static_functions_tests/core_statics.c new file mode 100644 index 00000000..cabd74b7 --- /dev/null +++ b/tests/anj/dm/static_functions_tests/core_statics.c @@ -0,0 +1,67 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#include + +AVS_UNIT_TEST(DataModelCoreStatics, DefaultDepthObject) { + fluf_uri_path_t uri = FLUF_MAKE_OBJECT_PATH(1); + AVS_UNIT_ASSERT_EQUAL(infer_depth(&uri, NULL), FLUF_ID_RID); +} + +AVS_UNIT_TEST(DataModelCoreStatics, DefaultDepthInstance) { + fluf_uri_path_t uri = FLUF_MAKE_INSTANCE_PATH(1, 1); + AVS_UNIT_ASSERT_EQUAL(infer_depth(&uri, NULL), FLUF_ID_RID); +} + +AVS_UNIT_TEST(DataModelCoreStatics, DefaultDepthResource) { + fluf_uri_path_t uri = FLUF_MAKE_RESOURCE_PATH(1, 1, 1); + AVS_UNIT_ASSERT_EQUAL(infer_depth(&uri, NULL), FLUF_ID_RIID); +} + +AVS_UNIT_TEST(DataModelCoreStatics, ArbitraryDepthObject) { + fluf_uri_path_t uri = FLUF_MAKE_OBJECT_PATH(1); + uint8_t depth = 0; + AVS_UNIT_ASSERT_EQUAL(infer_depth(&uri, &depth), FLUF_ID_OID); + depth = 1; + AVS_UNIT_ASSERT_EQUAL(infer_depth(&uri, &depth), FLUF_ID_IID); + depth = 2; + AVS_UNIT_ASSERT_EQUAL(infer_depth(&uri, &depth), FLUF_ID_RID); + depth = 3; + AVS_UNIT_ASSERT_EQUAL(infer_depth(&uri, &depth), FLUF_ID_RIID); + + // depth above permitted value + depth = 4; + AVS_UNIT_ASSERT_EQUAL(infer_depth(&uri, &depth), FLUF_ID_RIID); +} + +AVS_UNIT_TEST(DataModelCoreStatics, ArbitraryDepthInstance) { + fluf_uri_path_t uri = FLUF_MAKE_INSTANCE_PATH(1, 1); + uint8_t depth = 0; + AVS_UNIT_ASSERT_EQUAL(infer_depth(&uri, &depth), FLUF_ID_IID); + depth = 1; + AVS_UNIT_ASSERT_EQUAL(infer_depth(&uri, &depth), FLUF_ID_RID); + depth = 2; + AVS_UNIT_ASSERT_EQUAL(infer_depth(&uri, &depth), FLUF_ID_RIID); + + // depth above logical value + depth = 3; + AVS_UNIT_ASSERT_EQUAL(infer_depth(&uri, &depth), FLUF_ID_RIID); +} + +AVS_UNIT_TEST(DataModelCoreStatics, ArbitraryDepthResource) { + fluf_uri_path_t uri = FLUF_MAKE_RESOURCE_PATH(1, 1, 1); + uint8_t depth = 0; + AVS_UNIT_ASSERT_EQUAL(infer_depth(&uri, &depth), FLUF_ID_RID); + depth = 1; + AVS_UNIT_ASSERT_EQUAL(infer_depth(&uri, &depth), FLUF_ID_RIID); + + // depth above logical value + depth = 2; + AVS_UNIT_ASSERT_EQUAL(infer_depth(&uri, &depth), FLUF_ID_RIID); +} diff --git a/tests/anj/sdm/sdm.c b/tests/anj/sdm/sdm.c new file mode 100644 index 00000000..70050ed4 --- /dev/null +++ b/tests/anj/sdm/sdm.c @@ -0,0 +1,255 @@ +/* + * Copyright 2023-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 "../../../src/anj/sdm/sdm_core.h" + +AVS_UNIT_TEST(sdm, add_remove_object) { + sdm_data_model_t dm_obj_test; + sdm_obj_t *objs_array[5]; + uint16_t objs_array_size = 5; + + sdm_initialize(&dm_obj_test, objs_array, objs_array_size); + AVS_UNIT_ASSERT_EQUAL(dm_obj_test.max_allowed_objs_number, 5); + + sdm_obj_t obj_1 = { + .oid = 1 + }; + AVS_UNIT_ASSERT_SUCCESS(sdm_add_obj(&dm_obj_test, &obj_1)); + sdm_obj_t obj_2 = { + .oid = 3, + .version = "2.2" + }; + AVS_UNIT_ASSERT_SUCCESS(sdm_add_obj(&dm_obj_test, &obj_2)); + sdm_obj_t obj_3 = { + .oid = 2 + }; + AVS_UNIT_ASSERT_SUCCESS(sdm_add_obj(&dm_obj_test, &obj_3)); + sdm_obj_t obj_e1 = { + .oid = 2 + }; + AVS_UNIT_ASSERT_EQUAL(sdm_add_obj(&dm_obj_test, &obj_e1), SDM_ERR_LOGIC); + sdm_obj_t obj_4 = { + .oid = 0 + }; + AVS_UNIT_ASSERT_SUCCESS(sdm_add_obj(&dm_obj_test, &obj_4)); + sdm_obj_t obj_5 = { + .oid = 4 + }; + AVS_UNIT_ASSERT_SUCCESS(sdm_add_obj(&dm_obj_test, &obj_5)); + sdm_obj_t obj_e3 = { + .oid = 7 + }; + AVS_UNIT_ASSERT_EQUAL(sdm_add_obj(&dm_obj_test, &obj_e3), SDM_ERR_MEMORY); + AVS_UNIT_ASSERT_EQUAL(dm_obj_test.objs_count, 5); + + AVS_UNIT_ASSERT_SUCCESS(sdm_remove_obj(&dm_obj_test, 4)); + AVS_UNIT_ASSERT_EQUAL(dm_obj_test.objs_count, 4); + AVS_UNIT_ASSERT_EQUAL(sdm_remove_obj(&dm_obj_test, 4), SDM_ERR_NOT_FOUND); + AVS_UNIT_ASSERT_EQUAL(dm_obj_test.objs_count, 4); + AVS_UNIT_ASSERT_SUCCESS(sdm_remove_obj(&dm_obj_test, 1)); + AVS_UNIT_ASSERT_EQUAL(dm_obj_test.objs_count, 3); + AVS_UNIT_ASSERT_SUCCESS(sdm_remove_obj(&dm_obj_test, 2)); + AVS_UNIT_ASSERT_EQUAL(dm_obj_test.objs_count, 2); + AVS_UNIT_ASSERT_SUCCESS(sdm_remove_obj(&dm_obj_test, 3)); + AVS_UNIT_ASSERT_EQUAL(dm_obj_test.objs_count, 1); + AVS_UNIT_ASSERT_SUCCESS(sdm_add_obj(&dm_obj_test, &obj_3)); + AVS_UNIT_ASSERT_EQUAL(dm_obj_test.objs_count, 2); + AVS_UNIT_ASSERT_SUCCESS(sdm_remove_obj(&dm_obj_test, 2)); + AVS_UNIT_ASSERT_EQUAL(dm_obj_test.objs_count, 1); + AVS_UNIT_ASSERT_SUCCESS(sdm_remove_obj(&dm_obj_test, 0)); + AVS_UNIT_ASSERT_EQUAL(dm_obj_test.objs_count, 0); + AVS_UNIT_ASSERT_EQUAL(sdm_remove_obj(&dm_obj_test, 4), SDM_ERR_NOT_FOUND); + AVS_UNIT_ASSERT_EQUAL(dm_obj_test.objs_count, 0); +} + +static sdm_res_spec_t res_spec_0 = { + .rid = 0, + .operation = SDM_RES_R, + .type = FLUF_DATA_TYPE_INT +}; +static const sdm_res_spec_t res_spec_1 = { + .rid = 1, + .operation = SDM_RES_W, + .type = FLUF_DATA_TYPE_INT +}; +static const sdm_res_spec_t res_spec_2 = { + .rid = 2, + .operation = SDM_RES_RWM, + .type = FLUF_DATA_TYPE_INT +}; +static const sdm_res_spec_t res_spec_3 = { + .rid = 3, + .operation = SDM_RES_WM, + .type = FLUF_DATA_TYPE_INT +}; +static sdm_res_spec_t res_spec_4 = { + .rid = 4, + .operation = SDM_RES_R, + .type = FLUF_DATA_TYPE_INT +}; +static const sdm_res_spec_t res_spec_5 = { + .rid = 5, + .operation = SDM_RES_E +}; +static sdm_res_t inst_1_res[] = { + { + .res_spec = &res_spec_0 + }, + { + .res_spec = &res_spec_1 + } +}; +static sdm_res_inst_t res_inst_1 = { + .riid = 1 +}; +static sdm_res_inst_t res_inst_2 = { + .riid = 2 +}; +static sdm_res_inst_t *res_insts[] = { &res_inst_1, &res_inst_2 }; +static int res_execute(sdm_obj_t *obj, + sdm_obj_inst_t *obj_inst, + sdm_res_t *res, + const char *execute_arg, + size_t execute_arg_len) { + (void) obj; + (void) obj_inst; + (void) res; + (void) execute_arg; + (void) execute_arg_len; + return 0; +} + +static sdm_res_handlers_t res_handlers = { + .res_execute = res_execute +}; +static sdm_res_t inst_2_res[] = { + { + .res_spec = &res_spec_0 + }, + { + .res_spec = &res_spec_1 + }, + { + .res_spec = &res_spec_2, + .value.res_inst.inst_count = 2, + .value.res_inst.max_inst_count = 2, + .value.res_inst.insts = res_insts + }, + { + .res_spec = &res_spec_3, + .value.res_inst.inst_count = 0 + }, + { + .res_spec = &res_spec_4 + }, + { + .res_spec = &res_spec_5, + .res_handlers = &res_handlers + } +}; +static sdm_obj_inst_t obj_1_inst_1 = { + .iid = 1, + .res_count = 2, + .resources = inst_1_res +}; +static sdm_obj_inst_t obj_1_inst_2 = { + .iid = 2, + .res_count = 6, + .resources = inst_2_res +}; +static sdm_obj_inst_t *obj_1_insts[2] = { &obj_1_inst_1, &obj_1_inst_2 }; +static sdm_obj_t obj = { + .oid = 1, + .version = "1.1", + .insts = obj_1_insts, + .inst_count = 2, + .max_inst_count = 2 +}; + +AVS_UNIT_TEST(sdm, add_obj_check) { + AVS_UNIT_ASSERT_SUCCESS(_sdm_check_obj(&obj)); +} + +AVS_UNIT_TEST(sdm, add_obj_check_error_instances) { + obj.insts = NULL; + AVS_UNIT_ASSERT_EQUAL(_sdm_check_obj(&obj), SDM_ERR_INPUT_ARG); + obj.insts = obj_1_insts; + AVS_UNIT_ASSERT_SUCCESS(_sdm_check_obj(&obj)); +} + +AVS_UNIT_TEST(sdm, add_obj_check_error_max_inst_count) { + obj.max_inst_count = 1; + AVS_UNIT_ASSERT_EQUAL(_sdm_check_obj(&obj), SDM_ERR_INPUT_ARG); + obj.max_inst_count = 2; + AVS_UNIT_ASSERT_SUCCESS(_sdm_check_obj(&obj)); +} + +AVS_UNIT_TEST(sdm, add_obj_check_error_iid) { + obj.insts[0]->iid = 5; + AVS_UNIT_ASSERT_EQUAL(_sdm_check_obj(&obj), SDM_ERR_INPUT_ARG); + obj.insts[0]->iid = 2; + AVS_UNIT_ASSERT_EQUAL(_sdm_check_obj(&obj), SDM_ERR_INPUT_ARG); + obj.insts[0]->iid = 1; + AVS_UNIT_ASSERT_SUCCESS(_sdm_check_obj(&obj)); +} + +AVS_UNIT_TEST(sdm, add_obj_check_error_rid) { + res_spec_0.rid = 5; + AVS_UNIT_ASSERT_EQUAL(_sdm_check_obj(&obj), SDM_ERR_INPUT_ARG); + res_spec_0.rid = 0; + AVS_UNIT_ASSERT_SUCCESS(_sdm_check_obj(&obj)); +} + +AVS_UNIT_TEST(sdm, add_obj_check_error_type) { + res_spec_4.type = 7777; + AVS_UNIT_ASSERT_EQUAL(_sdm_check_obj(&obj), SDM_ERR_INPUT_ARG); + res_spec_4.type = FLUF_DATA_TYPE_INT; + AVS_UNIT_ASSERT_SUCCESS(_sdm_check_obj(&obj)); +} + +AVS_UNIT_TEST(sdm, add_obj_check_error_riid) { + res_inst_1.riid = 2; + AVS_UNIT_ASSERT_EQUAL(_sdm_check_obj(&obj), SDM_ERR_INPUT_ARG); + res_inst_1.riid = 1; + AVS_UNIT_ASSERT_SUCCESS(_sdm_check_obj(&obj)); +} + +AVS_UNIT_TEST(sdm, add_obj_check_error_execute_handler) { + res_handlers.res_execute = NULL; + AVS_UNIT_ASSERT_EQUAL(_sdm_check_obj(&obj), SDM_ERR_INPUT_ARG); + res_handlers.res_execute = res_execute; + AVS_UNIT_ASSERT_SUCCESS(_sdm_check_obj(&obj)); +} + +AVS_UNIT_TEST(sdm, add_obj_check_error_execute_handler_2) { + inst_2_res[5].res_handlers = NULL; + AVS_UNIT_ASSERT_EQUAL(_sdm_check_obj(&obj), SDM_ERR_INPUT_ARG); + inst_2_res[5].res_handlers = &res_handlers; + AVS_UNIT_ASSERT_SUCCESS(_sdm_check_obj(&obj)); +} + +AVS_UNIT_TEST(sdm, add_obj_check_error_max_allowed_res_insts_number) { + inst_2_res[2].value.res_inst.max_inst_count = 1; + AVS_UNIT_ASSERT_EQUAL(_sdm_check_obj(&obj), SDM_ERR_INPUT_ARG); + inst_2_res[2].value.res_inst.max_inst_count = 2; + AVS_UNIT_ASSERT_SUCCESS(_sdm_check_obj(&obj)); +} diff --git a/tests/anj/sdm/sdm_bootstrap_discover.c b/tests/anj/sdm/sdm_bootstrap_discover.c new file mode 100644 index 00000000..961f6612 --- /dev/null +++ b/tests/anj/sdm/sdm_bootstrap_discover.c @@ -0,0 +1,307 @@ +/* + * Copyright 2023-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 + +static const sdm_res_spec_t res_spec_00 = { + .rid = 0, + .operation = SDM_RES_R, + .type = FLUF_DATA_TYPE_STRING +}; +static const sdm_res_spec_t res_spec_01 = { + .rid = 1, + .operation = SDM_RES_RW, + .type = FLUF_DATA_TYPE_BOOL +}; +static const sdm_res_spec_t res_spec_010 = { + .rid = 10, + .operation = SDM_RES_R, + .type = FLUF_DATA_TYPE_INT +}; +static const sdm_res_spec_t res_spec_017 = { + .rid = 17, + .operation = SDM_RES_R, + .type = FLUF_DATA_TYPE_OBJLNK +}; + +static sdm_res_t inst_00_res[] = { + { + .res_spec = &res_spec_00, + .value.res_value.value.bytes_or_string.data = "DDD" + }, + { + .res_spec = &res_spec_01, + .value.res_value.value.bool_value = true + }, + { + .res_spec = &res_spec_010, + .value.res_value.value.uint_value = 99 + }, + { + .res_spec = &res_spec_017, + .value.res_value.value.objlnk.oid = 21, + .value.res_value.value.objlnk.iid = 0 + } +}; +static sdm_res_t inst_01_res[] = { + { + .res_spec = &res_spec_00, + .value.res_value.value.bytes_or_string.data = "SSS" + }, + { + .res_spec = &res_spec_01, + .value.res_value.value.bool_value = false + }, + { + .res_spec = &res_spec_010, + .value.res_value.value.uint_value = 199 + }, + { + .res_spec = &res_spec_017, + .value.res_value.value.objlnk.oid = 21, + .value.res_value.value.objlnk.iid = 0 + } +}; + +static sdm_obj_inst_t obj_0_inst_1 = { + .iid = 0, + .res_count = 4, + .resources = inst_00_res +}; +static sdm_obj_inst_t obj_0_inst_2 = { + .iid = 1, + .res_count = 4, + .resources = inst_01_res +}; +static sdm_obj_inst_t *obj_0_insts[] = { &obj_0_inst_1, &obj_0_inst_2 }; +static sdm_obj_t obj_0 = { + .oid = 0, + .insts = obj_0_insts, + .inst_count = 2, + .max_inst_count = 2 +}; + +static const sdm_res_spec_t res_spec_0 = { + .rid = 0, + .operation = SDM_RES_R, + .type = FLUF_DATA_TYPE_INT +}; +static const sdm_res_spec_t res_spec_1 = { + .rid = 1, + .operation = SDM_RES_W, + .type = FLUF_DATA_TYPE_INT +}; + +static sdm_res_t inst_1_res[] = { + { + .res_spec = &res_spec_0, + .value.res_value.value.int_value = 11 + }, + { + .res_spec = &res_spec_1 + } +}; +static sdm_res_t inst_2_res[] = { + { + .res_spec = &res_spec_0, + .value.res_value.value.int_value = 22 + }, + { + .res_spec = &res_spec_1 + } +}; + +static sdm_obj_inst_t obj_1_inst_1 = { + .iid = 1, + .res_count = 2, + .resources = inst_1_res +}; +static sdm_obj_inst_t obj_1_inst_2 = { + .iid = 2, + .res_count = 2, + .resources = inst_2_res +}; +static sdm_obj_inst_t *obj_1_insts[] = { &obj_1_inst_1, &obj_1_inst_2 }; +static sdm_obj_t obj_1 = { + .oid = 1, + .version = "1.1", + .insts = obj_1_insts, + .inst_count = 2, + .max_inst_count = 2 +}; +static sdm_obj_inst_t obj_3_inst_0 = { + .iid = 0 +}; +static sdm_obj_inst_t *obj_3_insts[1] = { &obj_3_inst_0 }; +static sdm_obj_t obj_3 = { + .oid = 3, + .insts = obj_3_insts, + .inst_count = 1, + .max_inst_count = 1 +}; +static sdm_obj_t obj_5 = { + .oid = 5 +}; +static sdm_obj_t obj_55 = { + .oid = 55, + .version = "1.2" +}; +static sdm_obj_inst_t obj_21_inst_0 = { + .iid = 0 +}; +static sdm_obj_inst_t *obj_21_insts[1] = { &obj_21_inst_0 }; +static sdm_obj_t obj_21 = { + .oid = 21, + .insts = obj_21_insts, + .inst_count = 1, + .max_inst_count = 1 +}; + +typedef struct { + fluf_uri_path_t path; + const char *version; + const uint16_t ssid; + const char *uri; +} boot_discover_record_t; + +#define BOOTSTRAP_DISCOVER_TEST(Path, Idx_start, Idx_end) \ + sdm_data_model_t dm; \ + sdm_obj_t *objs[6]; \ + sdm_initialize(&dm, objs, 6); \ + AVS_UNIT_ASSERT_SUCCESS(sdm_add_obj(&dm, &obj_0)); \ + AVS_UNIT_ASSERT_SUCCESS(sdm_add_obj(&dm, &obj_1)); \ + AVS_UNIT_ASSERT_SUCCESS(sdm_add_obj(&dm, &obj_3)); \ + AVS_UNIT_ASSERT_SUCCESS(sdm_add_obj(&dm, &obj_5)); \ + AVS_UNIT_ASSERT_SUCCESS(sdm_add_obj(&dm, &obj_55)); \ + AVS_UNIT_ASSERT_SUCCESS(sdm_add_obj(&dm, &obj_21)); \ + AVS_UNIT_ASSERT_SUCCESS( \ + sdm_operation_begin(&dm, FLUF_OP_DM_DISCOVER, true, &Path)); \ + for (size_t idx = Idx_start; idx <= Idx_end; idx++) { \ + fluf_uri_path_t out_path; \ + const char *out_version; \ + const uint16_t *out_ssid; \ + const char *out_uri; \ + int res = sdm_get_bootstrap_discover_record( \ + &dm, &out_path, &out_version, &out_ssid, &out_uri); \ + AVS_UNIT_ASSERT_TRUE( \ + fluf_uri_path_equal(&out_path, &boot_disc_records[idx].path)); \ + if (out_version && boot_disc_records[idx].version) { \ + AVS_UNIT_ASSERT_SUCCESS( \ + strcmp(out_version, boot_disc_records[idx].version)); \ + } else { \ + AVS_UNIT_ASSERT_TRUE(!out_version \ + && !boot_disc_records[idx].version); \ + } \ + if (out_ssid && boot_disc_records[idx].ssid) { \ + AVS_UNIT_ASSERT_TRUE(*out_ssid == boot_disc_records[idx].ssid); \ + } else { \ + AVS_UNIT_ASSERT_TRUE(!out_ssid && !boot_disc_records[idx].ssid); \ + } \ + if (out_uri && boot_disc_records[idx].uri) { \ + AVS_UNIT_ASSERT_SUCCESS( \ + strcmp(out_uri, boot_disc_records[idx].uri)); \ + } else { \ + AVS_UNIT_ASSERT_TRUE(!out_uri && !boot_disc_records[idx].uri); \ + } \ + if (idx == Idx_end) { \ + AVS_UNIT_ASSERT_EQUAL(res, SDM_LAST_RECORD); \ + } else { \ + AVS_UNIT_ASSERT_EQUAL(res, 0); \ + } \ + } \ + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + +/** + * 0: + * 0 + * 0 "DDD" + * 1 true + * 10 99 + * 17 21:0 + * 1 + * 0 "SSS" + * 1 false + * 10 199 + * 17 21:0 + * 1: version = "1.1" + * 1 + * 0 SSID = 11 + * 1 + * 2 + * 0 SSID = 22 + * 1 + * 3: + * 0 + * 5 + * 21: + * 0 + * 55: version = "1.2" + */ +// clang-format off +static boot_discover_record_t boot_disc_records [12] = { + {.path = FLUF_MAKE_OBJECT_PATH(0)}, + {.path = FLUF_MAKE_INSTANCE_PATH(0,0)}, + {.path = FLUF_MAKE_INSTANCE_PATH(0,1), .ssid = 199, .uri = "SSS"}, + {.path = FLUF_MAKE_OBJECT_PATH(1), .version = "1.1"}, + {.path = FLUF_MAKE_INSTANCE_PATH(1,1), .ssid = 11}, + {.path = FLUF_MAKE_INSTANCE_PATH(1,2), .ssid = 22}, + {.path = FLUF_MAKE_OBJECT_PATH(3)}, + {.path = FLUF_MAKE_INSTANCE_PATH(3,0)}, + {.path = FLUF_MAKE_OBJECT_PATH(5)}, + {.path = FLUF_MAKE_OBJECT_PATH(21)}, + {.path = FLUF_MAKE_INSTANCE_PATH(21,0), .ssid = 199}, + {.path = FLUF_MAKE_OBJECT_PATH(55), .version = "1.2"} +}; +// clang-format on + +AVS_UNIT_TEST(sdm_bootstrap_discover, root) { + fluf_uri_path_t path = FLUF_MAKE_ROOT_PATH(); + BOOTSTRAP_DISCOVER_TEST(path, 0, 11); +} + +AVS_UNIT_TEST(sdm_bootstrap_discover, object_0) { + fluf_uri_path_t path = FLUF_MAKE_OBJECT_PATH(0); + BOOTSTRAP_DISCOVER_TEST(path, 0, 2); +} + +AVS_UNIT_TEST(sdm_bootstrap_discover, object_1) { + fluf_uri_path_t path = FLUF_MAKE_OBJECT_PATH(1); + BOOTSTRAP_DISCOVER_TEST(path, 3, 5); +} + +AVS_UNIT_TEST(sdm_bootstrap_discover, object_3) { + fluf_uri_path_t path = FLUF_MAKE_OBJECT_PATH(3); + BOOTSTRAP_DISCOVER_TEST(path, 6, 7); +} + +AVS_UNIT_TEST(sdm_bootstrap_discover, object_5) { + fluf_uri_path_t path = FLUF_MAKE_OBJECT_PATH(5); + BOOTSTRAP_DISCOVER_TEST(path, 8, 8); +} + +AVS_UNIT_TEST(sdm_bootstrap_discover, object_21) { + fluf_uri_path_t path = FLUF_MAKE_OBJECT_PATH(21); + BOOTSTRAP_DISCOVER_TEST(path, 9, 10); +} + +AVS_UNIT_TEST(sdm_bootstrap_discover, object_55) { + fluf_uri_path_t path = FLUF_MAKE_OBJECT_PATH(55); + BOOTSTRAP_DISCOVER_TEST(path, 11, 11); +} diff --git a/tests/anj/sdm/sdm_create.c b/tests/anj/sdm/sdm_create.c new file mode 100644 index 00000000..e5df2d49 --- /dev/null +++ b/tests/anj/sdm/sdm_create.c @@ -0,0 +1,281 @@ +/* + * Copyright 2023-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 + +static int call_counter_begin; +static int call_counter_end; +static int call_counter_validate; +static bool inst_create_return_eror; +static int call_counter_create; +static fluf_iid_t call_iid; +static sdm_op_result_t call_result; + +static int operation_begin(sdm_obj_t *obj, fluf_op_t operation) { + (void) obj; + (void) operation; + call_counter_begin++; + return 0; +} + +static int operation_end(sdm_obj_t *obj, sdm_op_result_t result) { + (void) obj; + call_result = result; + call_counter_end++; + return 0; +} + +sdm_obj_inst_t new_0; +sdm_res_spec_t res_spec_new = { + .rid = 7, + .operation = SDM_RES_RW, + .type = FLUF_DATA_TYPE_DOUBLE +}; +sdm_res_t res_new[] = { + { + .res_spec = &res_spec_new, + } +}; +sdm_obj_inst_t new_1 = { + .res_count = 1, + .resources = res_new +}; +sdm_obj_inst_t new_2; +static int +inst_create(sdm_obj_t *obj, sdm_obj_inst_t **out_obj_inst, fluf_iid_t iid) { + (void) obj; + (void) obj; + call_iid = iid; + if (!call_counter_create) { + *out_obj_inst = &new_0; + } else if (call_counter_create == 1) { + *out_obj_inst = &new_1; + } else { + *out_obj_inst = &new_2; + } + call_counter_create++; + if (inst_create_return_eror) { + return -1; + } + return 0; +} + +static int operation_validate(sdm_obj_t *obj) { + (void) obj; + call_counter_validate++; + return 0; +} + +#define TEST_INIT(Dm, Obj) \ + sdm_res_spec_t res_spec_0 = { \ + .rid = 0, \ + .operation = SDM_RES_RW, \ + .type = FLUF_DATA_TYPE_INT \ + }; \ + sdm_res_t res_1[] = { \ + { \ + .res_spec = &res_spec_0, \ + } \ + }; \ + sdm_obj_inst_t obj_inst_1 = { \ + .iid = 1, \ + .res_count = 1, \ + .resources = res_1 \ + }; \ + sdm_obj_inst_t obj_inst_3 = { \ + .iid = 3, \ + }; \ + sdm_obj_inst_t *obj_insts[5] = { &obj_inst_1, &obj_inst_3 }; \ + sdm_obj_handlers_t handlers = { \ + .operation_begin = operation_begin, \ + .operation_end = operation_end, \ + .operation_validate = operation_validate, \ + .inst_create = inst_create \ + }; \ + sdm_obj_t Obj = { \ + .oid = 1, \ + .insts = obj_insts, \ + .inst_count = 2, \ + .obj_handlers = &handlers, \ + .max_inst_count = 5 \ + }; \ + sdm_data_model_t Dm; \ + sdm_obj_t *Objs[1]; \ + sdm_initialize(&Dm, Objs, 1); \ + AVS_UNIT_ASSERT_SUCCESS(sdm_add_obj(&Dm, &Obj)); \ + call_counter_begin = 0; \ + call_counter_end = 0; \ + call_counter_validate = 0; \ + call_counter_create = 0; \ + call_iid = 777; \ + call_result = 4; + +AVS_UNIT_TEST(sdm_create, create) { + TEST_INIT(dm, obj); + + fluf_uri_path_t path = FLUF_MAKE_OBJECT_PATH(1); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_CREATE, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + AVS_UNIT_ASSERT_EQUAL(call_iid, 0); + + path = FLUF_MAKE_OBJECT_PATH(1); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_CREATE, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + AVS_UNIT_ASSERT_EQUAL(call_iid, 2); + + path = FLUF_MAKE_OBJECT_PATH(1); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_CREATE, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + AVS_UNIT_ASSERT_EQUAL(call_iid, 4); + + AVS_UNIT_ASSERT_TRUE(obj_insts[0] == &new_0); + AVS_UNIT_ASSERT_TRUE(obj_insts[1] == &obj_inst_1); + AVS_UNIT_ASSERT_TRUE(obj_insts[2] == &new_1); + AVS_UNIT_ASSERT_TRUE(obj_insts[3] == &obj_inst_3); + AVS_UNIT_ASSERT_TRUE(obj_insts[4] == &new_2); + AVS_UNIT_ASSERT_EQUAL(obj_insts[0]->iid, new_0.iid); + AVS_UNIT_ASSERT_EQUAL(obj_insts[1]->iid, obj_inst_1.iid); + AVS_UNIT_ASSERT_EQUAL(obj_insts[2]->iid, new_1.iid); + AVS_UNIT_ASSERT_EQUAL(obj_insts[3]->iid, obj_inst_3.iid); + AVS_UNIT_ASSERT_EQUAL(obj_insts[4]->iid, new_2.iid); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 3); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 3); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 3); + AVS_UNIT_ASSERT_EQUAL(call_counter_create, 3); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_MODIFIED); +} + +AVS_UNIT_TEST(sdm_create, create_with_write) { + TEST_INIT(dm, obj); + + fluf_uri_path_t path = FLUF_MAKE_OBJECT_PATH(1); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_CREATE, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + AVS_UNIT_ASSERT_EQUAL(call_iid, 0); + + path = FLUF_MAKE_OBJECT_PATH(1); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_CREATE, false, &path)); + fluf_io_out_entry_t record = { + .type = FLUF_DATA_TYPE_DOUBLE, + .path = FLUF_MAKE_RESOURCE_PATH(1, 2, 7), + .value.double_value = 17.25 + }; + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + AVS_UNIT_ASSERT_EQUAL(call_iid, 2); + + path = FLUF_MAKE_OBJECT_PATH(1); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_CREATE, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + AVS_UNIT_ASSERT_EQUAL(call_iid, 4); + + AVS_UNIT_ASSERT_TRUE(obj_insts[0] == &new_0); + AVS_UNIT_ASSERT_TRUE(obj_insts[1] == &obj_inst_1); + AVS_UNIT_ASSERT_TRUE(obj_insts[2] == &new_1); + AVS_UNIT_ASSERT_TRUE(obj_insts[3] == &obj_inst_3); + AVS_UNIT_ASSERT_TRUE(obj_insts[4] == &new_2); + AVS_UNIT_ASSERT_EQUAL(obj_insts[0]->iid, new_0.iid); + AVS_UNIT_ASSERT_EQUAL(obj_insts[1]->iid, obj_inst_1.iid); + AVS_UNIT_ASSERT_EQUAL(obj_insts[2]->iid, new_1.iid); + AVS_UNIT_ASSERT_EQUAL(obj_insts[3]->iid, obj_inst_3.iid); + AVS_UNIT_ASSERT_EQUAL(obj_insts[4]->iid, new_2.iid); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 3); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 3); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 3); + AVS_UNIT_ASSERT_EQUAL(call_counter_create, 3); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_MODIFIED); + + AVS_UNIT_ASSERT_EQUAL(res_new[0].value.res_value.value.double_value, 17.25); +} + +AVS_UNIT_TEST(sdm_create, create_error_write_path) { + TEST_INIT(dm, obj); + + fluf_uri_path_t path = FLUF_MAKE_OBJECT_PATH(1); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_CREATE, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + AVS_UNIT_ASSERT_EQUAL(call_iid, 0); + + path = FLUF_MAKE_OBJECT_PATH(1); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_CREATE, false, &path)); + fluf_io_out_entry_t record = { + .type = FLUF_DATA_TYPE_DOUBLE, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 0), + .value.int_value = 1 + }; + AVS_UNIT_ASSERT_EQUAL(sdm_write_entry(&dm, &record), + SDM_ERR_METHOD_NOT_ALLOWED); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), SDM_ERR_METHOD_NOT_ALLOWED); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 2); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 2); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_create, 1); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_FAILURE); +} + +AVS_UNIT_TEST(sdm_create, callback_error) { + TEST_INIT(dm, obj); + + fluf_uri_path_t path = FLUF_MAKE_OBJECT_PATH(1); + inst_create_return_eror = true; + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_CREATE, false, &path)); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), -1); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_create, 1); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_FAILURE); + inst_create_return_eror = false; +} + +AVS_UNIT_TEST(sdm_create, error_no_space) { + TEST_INIT(dm, obj); + + obj.max_inst_count = 3; + fluf_uri_path_t path = FLUF_MAKE_OBJECT_PATH(1); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_CREATE, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + AVS_UNIT_ASSERT_EQUAL(call_iid, 0); + + path = FLUF_MAKE_OBJECT_PATH(1); + AVS_UNIT_ASSERT_EQUAL( + sdm_operation_begin(&dm, FLUF_OP_DM_CREATE, false, &path), + SDM_ERR_MEMORY); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 2); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_create, 1); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_MODIFIED); +} diff --git a/tests/anj/sdm/sdm_delete.c b/tests/anj/sdm/sdm_delete.c new file mode 100644 index 00000000..957423c0 --- /dev/null +++ b/tests/anj/sdm/sdm_delete.c @@ -0,0 +1,619 @@ +/* + * Copyright 2023-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 + +static int call_counter_begin; +static int call_counter_end; +static int call_counter_validate; +static int call_counter_delete; +static int call_counter_res_delete; +static sdm_obj_inst_t *call_obj_inst; +static sdm_res_inst_t *call_res_inst; +static sdm_res_t *call_res; +static bool inst_delete_return_eror; +static bool inst_operation_end_return_eror; +static bool res_inst_operation_return_eror; +static sdm_op_result_t call_result; + +static int res_inst_delete(sdm_obj_t *obj, + sdm_obj_inst_t *obj_inst, + sdm_res_t *res, + sdm_res_inst_t *res_inst) { + (void) obj; + (void) obj_inst; + call_res = res; + call_res_inst = res_inst; + call_counter_res_delete++; + if (res_inst_operation_return_eror) { + return -1; + } + return 0; +} + +static int operation_begin(sdm_obj_t *obj, fluf_op_t operation) { + (void) obj; + (void) operation; + call_counter_begin++; + return 0; +} + +static int operation_end(sdm_obj_t *obj, sdm_op_result_t result) { + (void) obj; + call_result = result; + call_counter_end++; + if (inst_operation_end_return_eror) { + return -22; + } + return 0; +} + +static int operation_validate(sdm_obj_t *obj) { + (void) obj; + call_counter_validate++; + return 0; +} + +static int inst_delete(sdm_obj_t *obj, sdm_obj_inst_t *obj_inst) { + (void) obj; + call_counter_delete++; + call_obj_inst = obj_inst; + if (inst_delete_return_eror) { + return -1; + } + return 0; +} + +#define TEST_INIT(Dm, Obj) \ + sdm_res_handlers_t res_handlers = { \ + .res_inst_delete = res_inst_delete \ + }; \ + sdm_res_spec_t res_spec_0 = { \ + .rid = 0, \ + .operation = SDM_RES_R, \ + .type = FLUF_DATA_TYPE_INT \ + }; \ + sdm_res_spec_t res_spec_1 = { \ + .rid = 1, \ + .operation = SDM_RES_RW, \ + .type = FLUF_DATA_TYPE_INT \ + }; \ + sdm_res_spec_t res_spec_2 = { \ + .rid = 2, \ + .operation = SDM_RES_BS_RW, \ + .type = FLUF_DATA_TYPE_INT \ + }; \ + sdm_res_spec_t res_spec_3 = { \ + .rid = 3, \ + .operation = SDM_RES_RM, \ + .type = FLUF_DATA_TYPE_INT \ + }; \ + sdm_res_spec_t res_spec_4 = { \ + .rid = 4, \ + .operation = SDM_RES_RM, \ + .type = FLUF_DATA_TYPE_INT \ + }; \ + sdm_res_spec_t res_spec_5 = { \ + .rid = 5, \ + .operation = SDM_RES_RM, \ + .type = FLUF_DATA_TYPE_INT \ + }; \ + sdm_res_spec_t res_spec_write = { \ + .rid = 6, \ + .operation = SDM_RES_W, \ + .type = FLUF_DATA_TYPE_INT \ + }; \ + sdm_res_t res_0[] = { \ + { \ + .res_spec = &res_spec_0, \ + .res_handlers = &res_handlers \ + }, \ + { \ + .res_spec = &res_spec_write, \ + .res_handlers = &res_handlers \ + } \ + }; \ + sdm_res_inst_t res_inst_0 = { \ + .riid = 0, \ + .res_value.value.int_value = 33 \ + }; \ + sdm_res_inst_t res_inst_1 = { \ + .riid = 1, \ + .res_value.value.int_value = 44 \ + }; \ + sdm_res_inst_t res_inst_2 = { \ + .riid = 2, \ + .res_value.value.int_value = 44 \ + }; \ + sdm_res_inst_t *res_insts[9] = { &res_inst_0, &res_inst_1, &res_inst_2 }; \ + sdm_res_inst_t *res_insts_2[9] = { &res_inst_0 }; \ + sdm_res_t res_1[] = { \ + { \ + .res_spec = &res_spec_0, \ + .res_handlers = &res_handlers \ + }, \ + { \ + .res_spec = &res_spec_1, \ + .value.res_value.value.int_value = 17 \ + }, \ + { \ + .res_spec = &res_spec_2, \ + .value.res_value.value.int_value = 18 \ + }, \ + { \ + .res_spec = &res_spec_3, \ + .value.res_inst.max_inst_count = 0, \ + .value.res_inst.inst_count = 0 \ + }, \ + { \ + .res_spec = &res_spec_4, \ + .value.res_inst.max_inst_count = 9, \ + .value.res_inst.inst_count = 3, \ + .value.res_inst.insts = res_insts, \ + .res_handlers = &res_handlers \ + }, \ + { \ + .res_spec = &res_spec_5, \ + .res_handlers = &res_handlers, \ + .value.res_inst.max_inst_count = 9, \ + .value.res_inst.inst_count = 1, \ + .value.res_inst.insts = res_insts_2 \ + } \ + }; \ + sdm_obj_inst_t obj_inst_0 = { \ + .iid = 0, \ + .res_count = 2, \ + .resources = res_0 \ + }; \ + sdm_obj_inst_t obj_inst_1 = { \ + .iid = 1, \ + .res_count = 6, \ + .resources = res_1 \ + }; \ + sdm_obj_inst_t obj_inst_2 = { \ + .iid = 2, \ + .res_count = 0 \ + }; \ + sdm_obj_inst_t *obj_insts[3] = { &obj_inst_0, &obj_inst_1, &obj_inst_2 }; \ + sdm_obj_handlers_t handlers = { \ + .operation_begin = operation_begin, \ + .operation_end = operation_end, \ + .operation_validate = operation_validate, \ + .inst_delete = inst_delete \ + }; \ + sdm_obj_t Obj = { \ + .oid = 1, \ + .insts = obj_insts, \ + .inst_count = 3, \ + .obj_handlers = &handlers, \ + .max_inst_count = 3 \ + }; \ + sdm_data_model_t Dm; \ + sdm_obj_t *Objs[1]; \ + sdm_initialize(&Dm, Objs, 1); \ + AVS_UNIT_ASSERT_SUCCESS(sdm_add_obj(&Dm, &Obj)); \ + call_counter_begin = 0; \ + call_counter_end = 0; \ + call_counter_validate = 0; \ + call_counter_delete = 0; \ + call_counter_res_delete = 0; \ + call_obj_inst = NULL; \ + call_res = NULL; \ + call_result = 4; \ + call_res_inst = NULL; + +AVS_UNIT_TEST(sdm_delete, delete_last) { + TEST_INIT(dm, obj); + fluf_uri_path_t path = FLUF_MAKE_INSTANCE_PATH(1, 2); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_DELETE, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + AVS_UNIT_ASSERT_EQUAL(obj.inst_count, 2); + AVS_UNIT_ASSERT_EQUAL(obj.insts[0]->iid, 0); + AVS_UNIT_ASSERT_EQUAL(obj.insts[1]->iid, 1); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_delete, 1); + AVS_UNIT_ASSERT_TRUE(call_obj_inst == &obj_inst_2); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_MODIFIED); +} + +AVS_UNIT_TEST(sdm_delete, delete_first) { + TEST_INIT(dm, obj); + fluf_uri_path_t path = FLUF_MAKE_INSTANCE_PATH(1, 0); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_DELETE, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + AVS_UNIT_ASSERT_EQUAL(obj.inst_count, 2); + AVS_UNIT_ASSERT_EQUAL(obj.insts[0]->iid, 1); + AVS_UNIT_ASSERT_EQUAL(obj.insts[1]->iid, 2); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_delete, 1); + AVS_UNIT_ASSERT_TRUE(call_obj_inst == &obj_inst_0); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_MODIFIED); +} + +AVS_UNIT_TEST(sdm_delete, delete_middle) { + TEST_INIT(dm, obj); + fluf_uri_path_t path = FLUF_MAKE_INSTANCE_PATH(1, 1); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_DELETE, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + AVS_UNIT_ASSERT_EQUAL(obj.inst_count, 2); + AVS_UNIT_ASSERT_EQUAL(obj.insts[0]->iid, 0); + AVS_UNIT_ASSERT_EQUAL(obj.insts[1]->iid, 2); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_delete, 1); + AVS_UNIT_ASSERT_TRUE(call_obj_inst == &obj_inst_1); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_MODIFIED); +} + +AVS_UNIT_TEST(sdm_delete, delete_all) { + TEST_INIT(dm, obj); + fluf_uri_path_t path = FLUF_MAKE_INSTANCE_PATH(1, 1); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_DELETE, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + AVS_UNIT_ASSERT_EQUAL(obj.inst_count, 2); + AVS_UNIT_ASSERT_EQUAL(obj.insts[0]->iid, 0); + AVS_UNIT_ASSERT_EQUAL(obj.insts[1]->iid, 2); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_delete, 1); + AVS_UNIT_ASSERT_TRUE(call_obj_inst == &obj_inst_1); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_MODIFIED); + + path = FLUF_MAKE_INSTANCE_PATH(1, 2); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_DELETE, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + AVS_UNIT_ASSERT_EQUAL(obj.inst_count, 1); + AVS_UNIT_ASSERT_EQUAL(obj.insts[0]->iid, 0); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 2); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 2); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 2); + AVS_UNIT_ASSERT_EQUAL(call_counter_delete, 2); + AVS_UNIT_ASSERT_TRUE(call_obj_inst == &obj_inst_2); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_MODIFIED); + + path = FLUF_MAKE_INSTANCE_PATH(1, 0); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_DELETE, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + AVS_UNIT_ASSERT_EQUAL(obj.inst_count, 0); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 3); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 3); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 3); + AVS_UNIT_ASSERT_EQUAL(call_counter_delete, 3); + AVS_UNIT_ASSERT_TRUE(call_obj_inst == &obj_inst_0); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_MODIFIED); +} + +AVS_UNIT_TEST(sdm_delete, delete_error_no_exist) { + TEST_INIT(dm, obj); + fluf_uri_path_t path = FLUF_MAKE_INSTANCE_PATH(1, 4); + AVS_UNIT_ASSERT_EQUAL( + sdm_operation_begin(&dm, FLUF_OP_DM_DELETE, false, &path), + SDM_ERR_NOT_FOUND); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), SDM_ERR_NOT_FOUND); + AVS_UNIT_ASSERT_EQUAL(obj.inst_count, 3); + AVS_UNIT_ASSERT_EQUAL(obj.insts[0]->iid, 0); + AVS_UNIT_ASSERT_EQUAL(obj.insts[1]->iid, 1); + AVS_UNIT_ASSERT_EQUAL(obj.insts[2]->iid, 2); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_delete, 0); +} + +AVS_UNIT_TEST(sdm_delete, delete_error_removed) { + TEST_INIT(dm, obj); + fluf_uri_path_t path = FLUF_MAKE_INSTANCE_PATH(1, 1); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_DELETE, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + AVS_UNIT_ASSERT_EQUAL(obj.inst_count, 2); + AVS_UNIT_ASSERT_EQUAL(obj.insts[0]->iid, 0); + AVS_UNIT_ASSERT_EQUAL(obj.insts[1]->iid, 2); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_delete, 1); + AVS_UNIT_ASSERT_TRUE(call_obj_inst == &obj_inst_1); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_MODIFIED); + + AVS_UNIT_ASSERT_EQUAL( + sdm_operation_begin(&dm, FLUF_OP_DM_DELETE, false, &path), + SDM_ERR_NOT_FOUND); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), SDM_ERR_NOT_FOUND); + AVS_UNIT_ASSERT_EQUAL(obj.inst_count, 2); + AVS_UNIT_ASSERT_EQUAL(obj.insts[0]->iid, 0); + AVS_UNIT_ASSERT_EQUAL(obj.insts[1]->iid, 2); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 2); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 2); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_delete, 1); + AVS_UNIT_ASSERT_TRUE(call_obj_inst == &obj_inst_1); +} + +AVS_UNIT_TEST(sdm_delete, delete_error_no_callback) { + TEST_INIT(dm, obj); + obj.obj_handlers = NULL; + fluf_uri_path_t path = FLUF_MAKE_INSTANCE_PATH(1, 0); + AVS_UNIT_ASSERT_EQUAL( + sdm_operation_begin(&dm, FLUF_OP_DM_DELETE, false, &path), + SDM_ERR_INTERNAL); + AVS_UNIT_ASSERT_EQUAL(obj.inst_count, 3); + AVS_UNIT_ASSERT_EQUAL(obj.insts[0]->iid, 0); + AVS_UNIT_ASSERT_EQUAL(obj.insts[1]->iid, 1); + AVS_UNIT_ASSERT_EQUAL(obj.insts[2]->iid, 2); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_delete, 0); +} + +AVS_UNIT_TEST(sdm_delete, delete_error_callback_error_1) { + TEST_INIT(dm, obj); + inst_delete_return_eror = true; + call_result = true; + + fluf_uri_path_t path = FLUF_MAKE_INSTANCE_PATH(1, 0); + AVS_UNIT_ASSERT_EQUAL( + sdm_operation_begin(&dm, FLUF_OP_DM_DELETE, false, &path), -1); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), -1); + AVS_UNIT_ASSERT_EQUAL(obj.inst_count, 3); + AVS_UNIT_ASSERT_EQUAL(obj.insts[0]->iid, 0); + AVS_UNIT_ASSERT_EQUAL(obj.insts[1]->iid, 1); + AVS_UNIT_ASSERT_EQUAL(obj.insts[2]->iid, 2); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_delete, 1); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_FAILURE); + inst_delete_return_eror = false; +} + +AVS_UNIT_TEST(sdm_delete, delete_error_callback_error_2) { + TEST_INIT(dm, obj); + inst_operation_end_return_eror = true; + call_result = 4; + + fluf_uri_path_t path = FLUF_MAKE_INSTANCE_PATH(1, 0); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_DELETE, false, &path)); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), -22); + AVS_UNIT_ASSERT_EQUAL(obj.inst_count, 2); + AVS_UNIT_ASSERT_EQUAL(obj.insts[0]->iid, 1); + AVS_UNIT_ASSERT_EQUAL(obj.insts[1]->iid, 2); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_delete, 1); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_MODIFIED); + inst_operation_end_return_eror = false; +} + +#ifdef FLUF_WITH_LWM2M12 +AVS_UNIT_TEST(sdm_delete, delete_res_last) { + TEST_INIT(dm, obj); + sdm_res_t *res = &res_1[4]; + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 4, 2); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_DELETE, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + AVS_UNIT_ASSERT_EQUAL(obj.inst_count, 3); + AVS_UNIT_ASSERT_EQUAL(obj.insts[0]->iid, 0); + AVS_UNIT_ASSERT_EQUAL(obj.insts[1]->iid, 1); + AVS_UNIT_ASSERT_EQUAL(obj.insts[2]->iid, 2); + + AVS_UNIT_ASSERT_EQUAL(res->value.res_inst.inst_count, 2); + AVS_UNIT_ASSERT_EQUAL(res->value.res_inst.insts[0]->riid, 0); + AVS_UNIT_ASSERT_EQUAL(res->value.res_inst.insts[1]->riid, 1); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_delete, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_delete, 1); + AVS_UNIT_ASSERT_TRUE(call_res == res); + AVS_UNIT_ASSERT_TRUE(call_res_inst == &res_inst_2); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_MODIFIED); +} + +AVS_UNIT_TEST(sdm_delete, delete_res_first) { + TEST_INIT(dm, obj); + sdm_res_t *res = &res_1[4]; + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 4, 0); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_DELETE, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + + AVS_UNIT_ASSERT_EQUAL(res->value.res_inst.inst_count, 2); + AVS_UNIT_ASSERT_EQUAL(res->value.res_inst.insts[0]->riid, 1); + AVS_UNIT_ASSERT_EQUAL(res->value.res_inst.insts[1]->riid, 2); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_delete, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_delete, 1); + AVS_UNIT_ASSERT_TRUE(call_res == res); + AVS_UNIT_ASSERT_TRUE(call_res_inst == &res_inst_0); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_MODIFIED); +} + +AVS_UNIT_TEST(sdm_delete, delete_res_middle) { + TEST_INIT(dm, obj); + sdm_res_t *res = &res_1[4]; + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 4, 1); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_DELETE, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + + AVS_UNIT_ASSERT_EQUAL(res->value.res_inst.inst_count, 2); + AVS_UNIT_ASSERT_EQUAL(res->value.res_inst.insts[0]->riid, 0); + AVS_UNIT_ASSERT_EQUAL(res->value.res_inst.insts[1]->riid, 2); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_delete, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_delete, 1); + AVS_UNIT_ASSERT_TRUE(call_res == res); + AVS_UNIT_ASSERT_TRUE(call_res_inst == &res_inst_1); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_MODIFIED); +} + +AVS_UNIT_TEST(sdm_delete, delete_res_all) { + TEST_INIT(dm, obj); + sdm_res_t *res = &res_1[4]; + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 4, 1); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_DELETE, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + + AVS_UNIT_ASSERT_EQUAL(res->value.res_inst.inst_count, 2); + AVS_UNIT_ASSERT_EQUAL(res->value.res_inst.insts[0]->riid, 0); + AVS_UNIT_ASSERT_EQUAL(res->value.res_inst.insts[1]->riid, 2); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_delete, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_delete, 1); + AVS_UNIT_ASSERT_TRUE(call_res == res); + AVS_UNIT_ASSERT_TRUE(call_res_inst == &res_inst_1); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_MODIFIED); + + path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 4, 0); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_DELETE, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + + AVS_UNIT_ASSERT_EQUAL(res->value.res_inst.inst_count, 1); + AVS_UNIT_ASSERT_EQUAL(res->value.res_inst.insts[0]->riid, 2); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 2); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 2); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 2); + AVS_UNIT_ASSERT_EQUAL(call_counter_delete, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_delete, 2); + AVS_UNIT_ASSERT_TRUE(call_res == res); + AVS_UNIT_ASSERT_TRUE(call_res_inst == &res_inst_0); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_MODIFIED); + + path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 4, 2); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_DELETE, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + + AVS_UNIT_ASSERT_EQUAL(res->value.res_inst.inst_count, 0); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 3); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 3); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 3); + AVS_UNIT_ASSERT_EQUAL(call_counter_delete, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_delete, 3); + AVS_UNIT_ASSERT_TRUE(call_res == res); + AVS_UNIT_ASSERT_TRUE(call_res_inst == &res_inst_2); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_MODIFIED); +} + +AVS_UNIT_TEST(sdm_delete, delete_res_error_path) { + TEST_INIT(dm, obj); + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 1, 1); + AVS_UNIT_ASSERT_EQUAL( + sdm_operation_begin(&dm, FLUF_OP_DM_DELETE, false, &path), + SDM_ERR_NOT_FOUND); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_delete, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_delete, 0); +} + +AVS_UNIT_TEST(sdm_delete, delete_res_error_no_instances) { + TEST_INIT(dm, obj); + sdm_res_t *res = &res_1[5]; + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 5, 0); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_DELETE, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + + AVS_UNIT_ASSERT_EQUAL(res->value.res_inst.inst_count, 0); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_delete, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_delete, 1); + AVS_UNIT_ASSERT_TRUE(call_res == res); + AVS_UNIT_ASSERT_TRUE(call_res_inst == &res_inst_0); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_MODIFIED); + + path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 5, 0); + AVS_UNIT_ASSERT_EQUAL( + sdm_operation_begin(&dm, FLUF_OP_DM_DELETE, false, &path), + SDM_ERR_NOT_FOUND); +} + +AVS_UNIT_TEST(sdm_delete, delete_res_error_callback) { + TEST_INIT(dm, obj); + res_inst_operation_return_eror = true; + sdm_res_t *res = &res_1[5]; + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 5, 0); + AVS_UNIT_ASSERT_EQUAL( + sdm_operation_begin(&dm, FLUF_OP_DM_DELETE, false, &path), -1); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), -1); + + AVS_UNIT_ASSERT_EQUAL(res->value.res_inst.inst_count, 1); + AVS_UNIT_ASSERT_EQUAL(res->value.res_inst.insts[0]->riid, 0); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_delete, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_delete, 1); + AVS_UNIT_ASSERT_TRUE(call_res == res); + AVS_UNIT_ASSERT_TRUE(call_res_inst == &res_inst_0); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_FAILURE); + res_inst_operation_return_eror = false; +} +#endif // FLUF_WITH_LWM2M12 diff --git a/tests/anj/sdm/sdm_discover.c b/tests/anj/sdm/sdm_discover.c new file mode 100644 index 00000000..df68d633 --- /dev/null +++ b/tests/anj/sdm/sdm_discover.c @@ -0,0 +1,236 @@ +/* + * Copyright 2023-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 + +static sdm_obj_t obj_0 = { + .oid = 0 +}; + +static const sdm_res_spec_t res_spec_0 = { + .rid = 0, + .operation = SDM_RES_R, + .type = FLUF_DATA_TYPE_INT +}; +static const sdm_res_spec_t res_spec_1 = { + .rid = 1, + .operation = SDM_RES_W, + .type = FLUF_DATA_TYPE_INT +}; +static const sdm_res_spec_t res_spec_2 = { + .rid = 2, + .operation = SDM_RES_RWM, + .type = FLUF_DATA_TYPE_INT +}; +static const sdm_res_spec_t res_spec_3 = { + .rid = 3, + .operation = SDM_RES_WM, + .type = FLUF_DATA_TYPE_INT +}; +static const sdm_res_spec_t res_spec_4 = { + .rid = 4, + .operation = SDM_RES_R, + .type = FLUF_DATA_TYPE_INT +}; +static sdm_res_t inst_1_res[] = { + { + .res_spec = &res_spec_0 + }, + { + .res_spec = &res_spec_1 + } +}; +static sdm_res_inst_t res_inst_1 = { + .riid = 1 +}; +static sdm_res_inst_t res_inst_2 = { + .riid = 2 +}; +static sdm_res_inst_t *res_insts[] = { &res_inst_1, &res_inst_2 }; +static sdm_res_t inst_2_res[] = { + { + .res_spec = &res_spec_0 + }, + { + .res_spec = &res_spec_1 + }, + { + .res_spec = &res_spec_2, + .value.res_inst.inst_count = 2, + .value.res_inst.max_inst_count = 2, + .value.res_inst.insts = res_insts + }, + { + .res_spec = &res_spec_3, + .value.res_inst.inst_count = 0 + }, + { + .res_spec = &res_spec_4 + } +}; +#define OBJ_1_INST_MAX_COUNT 3 +static sdm_obj_inst_t obj_1_inst_1 = { + .iid = 1, + .res_count = 2, + .resources = inst_1_res +}; +static sdm_obj_inst_t obj_1_inst_2 = { + .iid = 2, + .res_count = 5, + .resources = inst_2_res +}; +static sdm_obj_inst_t *obj_1_insts[OBJ_1_INST_MAX_COUNT] = { &obj_1_inst_1, + &obj_1_inst_2 }; +static sdm_obj_t obj_1 = { + .oid = 1, + .version = "1.1", + .insts = obj_1_insts, + .inst_count = 2, + .max_inst_count = OBJ_1_INST_MAX_COUNT +}; +static sdm_obj_inst_t obj_3_inst_1 = { + .iid = 0 +}; +static sdm_obj_inst_t *obj_3_insts[1] = { &obj_3_inst_1 }; +static sdm_obj_t obj_3 = { + .oid = 3, + .insts = obj_3_insts, + .inst_count = 1, + .max_inst_count = 1 +}; +static sdm_obj_t obj_5 = { + .oid = 5 +}; +static sdm_obj_t obj_55 = { + .oid = 55, + .version = "1.2" +}; + +typedef struct { + fluf_uri_path_t path; + const uint16_t *dim; + const char *version; +} discover_record_t; + +#define DISCOVER_TEST(Path, Idx_start, Idx_end) \ + sdm_data_model_t dm; \ + sdm_obj_t *objs[5]; \ + sdm_initialize(&dm, objs, 5); \ + AVS_UNIT_ASSERT_SUCCESS(sdm_add_obj(&dm, &obj_0)); \ + AVS_UNIT_ASSERT_SUCCESS(sdm_add_obj(&dm, &obj_1)); \ + AVS_UNIT_ASSERT_SUCCESS(sdm_add_obj(&dm, &obj_3)); \ + AVS_UNIT_ASSERT_SUCCESS(sdm_add_obj(&dm, &obj_5)); \ + AVS_UNIT_ASSERT_SUCCESS(sdm_add_obj(&dm, &obj_55)); \ + AVS_UNIT_ASSERT_SUCCESS( \ + sdm_operation_begin(&dm, FLUF_OP_DM_DISCOVER, false, &Path)); \ + for (size_t idx = Idx_start; idx <= Idx_end; idx++) { \ + fluf_uri_path_t out_path; \ + const char *out_version; \ + const uint16_t *out_dim; \ + int res = sdm_get_discover_record(&dm, &out_path, &out_version, \ + &out_dim); \ + AVS_UNIT_ASSERT_TRUE( \ + fluf_uri_path_equal(&out_path, &disc_records[idx].path)); \ + if (disc_records[idx].version) { \ + AVS_UNIT_ASSERT_FALSE( \ + strcmp(out_version, disc_records[idx].version)); \ + } else { \ + AVS_UNIT_ASSERT_NULL(out_version); \ + } \ + AVS_UNIT_ASSERT_TRUE(out_dim == disc_records[idx].dim); \ + if (idx == Idx_end) { \ + AVS_UNIT_ASSERT_EQUAL(res, SDM_LAST_RECORD); \ + } else { \ + AVS_UNIT_ASSERT_EQUAL(res, 0); \ + } \ + } \ + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + +/** + * Object 1: + * 1: version = "1.1" + * 1 + * 0 + * 1 + * 2 + * 0 + * 1 + * 2: dim = 2 + * 1 + * 2 + * 3: dim = 0 + * 4 + */ +// clang-format off +static discover_record_t disc_records [12] = { + {.path = FLUF_MAKE_OBJECT_PATH(1), .version = "1.1"}, + {.path = FLUF_MAKE_INSTANCE_PATH(1,1)}, + {.path = FLUF_MAKE_RESOURCE_PATH(1,1,0)}, + {.path = FLUF_MAKE_RESOURCE_PATH(1,1,1)}, + {.path = FLUF_MAKE_INSTANCE_PATH(1,2)}, + {.path = FLUF_MAKE_RESOURCE_PATH(1,2,0)}, + {.path = FLUF_MAKE_RESOURCE_PATH(1,2,1)}, + {.path = FLUF_MAKE_RESOURCE_PATH(1,2,2), .dim = &inst_2_res[2].value.res_inst.inst_count}, + {.path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1,2,2,1)}, + {.path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1,2,2,2)}, + {.path = FLUF_MAKE_RESOURCE_PATH(1,2,3), .dim = &inst_2_res[3].value.res_inst.inst_count}, + {.path = FLUF_MAKE_RESOURCE_PATH(1,2,4)} +}; +// clang-format on +AVS_UNIT_TEST(sdm_discover, discover_operation_object) { + fluf_uri_path_t path = FLUF_MAKE_OBJECT_PATH(1); + DISCOVER_TEST(path, 0, 11); +} +AVS_UNIT_TEST(sdm_discover, discover_operation_inst_1) { + fluf_uri_path_t path = FLUF_MAKE_INSTANCE_PATH(1, 1); + DISCOVER_TEST(path, 1, 3); +} +AVS_UNIT_TEST(sdm_discover, discover_operation_inst_2) { + fluf_uri_path_t path = FLUF_MAKE_INSTANCE_PATH(1, 2); + DISCOVER_TEST(path, 4, 11); +} +AVS_UNIT_TEST(sdm_discover, discover_operation_inst_1_res_0) { + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_PATH(1, 1, 0); + DISCOVER_TEST(path, 2, 2); +} +AVS_UNIT_TEST(sdm_discover, discover_operation_inst_1_res_1) { + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_PATH(1, 1, 1); + DISCOVER_TEST(path, 3, 3); +} +AVS_UNIT_TEST(sdm_discover, discover_operation_inst_2_res_0) { + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_PATH(1, 2, 0); + DISCOVER_TEST(path, 5, 5); +} +AVS_UNIT_TEST(sdm_discover, discover_operation_inst_2_res_1) { + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_PATH(1, 2, 1); + DISCOVER_TEST(path, 6, 6); +} +AVS_UNIT_TEST(sdm_discover, discover_operation_inst_2_res_2) { + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_PATH(1, 2, 2); + DISCOVER_TEST(path, 7, 9); +} +AVS_UNIT_TEST(sdm_discover, discover_operation_inst_2_res_3) { + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_PATH(1, 2, 3); + DISCOVER_TEST(path, 10, 10); +} +AVS_UNIT_TEST(sdm_discover, discover_operation_inst_2_res_4) { + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_PATH(1, 2, 4); + DISCOVER_TEST(path, 11, 11); +} diff --git a/tests/anj/sdm/sdm_execute.c b/tests/anj/sdm/sdm_execute.c new file mode 100644 index 00000000..0355471e --- /dev/null +++ b/tests/anj/sdm/sdm_execute.c @@ -0,0 +1,166 @@ +/* + * Copyright 2023-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 + +static sdm_obj_t *call_obj; +static sdm_obj_inst_t *call_obj_inst; +static sdm_res_t *call_res; +static size_t call_execute_arg_len; +static const char *call_execute_arg; +static fluf_op_t call_operation; +static int call_counter_execute; +static int call_counter_begin; +static int call_counter_end; +static sdm_op_result_t call_result; + +static int operation_begin(sdm_obj_t *obj, fluf_op_t operation) { + call_obj = obj; + call_operation = operation; + call_counter_begin++; + return 0; +} +static int operation_end(sdm_obj_t *obj, sdm_op_result_t result) { + call_obj = obj; + call_counter_end++; + call_result = result; + return 0; +} + +static int res_execute(sdm_obj_t *obj, + sdm_obj_inst_t *obj_inst, + sdm_res_t *res, + const char *execute_arg, + size_t execute_arg_len) { + call_obj = obj; + call_obj_inst = obj_inst; + call_res = res; + call_execute_arg_len = execute_arg_len; + call_execute_arg = execute_arg; + call_counter_execute++; + return 0; +} + +static sdm_res_handlers_t res_handlers = { + .res_execute = res_execute +}; +static const sdm_res_spec_t res_spec_0 = { + .rid = 0, + .operation = SDM_RES_E +}; +static const sdm_res_spec_t res_spec_1 = { + .rid = 1, + .operation = SDM_RES_W, + .type = FLUF_DATA_TYPE_INT +}; +static sdm_res_t res[] = { + { + .res_spec = &res_spec_0, + .res_handlers = &res_handlers + }, + { + .res_spec = &res_spec_1 + } +}; +static sdm_obj_inst_t obj_inst = { + .iid = 1, + .res_count = 2, + .resources = res +}; +static sdm_obj_inst_t *obj_insts[1] = { &obj_inst }; +static sdm_obj_handlers_t handlers = { + .operation_begin = operation_begin, + .operation_end = operation_end +}; +static sdm_obj_t obj = { + .oid = 1, + .insts = obj_insts, + .inst_count = 1, + .obj_handlers = &handlers, + .max_inst_count = 1 +}; + +AVS_UNIT_TEST(sdm_execute, base) { + sdm_data_model_t dm; + sdm_obj_t *objs[1]; + sdm_initialize(&dm, objs, 1); + + AVS_UNIT_ASSERT_SUCCESS(sdm_add_obj(&dm, &obj)); + + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_begin( + &dm, FLUF_OP_DM_EXECUTE, false, &FLUF_MAKE_RESOURCE_PATH(1, 1, 0))); + + AVS_UNIT_ASSERT_EQUAL(call_counter_execute, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 0); + AVS_UNIT_ASSERT_TRUE(call_obj == &obj); + AVS_UNIT_ASSERT_EQUAL(call_operation, FLUF_OP_DM_EXECUTE); + + const char *test_arg = "ddd"; + AVS_UNIT_ASSERT_SUCCESS(sdm_execute(&dm, test_arg, sizeof(test_arg))); + AVS_UNIT_ASSERT_EQUAL(call_counter_execute, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 0); + AVS_UNIT_ASSERT_TRUE(call_obj == &obj); + AVS_UNIT_ASSERT_TRUE(call_obj_inst == &obj_inst); + AVS_UNIT_ASSERT_TRUE(call_res == res); + AVS_UNIT_ASSERT_TRUE(call_execute_arg == test_arg); + AVS_UNIT_ASSERT_EQUAL(call_execute_arg_len, sizeof(test_arg)); + + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + AVS_UNIT_ASSERT_EQUAL(call_counter_execute, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_NOT_MODIFIED); +} + +AVS_UNIT_TEST(sdm_execute, error_calls) { + sdm_data_model_t dm; + sdm_obj_t *objs[1]; + sdm_initialize(&dm, objs, 1); + + AVS_UNIT_ASSERT_SUCCESS(sdm_add_obj(&dm, &obj)); + + AVS_UNIT_ASSERT_EQUAL( + sdm_operation_begin(&dm, + FLUF_OP_DM_EXECUTE, + false, + &FLUF_MAKE_RESOURCE_PATH(1, 1, 1)), + SDM_ERR_METHOD_NOT_ALLOWED); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), SDM_ERR_METHOD_NOT_ALLOWED); + AVS_UNIT_ASSERT_EQUAL( + sdm_operation_begin(&dm, + FLUF_OP_DM_EXECUTE, + false, + &FLUF_MAKE_RESOURCE_PATH(1, 2, 1)), + SDM_ERR_NOT_FOUND); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), SDM_ERR_NOT_FOUND); + AVS_UNIT_ASSERT_EQUAL( + sdm_operation_begin(&dm, + FLUF_OP_DM_EXECUTE, + false, + &FLUF_MAKE_RESOURCE_PATH(2, 2, 1)), + SDM_ERR_NOT_FOUND); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), SDM_ERR_NOT_FOUND); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_begin( + &dm, FLUF_OP_DM_EXECUTE, false, &FLUF_MAKE_RESOURCE_PATH(1, 1, 0))); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), SDM_ERR_LOGIC); +} diff --git a/tests/anj/sdm/sdm_read.c b/tests/anj/sdm/sdm_read.c new file mode 100644 index 00000000..b66b6a4e --- /dev/null +++ b/tests/anj/sdm/sdm_read.c @@ -0,0 +1,500 @@ +/* + * Copyright 2023-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 "../../../src/anj/sdm/sdm_core.h" + +static sdm_obj_t *call_obj; +static sdm_obj_inst_t *call_obj_inst; +static sdm_res_t *call_res; +static sdm_res_inst_t *call_res_inst; +static fluf_op_t call_operation; +static int call_counter_read; +static int call_counter_begin; +static int call_counter_end; +static fluf_res_value_t callback_value; +static sdm_op_result_t call_result; + +static int operation_begin(sdm_obj_t *obj, fluf_op_t operation) { + call_obj = obj; + call_operation = operation; + call_counter_begin++; + return 0; +} +static int operation_end(sdm_obj_t *obj, sdm_op_result_t result) { + call_obj = obj; + call_result = result; + call_counter_end++; + return 0; +} + +static int res_read(sdm_obj_t *obj, + sdm_obj_inst_t *obj_inst, + sdm_res_t *res, + sdm_res_inst_t *res_inst, + fluf_res_value_t *out_value) { + call_obj = obj; + call_obj_inst = obj_inst; + call_res = res; + call_res_inst = res_inst; + call_counter_read++; + *out_value = callback_value; + return 0; +} + +static sdm_res_handlers_t res_handlers = { + .res_read = res_read +}; +static const sdm_res_spec_t res_spec_0 = { + .rid = 0, + .operation = SDM_RES_R, + .type = FLUF_DATA_TYPE_INT +}; +static const sdm_res_spec_t res_spec_1 = { + .rid = 1, + .operation = SDM_RES_RW, + .type = FLUF_DATA_TYPE_INT +}; +// bootstrap read +static const sdm_res_spec_t res_spec_2 = { + .rid = 2, + .operation = SDM_RES_BS_RW, + .type = FLUF_DATA_TYPE_INT +}; +// empty multi-instance +static const sdm_res_spec_t res_spec_3 = { + .rid = 3, + .operation = SDM_RES_RM, + .type = FLUF_DATA_TYPE_INT +}; +// mulit-instance +static const sdm_res_spec_t res_spec_4 = { + .rid = 4, + .operation = SDM_RES_RM, + .type = FLUF_DATA_TYPE_INT +}; +static const sdm_res_spec_t res_spec_5 = { + .rid = 5, + .operation = SDM_RES_RM, + .type = FLUF_DATA_TYPE_INT +}; +static const sdm_res_spec_t res_spec_write = { + .rid = 6, + .operation = SDM_RES_W, + .type = FLUF_DATA_TYPE_INT +}; +static sdm_res_t res_0[] = { + { + .res_spec = &res_spec_0, + .res_handlers = &res_handlers + }, + { + .res_spec = &res_spec_write, + .res_handlers = &res_handlers + } +}; +static sdm_res_inst_t res_inst_0 = { + .riid = 0, + .res_value.value.int_value = 33 +}; +static sdm_res_inst_t res_inst_1 = { + .riid = 1, + .res_value.value.int_value = 44 +}; +static sdm_res_inst_t *res_insts[9] = { &res_inst_0, &res_inst_1 }; +static sdm_res_inst_t *res_insts_2[9] = { &res_inst_0 }; +static sdm_res_t res_1[] = { + { + .res_spec = &res_spec_0, + .res_handlers = &res_handlers + }, + { + .res_spec = &res_spec_1, + .value.res_value.value.int_value = 17 + }, + { + .res_spec = &res_spec_2, + .value.res_value.value.int_value = 18 + }, + { + .res_spec = &res_spec_3, + .value.res_inst.max_inst_count = 0, + .value.res_inst.inst_count = 0 + }, + { + .res_spec = &res_spec_4, + .value.res_inst.max_inst_count = 9, + .value.res_inst.inst_count = 2, + .value.res_inst.insts = res_insts + }, + { + .res_spec = &res_spec_5, + .res_handlers = &res_handlers, + .value.res_inst.max_inst_count = 9, + .value.res_inst.inst_count = 1, + .value.res_inst.insts = res_insts_2 + } +}; +static sdm_obj_inst_t obj_inst_0 = { + .iid = 0, + .res_count = 2, + .resources = res_0 +}; +static sdm_obj_inst_t obj_inst_1 = { + .iid = 1, + .res_count = 6, + .resources = res_1 +}; +static sdm_obj_inst_t *obj_insts[2] = { &obj_inst_0, &obj_inst_1 }; +static sdm_obj_handlers_t handlers = { + .operation_begin = operation_begin, + .operation_end = operation_end +}; +static sdm_obj_t obj = { + .oid = 1, + .insts = obj_insts, + .inst_count = 2, + .obj_handlers = &handlers, + .max_inst_count = 2 +}; + +#define READ_INIT(Dm, Obj) \ + sdm_data_model_t Dm; \ + sdm_obj_t *Objs[1]; \ + sdm_initialize(&Dm, Objs, 1); \ + AVS_UNIT_ASSERT_SUCCESS(sdm_add_obj(&Dm, &Obj)); + +#define VERIFY_ENTRY(Out, Path, Value) \ + AVS_UNIT_ASSERT_TRUE(fluf_uri_path_equal(&Out.path, Path)); \ + AVS_UNIT_ASSERT_EQUAL(Out.value.int_value, Value); \ + AVS_UNIT_ASSERT_EQUAL(Out.type, FLUF_DATA_TYPE_INT); + +AVS_UNIT_TEST(sdm_read, read_res_instance) { + READ_INIT(dm, obj); + fluf_io_out_entry_t record = { 0 }; + size_t out_res_count = 0; + + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 4, 0); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_READ, false, &path)); + + AVS_UNIT_ASSERT_SUCCESS(sdm_get_readable_res_count(&dm, &out_res_count)); + AVS_UNIT_ASSERT_EQUAL(1, out_res_count); + AVS_UNIT_ASSERT_EQUAL(sdm_get_read_entry(&dm, &record), SDM_LAST_RECORD); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + VERIFY_ENTRY(record, &path, 33); + + path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 4, 1); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_READ, false, &path)); + + AVS_UNIT_ASSERT_SUCCESS(sdm_get_readable_res_count(&dm, &out_res_count)); + AVS_UNIT_ASSERT_EQUAL(1, out_res_count); + AVS_UNIT_ASSERT_EQUAL(sdm_get_read_entry(&dm, &record), SDM_LAST_RECORD); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + VERIFY_ENTRY(record, &path, 44); + + path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 5, 0); + callback_value.int_value = 222; + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_READ, false, &path)); + + AVS_UNIT_ASSERT_SUCCESS(sdm_get_readable_res_count(&dm, &out_res_count)); + AVS_UNIT_ASSERT_EQUAL(1, out_res_count); + AVS_UNIT_ASSERT_EQUAL(sdm_get_read_entry(&dm, &record), SDM_LAST_RECORD); + ; + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + VERIFY_ENTRY(record, &path, 222); + + AVS_UNIT_ASSERT_EQUAL(call_counter_read, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 3); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 3); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_NOT_MODIFIED); + AVS_UNIT_ASSERT_TRUE(call_obj == &obj); + AVS_UNIT_ASSERT_TRUE(call_obj_inst == &obj_inst_1); + AVS_UNIT_ASSERT_TRUE(call_res == &res_1[5]); + AVS_UNIT_ASSERT_TRUE(call_res_inst == &res_inst_0); +} + +AVS_UNIT_TEST(sdm_read, read_res_error) { + READ_INIT(dm, obj); + + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(2, 1, 4, 0); + AVS_UNIT_ASSERT_EQUAL( + sdm_operation_begin(&dm, FLUF_OP_DM_READ, false, &path), + SDM_ERR_NOT_FOUND); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), SDM_ERR_NOT_FOUND); + path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 2, 4, 0); + AVS_UNIT_ASSERT_EQUAL( + sdm_operation_begin(&dm, FLUF_OP_DM_READ, false, &path), + SDM_ERR_NOT_FOUND); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), SDM_ERR_NOT_FOUND); + path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 6, 0); + AVS_UNIT_ASSERT_EQUAL( + sdm_operation_begin(&dm, FLUF_OP_DM_READ, false, &path), + SDM_ERR_NOT_FOUND); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), SDM_ERR_NOT_FOUND); + path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 4, 4); + AVS_UNIT_ASSERT_EQUAL( + sdm_operation_begin(&dm, FLUF_OP_DM_READ, false, &path), + SDM_ERR_NOT_FOUND); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), SDM_ERR_NOT_FOUND); + path = FLUF_MAKE_RESOURCE_PATH(1, 1, 3); + AVS_UNIT_ASSERT_EQUAL( + sdm_operation_begin(&dm, FLUF_OP_DM_READ, false, &path), + SDM_ERR_NOT_FOUND); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), SDM_ERR_NOT_FOUND); + path = FLUF_MAKE_RESOURCE_PATH(1, 0, 6); + AVS_UNIT_ASSERT_EQUAL( + sdm_operation_begin(&dm, FLUF_OP_DM_READ, false, &path), + SDM_ERR_NOT_FOUND); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), SDM_ERR_NOT_FOUND); +} + +AVS_UNIT_TEST(sdm_read, read_res) { + READ_INIT(dm, obj); + fluf_io_out_entry_t record = { 0 }; + size_t out_res_count = 0; + + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_PATH(1, 1, 4); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_READ, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_get_readable_res_count(&dm, &out_res_count)); + AVS_UNIT_ASSERT_EQUAL(2, out_res_count); + AVS_UNIT_ASSERT_EQUAL(sdm_get_read_entry(&dm, &record), 0); + VERIFY_ENTRY(record, &FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 4, 0), 33); + AVS_UNIT_ASSERT_EQUAL(sdm_get_read_entry(&dm, &record), SDM_LAST_RECORD); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + VERIFY_ENTRY(record, &FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 4, 1), 44); + + callback_value.int_value = 45; + path = FLUF_MAKE_RESOURCE_PATH(1, 0, 0); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_READ, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_get_readable_res_count(&dm, &out_res_count)); + AVS_UNIT_ASSERT_EQUAL(1, out_res_count); + AVS_UNIT_ASSERT_EQUAL(sdm_get_read_entry(&dm, &record), SDM_LAST_RECORD); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + VERIFY_ENTRY(record, &path, 45); + + path = FLUF_MAKE_RESOURCE_PATH(1, 1, 1); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_READ, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_get_readable_res_count(&dm, &out_res_count)); + AVS_UNIT_ASSERT_EQUAL(1, out_res_count); + AVS_UNIT_ASSERT_EQUAL(sdm_get_read_entry(&dm, &record), SDM_LAST_RECORD); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + VERIFY_ENTRY(record, &path, 17); +} + +AVS_UNIT_TEST(sdm_read, read_inst) { + READ_INIT(dm, obj); + fluf_io_out_entry_t record = { 0 }; + size_t out_res_count = 0; + + callback_value.int_value = 999; + fluf_uri_path_t path = FLUF_MAKE_INSTANCE_PATH(1, 1); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_READ, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_get_readable_res_count(&dm, &out_res_count)); + AVS_UNIT_ASSERT_EQUAL(out_res_count, 5); + AVS_UNIT_ASSERT_EQUAL(sdm_get_read_entry(&dm, &record), 0); + VERIFY_ENTRY(record, &FLUF_MAKE_RESOURCE_PATH(1, 1, 0), 999); + AVS_UNIT_ASSERT_EQUAL(sdm_get_read_entry(&dm, &record), 0); + VERIFY_ENTRY(record, &FLUF_MAKE_RESOURCE_PATH(1, 1, 1), 17); + AVS_UNIT_ASSERT_EQUAL(sdm_get_read_entry(&dm, &record), 0); + VERIFY_ENTRY(record, &FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 4, 0), 33); + AVS_UNIT_ASSERT_EQUAL(sdm_get_read_entry(&dm, &record), 0); + VERIFY_ENTRY(record, &FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 4, 1), 44); + AVS_UNIT_ASSERT_EQUAL(sdm_get_read_entry(&dm, &record), SDM_LAST_RECORD); + VERIFY_ENTRY(record, &FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 5, 0), 999); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + + callback_value.int_value = 7; + path = FLUF_MAKE_INSTANCE_PATH(1, 0); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_READ, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_get_readable_res_count(&dm, &out_res_count)); + AVS_UNIT_ASSERT_EQUAL(out_res_count, 1); + AVS_UNIT_ASSERT_EQUAL(sdm_get_read_entry(&dm, &record), SDM_LAST_RECORD); + VERIFY_ENTRY(record, &FLUF_MAKE_RESOURCE_PATH(1, 0, 0), 7); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); +} + +AVS_UNIT_TEST(sdm_read, read_obj) { + READ_INIT(dm, obj); + fluf_io_out_entry_t record = { 0 }; + size_t out_res_count = 0; + + call_counter_read = 0; + call_counter_begin = 0; + call_counter_read = 0; + call_counter_end = 0; + + callback_value.int_value = 225; + fluf_uri_path_t path = FLUF_MAKE_OBJECT_PATH(1); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_READ, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_get_readable_res_count(&dm, &out_res_count)); + AVS_UNIT_ASSERT_EQUAL(out_res_count, 6); + AVS_UNIT_ASSERT_EQUAL(sdm_get_read_entry(&dm, &record), 0); + VERIFY_ENTRY(record, &FLUF_MAKE_RESOURCE_PATH(1, 0, 0), 225); + callback_value.int_value = 7; + AVS_UNIT_ASSERT_EQUAL(sdm_get_read_entry(&dm, &record), 0); + VERIFY_ENTRY(record, &FLUF_MAKE_RESOURCE_PATH(1, 1, 0), 7); + AVS_UNIT_ASSERT_EQUAL(sdm_get_read_entry(&dm, &record), 0); + VERIFY_ENTRY(record, &FLUF_MAKE_RESOURCE_PATH(1, 1, 1), 17); + AVS_UNIT_ASSERT_EQUAL(sdm_get_read_entry(&dm, &record), 0); + VERIFY_ENTRY(record, &FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 4, 0), 33); + AVS_UNIT_ASSERT_EQUAL(sdm_get_read_entry(&dm, &record), 0); + VERIFY_ENTRY(record, &FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 4, 1), 44); + AVS_UNIT_ASSERT_EQUAL(sdm_get_read_entry(&dm, &record), SDM_LAST_RECORD); + VERIFY_ENTRY(record, &FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 5, 0), 7); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + + AVS_UNIT_ASSERT_EQUAL(call_counter_read, 3); + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_NOT_MODIFIED); +} + +AVS_UNIT_TEST(sdm_read, bootstrap_read_obj) { + READ_INIT(dm, obj); + fluf_io_out_entry_t record = { 0 }; + size_t out_res_count = 0; + + callback_value.int_value = 225; + fluf_uri_path_t path = FLUF_MAKE_OBJECT_PATH(1); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_READ, true, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_get_readable_res_count(&dm, &out_res_count)); + AVS_UNIT_ASSERT_EQUAL(out_res_count, 7); + AVS_UNIT_ASSERT_EQUAL(sdm_get_read_entry(&dm, &record), 0); + VERIFY_ENTRY(record, &FLUF_MAKE_RESOURCE_PATH(1, 0, 0), 225); + callback_value.int_value = 7; + AVS_UNIT_ASSERT_EQUAL(sdm_get_read_entry(&dm, &record), 0); + VERIFY_ENTRY(record, &FLUF_MAKE_RESOURCE_PATH(1, 1, 0), 7); + AVS_UNIT_ASSERT_EQUAL(sdm_get_read_entry(&dm, &record), 0); + VERIFY_ENTRY(record, &FLUF_MAKE_RESOURCE_PATH(1, 1, 1), 17); + AVS_UNIT_ASSERT_EQUAL(sdm_get_read_entry(&dm, &record), 0); + VERIFY_ENTRY(record, &FLUF_MAKE_RESOURCE_PATH(1, 1, 2), 18); + AVS_UNIT_ASSERT_EQUAL(sdm_get_read_entry(&dm, &record), 0); + VERIFY_ENTRY(record, &FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 4, 0), 33); + AVS_UNIT_ASSERT_EQUAL(sdm_get_read_entry(&dm, &record), 0); + VERIFY_ENTRY(record, &FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 4, 1), 44); + AVS_UNIT_ASSERT_EQUAL(sdm_get_read_entry(&dm, &record), SDM_LAST_RECORD); + VERIFY_ENTRY(record, &FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 5, 0), 7); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); +} + +AVS_UNIT_TEST(sdm_read, bootstrap_read_obj_error) { + READ_INIT(dm, obj); + + fluf_uri_path_t path = FLUF_MAKE_OBJECT_PATH(3); + AVS_UNIT_ASSERT_EQUAL( + sdm_operation_begin(&dm, FLUF_OP_DM_READ, true, &path), + SDM_ERR_METHOD_NOT_ALLOWED); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), SDM_ERR_METHOD_NOT_ALLOWED); + path = FLUF_MAKE_OBJECT_PATH(2); + AVS_UNIT_ASSERT_EQUAL( + sdm_operation_begin(&dm, FLUF_OP_DM_READ, true, &path), + SDM_ERR_NOT_FOUND); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), SDM_ERR_NOT_FOUND); + path = FLUF_MAKE_INSTANCE_PATH(1, 2); + AVS_UNIT_ASSERT_EQUAL( + sdm_operation_begin(&dm, FLUF_OP_DM_READ, true, &path), + SDM_ERR_NOT_FOUND); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), SDM_ERR_NOT_FOUND); + path = FLUF_MAKE_RESOURCE_PATH(1, 1, 1); + AVS_UNIT_ASSERT_EQUAL( + sdm_operation_begin(&dm, FLUF_OP_DM_READ, true, &path), + SDM_ERR_METHOD_NOT_ALLOWED); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), SDM_ERR_METHOD_NOT_ALLOWED); + path = FLUF_MAKE_INSTANCE_PATH(1, 1); + AVS_UNIT_ASSERT_EQUAL( + sdm_operation_begin(&dm, FLUF_OP_DM_READ, true, &path), 0); +} + +AVS_UNIT_TEST(sdm_read, get_res_val) { + READ_INIT(dm, obj); + fluf_res_value_t out_value; + + callback_value.int_value = 3333; + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_PATH(1, 0, 0); + fluf_data_type_t type = 0; + AVS_UNIT_ASSERT_SUCCESS( + _sdm_get_resource_value(&dm, &path, &out_value, &type)); + AVS_UNIT_ASSERT_EQUAL(type, FLUF_DATA_TYPE_INT); + AVS_UNIT_ASSERT_EQUAL(out_value.int_value, 3333); + path = FLUF_MAKE_RESOURCE_PATH(1, 1, 1); + AVS_UNIT_ASSERT_SUCCESS( + _sdm_get_resource_value(&dm, &path, &out_value, NULL)); + AVS_UNIT_ASSERT_EQUAL(out_value.int_value, 17); + path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 4, 0); + AVS_UNIT_ASSERT_SUCCESS( + _sdm_get_resource_value(&dm, &path, &out_value, NULL)); + AVS_UNIT_ASSERT_EQUAL(out_value.int_value, 33); + path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 5, 0); + callback_value.int_value = 3331; + AVS_UNIT_ASSERT_SUCCESS( + _sdm_get_resource_value(&dm, &path, &out_value, NULL)); + AVS_UNIT_ASSERT_EQUAL(out_value.int_value, 3331); + + path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 5, 2); + AVS_UNIT_ASSERT_EQUAL(_sdm_get_resource_value(&dm, &path, &out_value, NULL), + SDM_ERR_NOT_FOUND); + path = FLUF_MAKE_RESOURCE_PATH(1, 1, 8); + AVS_UNIT_ASSERT_EQUAL(_sdm_get_resource_value(&dm, &path, &out_value, NULL), + SDM_ERR_NOT_FOUND); + path = FLUF_MAKE_INSTANCE_PATH(1, 1); + AVS_UNIT_ASSERT_EQUAL(_sdm_get_resource_value(&dm, &path, &out_value, NULL), + SDM_ERR_NOT_FOUND); + path = FLUF_MAKE_OBJECT_PATH(2); + AVS_UNIT_ASSERT_EQUAL(_sdm_get_resource_value(&dm, &path, &out_value, NULL), + SDM_ERR_NOT_FOUND); + path = FLUF_MAKE_RESOURCE_PATH(1, 1, 5); + AVS_UNIT_ASSERT_EQUAL(_sdm_get_resource_value(&dm, &path, &out_value, NULL), + SDM_ERR_NOT_FOUND); + path = FLUF_MAKE_RESOURCE_PATH(1, 0, 6); + AVS_UNIT_ASSERT_EQUAL(_sdm_get_resource_value(&dm, &path, &out_value, NULL), + SDM_ERR_NOT_FOUND); +} + +AVS_UNIT_TEST(sdm_read, get_res_type) { + READ_INIT(dm, obj); + fluf_data_type_t out_type = 0; + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_PATH(1, 0, 0); + AVS_UNIT_ASSERT_SUCCESS(sdm_get_resource_type(&dm, &path, &out_type)); + AVS_UNIT_ASSERT_EQUAL(FLUF_DATA_TYPE_INT, out_type); + path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 5, 0); + AVS_UNIT_ASSERT_SUCCESS(sdm_get_resource_type(&dm, &path, &out_type)); + AVS_UNIT_ASSERT_EQUAL(FLUF_DATA_TYPE_INT, out_type); + + path = FLUF_MAKE_RESOURCE_PATH(1, 1, 8); + AVS_UNIT_ASSERT_EQUAL(sdm_get_resource_type(&dm, &path, &out_type), + SDM_ERR_NOT_FOUND); + path = FLUF_MAKE_INSTANCE_PATH(1, 1); + AVS_UNIT_ASSERT_EQUAL(sdm_get_resource_type(&dm, &path, &out_type), + SDM_ERR_INPUT_ARG); + path = FLUF_MAKE_OBJECT_PATH(2); + AVS_UNIT_ASSERT_EQUAL(sdm_get_resource_type(&dm, &path, &out_type), + SDM_ERR_INPUT_ARG); +} diff --git a/tests/anj/sdm/sdm_register.c b/tests/anj/sdm/sdm_register.c new file mode 100644 index 00000000..6c43a360 --- /dev/null +++ b/tests/anj/sdm/sdm_register.c @@ -0,0 +1,165 @@ +/* + * Copyright 2023-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 + +static sdm_obj_t obj_0 = { + .oid = 0 +}; + +static const sdm_res_spec_t res_spec_0 = { + .rid = 0, + .operation = SDM_RES_R, + .type = FLUF_DATA_TYPE_INT +}; +static const sdm_res_spec_t res_spec_1 = { + .rid = 1, + .operation = SDM_RES_W, + .type = FLUF_DATA_TYPE_INT +}; +static const sdm_res_spec_t res_spec_2 = { + .rid = 2, + .operation = SDM_RES_RWM, + .type = FLUF_DATA_TYPE_INT +}; +static const sdm_res_spec_t res_spec_3 = { + .rid = 3, + .operation = SDM_RES_WM, + .type = FLUF_DATA_TYPE_INT +}; +static const sdm_res_spec_t res_spec_4 = { + .rid = 4, + .operation = SDM_RES_R, + .type = FLUF_DATA_TYPE_INT +}; +static sdm_res_t inst_1_res[] = { + { + .res_spec = &res_spec_0 + }, + { + .res_spec = &res_spec_1 + } +}; +static sdm_res_inst_t res_inst_1 = { + .riid = 1 +}; +static sdm_res_inst_t res_inst_2 = { + .riid = 2 +}; +static sdm_res_inst_t *res_insts[] = { &res_inst_1, &res_inst_2 }; +static sdm_res_t inst_2_res[] = { + { + .res_spec = &res_spec_0 + }, + { + .res_spec = &res_spec_1 + }, + { + .res_spec = &res_spec_2, + .value.res_inst.inst_count = 2, + .value.res_inst.max_inst_count = 2, + .value.res_inst.insts = res_insts + }, + { + .res_spec = &res_spec_3, + .value.res_inst.inst_count = 0 + }, + { + .res_spec = &res_spec_4 + } +}; +#define OBJ_1_INST_MAX_COUNT 3 +static sdm_obj_inst_t obj_1_inst_1 = { + .iid = 1, + .res_count = 2, + .resources = inst_1_res +}; +static sdm_obj_inst_t obj_1_inst_2 = { + .iid = 2, + .res_count = 5, + .resources = inst_2_res +}; +static sdm_obj_inst_t *obj_1_insts[OBJ_1_INST_MAX_COUNT] = { &obj_1_inst_1, + &obj_1_inst_2 }; +static sdm_obj_t obj_1 = { + .oid = 1, + .version = "1.1", + .insts = obj_1_insts, + .inst_count = 2, + .max_inst_count = OBJ_1_INST_MAX_COUNT +}; +static sdm_obj_inst_t obj_3_inst_1 = { + .iid = 0 +}; +static sdm_obj_inst_t *obj_3_insts[1] = { &obj_3_inst_1 }; +static sdm_obj_t obj_3 = { + .oid = 3, + .insts = obj_3_insts, + .inst_count = 1, + .max_inst_count = 1 +}; +static sdm_obj_t obj_5 = { + .oid = 5 +}; +static sdm_obj_t obj_55 = { + .oid = 55, + .version = "1.2" +}; + +AVS_UNIT_TEST(sdm_register, register_operation) { + sdm_data_model_t dm; + sdm_obj_t *objs[5]; + sdm_initialize(&dm, objs, 5); + + AVS_UNIT_ASSERT_SUCCESS(sdm_add_obj(&dm, &obj_0)); + AVS_UNIT_ASSERT_SUCCESS(sdm_add_obj(&dm, &obj_1)); + AVS_UNIT_ASSERT_SUCCESS(sdm_add_obj(&dm, &obj_3)); + AVS_UNIT_ASSERT_SUCCESS(sdm_add_obj(&dm, &obj_5)); + AVS_UNIT_ASSERT_SUCCESS(sdm_add_obj(&dm, &obj_55)); + + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_REGISTER, false, NULL)); + + fluf_uri_path_t path; + const char *version; + AVS_UNIT_ASSERT_SUCCESS(sdm_get_register_record(&dm, &path, &version)); + AVS_UNIT_ASSERT_TRUE(fluf_uri_path_equal(&path, &FLUF_MAKE_OBJECT_PATH(1))); + AVS_UNIT_ASSERT_EQUAL_STRING(version, obj_1.version); + AVS_UNIT_ASSERT_SUCCESS(sdm_get_register_record(&dm, &path, &version)); + AVS_UNIT_ASSERT_TRUE( + fluf_uri_path_equal(&path, &FLUF_MAKE_INSTANCE_PATH(1, 1))); + AVS_UNIT_ASSERT_SUCCESS(sdm_get_register_record(&dm, &path, &version)); + AVS_UNIT_ASSERT_TRUE( + fluf_uri_path_equal(&path, &FLUF_MAKE_INSTANCE_PATH(1, 2))); + AVS_UNIT_ASSERT_SUCCESS(sdm_get_register_record(&dm, &path, &version)); + AVS_UNIT_ASSERT_TRUE(fluf_uri_path_equal(&path, &FLUF_MAKE_OBJECT_PATH(3))); + AVS_UNIT_ASSERT_SUCCESS(sdm_get_register_record(&dm, &path, &version)); + AVS_UNIT_ASSERT_TRUE( + fluf_uri_path_equal(&path, &FLUF_MAKE_INSTANCE_PATH(3, 0))); + AVS_UNIT_ASSERT_SUCCESS(sdm_get_register_record(&dm, &path, &version)); + AVS_UNIT_ASSERT_TRUE(fluf_uri_path_equal(&path, &FLUF_MAKE_OBJECT_PATH(5))); + AVS_UNIT_ASSERT_EQUAL(sdm_get_register_record(&dm, &path, &version), + SDM_LAST_RECORD); + AVS_UNIT_ASSERT_TRUE( + fluf_uri_path_equal(&path, &FLUF_MAKE_OBJECT_PATH(55))); + AVS_UNIT_ASSERT_EQUAL_STRING(version, obj_55.version); + + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); +} diff --git a/tests/anj/sdm/sdm_write_replace.c b/tests/anj/sdm/sdm_write_replace.c new file mode 100644 index 00000000..48689d2f --- /dev/null +++ b/tests/anj/sdm/sdm_write_replace.c @@ -0,0 +1,744 @@ +/* + * Copyright 2023-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 + +static int call_counter_begin; +static int call_counter_end; +static int call_counter_validate; +static int call_counter_res_write; +static int call_counter_res_create; +static int call_counter_res_delete; +static int call_counter_reset; +static sdm_res_inst_t *call_res_inst; +static sdm_res_inst_t *call_res_inst_delete; +static sdm_res_t *call_res; +static bool inst_operation_end_return_eror; +static bool res_write_operation_return_eror; +static bool res_create_operation_return_eror; +static bool validate_return_eror; +static const fluf_res_value_t *call_value; +static sdm_op_result_t call_result; + +static int res_write(sdm_obj_t *obj, + sdm_obj_inst_t *obj_inst, + sdm_res_t *res, + sdm_res_inst_t *res_inst, + const fluf_res_value_t *value) { + (void) obj; + (void) obj_inst; + call_res = res; + call_res_inst = res_inst; + call_value = value; + call_counter_res_write++; + if (res_write_operation_return_eror) { + return -123; + } + return 0; +} + +static sdm_res_inst_t res_inst_new_1 = { + .riid = 0xFFFF +}; +static sdm_res_inst_t res_inst_new_2 = { + .riid = 0xFFFF +}; +static sdm_res_inst_t res_inst_new_3 = { + .riid = 0xFFFF +}; +static int res_inst_create(sdm_obj_t *obj, + sdm_obj_inst_t *obj_inst, + sdm_res_t *res, + sdm_res_inst_t **out_res_inst, + fluf_riid_t riid) { + (void) obj; + (void) obj_inst; + (void) res; + (void) riid; + + if (res_inst_new_1.riid == 0xFFFF) { + *out_res_inst = &res_inst_new_1; + } else if (res_inst_new_2.riid == 0xFFFF) { + *out_res_inst = &res_inst_new_2; + } else { + *out_res_inst = &res_inst_new_3; + } + call_counter_res_create++; + if (res_create_operation_return_eror) { + return -1; + } + return 0; +} +static int res_inst_delete(sdm_obj_t *obj, + sdm_obj_inst_t *obj_inst, + sdm_res_t *res, + sdm_res_inst_t *res_inst) { + (void) obj; + (void) obj_inst; + (void) res; + call_res_inst_delete = res_inst; + call_counter_res_delete++; + return 0; +} + +static int operation_begin(sdm_obj_t *obj, fluf_op_t operation) { + (void) obj; + (void) operation; + call_counter_begin++; + return 0; +} + +static int operation_end(sdm_obj_t *obj, sdm_op_result_t result) { + (void) obj; + call_counter_end++; + call_result = result; + if (inst_operation_end_return_eror) { + return -1; + } + return 0; +} + +static int operation_validate(sdm_obj_t *obj) { + (void) obj; + call_counter_validate++; + if (validate_return_eror) { + return -12; + } + return 0; +} + +static int inst_reset(sdm_obj_t *obj, sdm_obj_inst_t *obj_inst) { + (void) obj; + (void) obj_inst; + call_counter_reset++; + return 0; +} + +#define TEST_INIT(Dm, Obj) \ + sdm_res_handlers_t res_handlers = { \ + .res_write = res_write, \ + .res_inst_create = res_inst_create, \ + .res_inst_delete = res_inst_delete \ + }; \ + sdm_res_handlers_t res_handlers_2 = { \ + .res_inst_create = res_inst_create, \ + .res_inst_delete = res_inst_delete \ + }; \ + sdm_res_spec_t res_spec_0 = { \ + .rid = 0, \ + .operation = SDM_RES_RW, \ + .type = FLUF_DATA_TYPE_INT \ + }; \ + sdm_res_spec_t res_spec_1 = { \ + .rid = 1, \ + .operation = SDM_RES_RW, \ + .type = FLUF_DATA_TYPE_INT \ + }; \ + sdm_res_spec_t res_spec_2 = { \ + .rid = 2, \ + .operation = SDM_RES_BS_RW, \ + .type = FLUF_DATA_TYPE_DOUBLE \ + }; \ + sdm_res_spec_t res_spec_3 = { \ + .rid = 3, \ + .operation = SDM_RES_RM, \ + .type = FLUF_DATA_TYPE_INT \ + }; \ + sdm_res_spec_t res_spec_4 = { \ + .rid = 4, \ + .operation = SDM_RES_RWM, \ + .type = FLUF_DATA_TYPE_INT \ + }; \ + sdm_res_spec_t res_spec_5 = { \ + .rid = 5, \ + .operation = SDM_RES_RWM, \ + .type = FLUF_DATA_TYPE_INT \ + }; \ + sdm_res_spec_t res_spec_6 = { \ + .rid = 6, \ + .operation = SDM_RES_W, \ + .type = FLUF_DATA_TYPE_INT \ + }; \ + sdm_res_spec_t res_spec_7 = { \ + .rid = 7, \ + .operation = SDM_RES_RW, \ + .type = FLUF_DATA_TYPE_STRING \ + }; \ + sdm_res_t res_0[] = { \ + { \ + .res_spec = &res_spec_0, \ + .res_handlers = &res_handlers \ + }, \ + { \ + .res_spec = &res_spec_6, \ + .res_handlers = &res_handlers \ + } \ + }; \ + sdm_res_inst_t res_inst_1 = { \ + .riid = 1, \ + .res_value.value.int_value = 44 \ + }; \ + sdm_res_inst_t res_inst_3 = { \ + .riid = 3, \ + .res_value.value.int_value = 44 \ + }; \ + sdm_res_inst_t *res_insts[9] = { &res_inst_1, &res_inst_3 }; \ + sdm_res_inst_t *res_insts_5[1] = { &res_inst_1 }; \ + char res_7_buff[50] = { 0 }; \ + sdm_res_t res_1[] = { \ + { \ + .res_spec = &res_spec_0, \ + .res_handlers = &res_handlers \ + }, \ + { \ + .res_spec = &res_spec_1, \ + .value.res_value.value.int_value = 17 \ + }, \ + { \ + .res_spec = &res_spec_2, \ + .value.res_value.value.double_value = 18.0 \ + }, \ + { \ + .res_spec = &res_spec_3, \ + .value.res_inst.max_inst_count = 9, \ + .value.res_inst.inst_count = 0 \ + }, \ + { \ + .res_spec = &res_spec_4, \ + .value.res_inst.max_inst_count = 9, \ + .value.res_inst.inst_count = 2, \ + .value.res_inst.insts = res_insts, \ + .res_handlers = &res_handlers \ + }, \ + { \ + .res_spec = &res_spec_5, \ + .value.res_inst.max_inst_count = 2, \ + .value.res_inst.insts = res_insts_5, \ + .value.res_inst.inst_count = 1, \ + .res_handlers = &res_handlers_2 \ + }, \ + { \ + .res_spec = &res_spec_6, \ + .value.res_value.value.int_value = 17 \ + }, \ + { \ + .res_spec = &res_spec_7, \ + .value.res_value.value.bytes_or_string.data = res_7_buff, \ + .value.res_value.resource_buffer_size = 50 \ + } \ + }; \ + sdm_obj_inst_t obj_inst_0 = { \ + .iid = 0, \ + .res_count = 2, \ + .resources = res_0 \ + }; \ + sdm_obj_inst_t obj_inst_1 = { \ + .iid = 1, \ + .res_count = 8, \ + .resources = res_1 \ + }; \ + sdm_obj_inst_t obj_inst_2 = { \ + .iid = 2, \ + .res_count = 0 \ + }; \ + sdm_obj_inst_t *obj_insts[3] = { &obj_inst_0, &obj_inst_1, &obj_inst_2 }; \ + sdm_obj_handlers_t handlers = { \ + .operation_begin = operation_begin, \ + .operation_end = operation_end, \ + .operation_validate = operation_validate, \ + .inst_reset = inst_reset \ + }; \ + sdm_obj_t Obj = { \ + .oid = 1, \ + .insts = obj_insts, \ + .inst_count = 3, \ + .obj_handlers = &handlers, \ + .max_inst_count = 3 \ + }; \ + sdm_data_model_t Dm; \ + sdm_obj_t *Objs[1]; \ + sdm_initialize(&Dm, Objs, 1); \ + AVS_UNIT_ASSERT_SUCCESS(sdm_add_obj(&Dm, &Obj)); \ + call_counter_begin = 0; \ + call_counter_end = 0; \ + call_counter_validate = 0; \ + call_counter_res_write = 0; \ + call_value = NULL; \ + call_res = NULL; \ + call_res_inst = NULL; \ + call_result = 4; \ + call_counter_reset = 0; \ + call_counter_res_delete = 0; \ + call_counter_res_create = 0; \ + res_inst_new_1.riid = 0xFFFF; \ + res_inst_new_2.riid = 0xFFFF; \ + res_inst_new_3.riid = 0xFFFF; + +AVS_UNIT_TEST(sdm_write_replace, write_handler) { + TEST_INIT(dm, obj); + + fluf_io_out_entry_t record = { + .type = FLUF_DATA_TYPE_INT, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 0) + }; + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_PATH(1, 1, 0); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_WRITE_REPLACE, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_write, 1); + AVS_UNIT_ASSERT_TRUE(call_res == &res_1[0]); + AVS_UNIT_ASSERT_TRUE(call_res_inst == NULL); + AVS_UNIT_ASSERT_TRUE(call_value == &record.value); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_MODIFIED); +} + +AVS_UNIT_TEST(sdm_write_replace, write_no_handler) { + TEST_INIT(dm, obj); + + fluf_uri_path_t path = FLUF_MAKE_INSTANCE_PATH(1, 1); + + fluf_io_out_entry_t record_1 = { + .type = FLUF_DATA_TYPE_INT, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 1), + .value.int_value = 77777 + }; + fluf_io_out_entry_t record_6 = { + .type = FLUF_DATA_TYPE_INT, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 6), + .value.int_value = 88888 + }; + + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_WRITE_REPLACE, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record_1)); + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record_6)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_write, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_reset, 1); + AVS_UNIT_ASSERT_EQUAL(res_1[1].value.res_value.value.int_value, 77777); + AVS_UNIT_ASSERT_EQUAL(res_1[6].value.res_value.value.int_value, 88888); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_MODIFIED); +} + +AVS_UNIT_TEST(sdm_write_replace, write_string_in_chunk) { + TEST_INIT(dm, obj); + + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_PATH(1, 1, 7); + + fluf_io_out_entry_t record_1 = { + .type = FLUF_DATA_TYPE_STRING, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 7), + .value.bytes_or_string.data = "123", + .value.bytes_or_string.chunk_length = 3, + }; + fluf_io_out_entry_t record_2 = { + .type = FLUF_DATA_TYPE_STRING, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 7), + .value.bytes_or_string.data = "ABC", + .value.bytes_or_string.offset = 3, + .value.bytes_or_string.chunk_length = 3 + }; + fluf_io_out_entry_t record_3 = { + .type = FLUF_DATA_TYPE_STRING, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 7), + .value.bytes_or_string.data = "DEF", + .value.bytes_or_string.offset = 6, + .value.bytes_or_string.chunk_length = 3 + }; + + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_WRITE_REPLACE, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record_1)); + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record_2)); + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record_3)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_write, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_reset, 0); + AVS_UNIT_ASSERT_SUCCESS(strcmp(res_7_buff, "123ABCDEF")); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_MODIFIED); +} + +AVS_UNIT_TEST(sdm_write_replace, multi_res_write) { + TEST_INIT(dm, obj); + + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_PATH(1, 1, 4); + + fluf_io_out_entry_t record = { + .type = FLUF_DATA_TYPE_INT, + .path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 4, 3) + }; + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_WRITE_REPLACE, false, &path)); + + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record)); + AVS_UNIT_ASSERT_TRUE(call_res == &res_1[4]); + AVS_UNIT_ASSERT_TRUE(call_res_inst == &res_inst_new_1); + AVS_UNIT_ASSERT_TRUE(call_res_inst_delete == &res_inst_1); + AVS_UNIT_ASSERT_TRUE(call_value == &record.value); + call_value = NULL; + call_res = NULL; + AVS_UNIT_ASSERT_EQUAL(res_1[4].value.res_inst.inst_count, 1); + AVS_UNIT_ASSERT_TRUE(res_insts[0] == &res_inst_new_1); + + record.path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 4, 2); + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + AVS_UNIT_ASSERT_TRUE(call_res == &res_1[4]); + AVS_UNIT_ASSERT_TRUE(call_res_inst == &res_inst_new_2); + AVS_UNIT_ASSERT_TRUE(call_value == &record.value); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_write, 2); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_delete, 2); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_create, 2); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_MODIFIED); + + AVS_UNIT_ASSERT_EQUAL(res_inst_new_1.riid, 3); + AVS_UNIT_ASSERT_EQUAL(res_inst_new_2.riid, 2); + AVS_UNIT_ASSERT_TRUE(res_insts[0] == &res_inst_new_2); + AVS_UNIT_ASSERT_TRUE(res_insts[1] == &res_inst_new_1); + AVS_UNIT_ASSERT_EQUAL(res_1[4].value.res_inst.inst_count, 2); +} + +AVS_UNIT_TEST(sdm_write_replace, multi_res_write_no_handler) { + TEST_INIT(dm, obj); + + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_PATH(1, 1, 5); + fluf_io_out_entry_t record = { + .type = FLUF_DATA_TYPE_INT, + .value.int_value = 555555, + .path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 5, 188) + }; + + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_WRITE_REPLACE, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + + AVS_UNIT_ASSERT_EQUAL(res_inst_new_1.res_value.value.int_value, 555555); + AVS_UNIT_ASSERT_EQUAL(res_inst_new_1.riid, 188); + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_write, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_delete, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_create, 1); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_MODIFIED); +} + +AVS_UNIT_TEST(sdm_write_replace, bootstrap_write) { + TEST_INIT(dm, obj); + + fluf_uri_path_t path = FLUF_MAKE_INSTANCE_PATH(1, 1); + + fluf_io_out_entry_t record_1 = { + .type = FLUF_DATA_TYPE_DOUBLE, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 2), + .value.double_value = 1.25 + }; + + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_WRITE_REPLACE, true, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record_1)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_write, 0); + AVS_UNIT_ASSERT_EQUAL(res_1[2].value.res_value.value.double_value, 1.25); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_MODIFIED); + + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_WRITE_REPLACE, true, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record_1)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_WRITE_REPLACE, false, &path)); + AVS_UNIT_ASSERT_EQUAL(sdm_write_entry(&dm, &record_1), SDM_ERR_BAD_REQUEST); +} + +AVS_UNIT_TEST(sdm_write_replace, multi_res_write_create) { + TEST_INIT(dm, obj); + + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_PATH(1, 1, 4); + + fluf_io_out_entry_t record = { + .type = FLUF_DATA_TYPE_INT, + .path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 4, 0) + }; + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_WRITE_REPLACE, false, &path)); + + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record)); + AVS_UNIT_ASSERT_TRUE(call_res == &res_1[4]); + AVS_UNIT_ASSERT_TRUE(call_res_inst == &res_inst_new_1); + AVS_UNIT_ASSERT_TRUE(call_value == &record.value); + AVS_UNIT_ASSERT_EQUAL(res_inst_new_1.riid, 0); + call_value = NULL; + call_res = NULL; + + record.path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 4, 2); + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record)); + AVS_UNIT_ASSERT_TRUE(call_res == &res_1[4]); + AVS_UNIT_ASSERT_TRUE(call_res_inst == &res_inst_new_2); + AVS_UNIT_ASSERT_TRUE(call_value == &record.value); + AVS_UNIT_ASSERT_EQUAL(res_inst_new_2.riid, 2); + call_value = NULL; + call_res = NULL; + + record.path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 4, 8); + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + AVS_UNIT_ASSERT_TRUE(call_res == &res_1[4]); + AVS_UNIT_ASSERT_TRUE(call_res_inst == &res_inst_new_3); + AVS_UNIT_ASSERT_TRUE(call_value == &record.value); + AVS_UNIT_ASSERT_EQUAL(res_inst_new_3.riid, 8); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_write, 3); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_MODIFIED); + + AVS_UNIT_ASSERT_TRUE(res_insts[0] == &res_inst_new_1); + AVS_UNIT_ASSERT_TRUE(res_insts[1] == &res_inst_new_2); + AVS_UNIT_ASSERT_TRUE(res_insts[2] == &res_inst_new_3); + AVS_UNIT_ASSERT_TRUE(res_insts[0]->riid == 0 && res_insts[1]->riid == 2 + && res_insts[2]->riid == 8); +} + +AVS_UNIT_TEST(sdm_write_replace, error_type) { + TEST_INIT(dm, obj); + + fluf_io_out_entry_t record = { + .type = FLUF_DATA_TYPE_BOOL, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 0) + }; + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_PATH(1, 1, 0); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_WRITE_REPLACE, false, &path)); + AVS_UNIT_ASSERT_EQUAL(sdm_write_entry(&dm, &record), SDM_ERR_BAD_REQUEST); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), SDM_ERR_BAD_REQUEST); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_write, 0); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_FAILURE); +} + +AVS_UNIT_TEST(sdm_write_replace, error_no_writable) { + TEST_INIT(dm, obj); + res_spec_0.operation = SDM_RES_R; + fluf_io_out_entry_t record = { + .type = FLUF_DATA_TYPE_INT, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 0) + }; + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_PATH(1, 1, 0); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_WRITE_REPLACE, false, &path)); + AVS_UNIT_ASSERT_EQUAL(sdm_write_entry(&dm, &record), SDM_ERR_BAD_REQUEST); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), SDM_ERR_BAD_REQUEST); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_write, 0); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_FAILURE); +} + +AVS_UNIT_TEST(sdm_write_replace, error_path) { + TEST_INIT(dm, obj); + fluf_io_out_entry_t record = { + .type = FLUF_DATA_TYPE_INT, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 12) + }; + fluf_uri_path_t path = FLUF_MAKE_INSTANCE_PATH(1, 1); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_WRITE_REPLACE, false, &path)); + AVS_UNIT_ASSERT_EQUAL(sdm_write_entry(&dm, &record), SDM_ERR_NOT_FOUND); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), SDM_ERR_NOT_FOUND); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_write, 0); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_FAILURE); +} + +AVS_UNIT_TEST(sdm_write_replace, error_path_multi_instance) { + TEST_INIT(dm, obj); + fluf_io_out_entry_t record = { + .type = FLUF_DATA_TYPE_INT, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 4) + }; + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_PATH(1, 1, 4); + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_WRITE_REPLACE, false, &path)); + AVS_UNIT_ASSERT_EQUAL(sdm_write_entry(&dm, &record), + SDM_ERR_METHOD_NOT_ALLOWED); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), SDM_ERR_METHOD_NOT_ALLOWED); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_write, 0); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_FAILURE); +} + +AVS_UNIT_TEST(sdm_write_replace, handler_error) { + TEST_INIT(dm, obj); + + fluf_io_out_entry_t record = { + .type = FLUF_DATA_TYPE_INT, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 0) + }; + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_PATH(1, 1, 0); + res_write_operation_return_eror = true; + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_WRITE_REPLACE, false, &path)); + AVS_UNIT_ASSERT_EQUAL(sdm_write_entry(&dm, &record), -123); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), -123); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_write, 1); + AVS_UNIT_ASSERT_TRUE(call_res == &res_1[0]); + AVS_UNIT_ASSERT_TRUE(call_res_inst == NULL); + AVS_UNIT_ASSERT_TRUE(call_value == &record.value); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_FAILURE); + + res_write_operation_return_eror = false; +} + +AVS_UNIT_TEST(sdm_write_replace, handler_error_2) { + TEST_INIT(dm, obj); + + fluf_io_out_entry_t record = { + .type = FLUF_DATA_TYPE_INT, + .path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 4, 0) + }; + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_PATH(1, 1, 4); + res_create_operation_return_eror = true; + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_WRITE_REPLACE, false, &path)); + AVS_UNIT_ASSERT_EQUAL(sdm_write_entry(&dm, &record), -1); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), -1); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_write, 0); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_FAILURE); + + res_create_operation_return_eror = false; +} + +AVS_UNIT_TEST(sdm_write_replace, handler_error_3) { + TEST_INIT(dm, obj); + + fluf_io_out_entry_t record = { + .type = FLUF_DATA_TYPE_INT, + .path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 4, 0) + }; + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_PATH(1, 1, 4); + validate_return_eror = true; + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_WRITE_REPLACE, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record)); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), -12); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_write, 1); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_FAILURE); + + validate_return_eror = false; +} + +AVS_UNIT_TEST(sdm_write_replace, string_in_chunk_error) { + TEST_INIT(dm, obj); + + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_PATH(1, 1, 7); + + fluf_io_out_entry_t record_1 = { + .type = FLUF_DATA_TYPE_STRING, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 7), + .value.bytes_or_string.data = "123", + .value.bytes_or_string.chunk_length = 3, + }; + fluf_io_out_entry_t record_2 = { + .type = FLUF_DATA_TYPE_STRING, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 7), + .value.bytes_or_string.data = "ABC", + .value.bytes_or_string.offset = 3, + .value.bytes_or_string.chunk_length = 3 + }; + fluf_io_out_entry_t record_3 = { + .type = FLUF_DATA_TYPE_STRING, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 7), + .value.bytes_or_string.data = "DEF", + .value.bytes_or_string.offset = 6, + .value.bytes_or_string.chunk_length = 3 + }; + + res_1[7].value.res_value.resource_buffer_size = 7; + AVS_UNIT_ASSERT_SUCCESS( + sdm_operation_begin(&dm, FLUF_OP_DM_WRITE_REPLACE, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record_1)); + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record_2)); + AVS_UNIT_ASSERT_EQUAL(sdm_write_entry(&dm, &record_3), SDM_ERR_MEMORY); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), SDM_ERR_MEMORY); + res_1[7].value.res_value.resource_buffer_size = 50; + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_write, 0); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_FAILURE); +} + +AVS_UNIT_TEST(sdm_write_replace, lack_of_inst_reset_error) { + TEST_INIT(dm, obj); + + fluf_uri_path_t path = FLUF_MAKE_INSTANCE_PATH(1, 1); + handlers.inst_reset = NULL; + AVS_UNIT_ASSERT_EQUAL( + sdm_operation_begin(&dm, FLUF_OP_DM_WRITE_REPLACE, false, &path), + SDM_ERR_INTERNAL); +} diff --git a/tests/anj/sdm/sdm_write_update.c b/tests/anj/sdm/sdm_write_update.c new file mode 100644 index 00000000..9cee8982 --- /dev/null +++ b/tests/anj/sdm/sdm_write_update.c @@ -0,0 +1,663 @@ +/* + * Copyright 2023-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 + +static int call_counter_begin; +static int call_counter_end; +static int call_counter_validate; +static int call_counter_res_write; +static int call_counter_res_create; +static sdm_res_inst_t *call_res_inst; +static sdm_res_t *call_res; +static bool inst_operation_end_return_eror; +static bool res_write_operation_return_eror; +static bool res_create_operation_return_eror; +static const fluf_res_value_t *call_value; +static sdm_op_result_t call_result; + +static int res_write(sdm_obj_t *obj, + sdm_obj_inst_t *obj_inst, + sdm_res_t *res, + sdm_res_inst_t *res_inst, + const fluf_res_value_t *value) { + (void) obj; + (void) obj_inst; + call_res = res; + call_res_inst = res_inst; + call_value = value; + call_counter_res_write++; + if (res_write_operation_return_eror) { + return -1; + } + return 0; +} + +static sdm_res_inst_t res_inst_new_1 = { + .riid = 0xFFFF +}; +static sdm_res_inst_t res_inst_new_2 = { + .riid = 0xFFFF +}; +static sdm_res_inst_t res_inst_new_3 = { + .riid = 0xFFFF +}; +static int res_inst_create(sdm_obj_t *obj, + sdm_obj_inst_t *obj_inst, + sdm_res_t *res, + sdm_res_inst_t **out_res_inst, + fluf_riid_t riid) { + (void) obj; + (void) obj_inst; + (void) res; + (void) riid; + + if (res_inst_new_1.riid == 0xFFFF) { + *out_res_inst = &res_inst_new_1; + } else if (res_inst_new_2.riid == 0xFFFF) { + *out_res_inst = &res_inst_new_2; + } else { + *out_res_inst = &res_inst_new_3; + } + call_counter_res_create++; + if (res_create_operation_return_eror) { + return -2; + } + return 0; +} + +static int operation_begin(sdm_obj_t *obj, fluf_op_t operation) { + (void) obj; + (void) operation; + call_counter_begin++; + return 0; +} + +static int operation_end(sdm_obj_t *obj, sdm_op_result_t result) { + (void) obj; + call_counter_end++; + call_result = result; + if (inst_operation_end_return_eror) { + return -1; + } + return 0; +} + +static int operation_validate(sdm_obj_t *obj) { + (void) obj; + call_counter_validate++; + return 0; +} + +#define TEST_INIT(Dm, Obj) \ + sdm_res_handlers_t res_handlers = { \ + .res_write = res_write, \ + .res_inst_create = res_inst_create \ + }; \ + sdm_res_spec_t res_spec_0 = { \ + .rid = 0, \ + .operation = SDM_RES_RW, \ + .type = FLUF_DATA_TYPE_INT \ + }; \ + sdm_res_spec_t res_spec_1 = { \ + .rid = 1, \ + .operation = SDM_RES_RW, \ + .type = FLUF_DATA_TYPE_INT \ + }; \ + sdm_res_spec_t res_spec_2 = { \ + .rid = 2, \ + .operation = SDM_RES_BS_RW, \ + .type = FLUF_DATA_TYPE_DOUBLE \ + }; \ + sdm_res_spec_t res_spec_3 = { \ + .rid = 3, \ + .operation = SDM_RES_RM, \ + .type = FLUF_DATA_TYPE_INT \ + }; \ + sdm_res_spec_t res_spec_4 = { \ + .rid = 4, \ + .operation = SDM_RES_RWM, \ + .type = FLUF_DATA_TYPE_INT \ + }; \ + sdm_res_spec_t res_spec_5 = { \ + .rid = 5, \ + .operation = SDM_RES_RWM, \ + .type = FLUF_DATA_TYPE_INT \ + }; \ + sdm_res_spec_t res_spec_6 = { \ + .rid = 6, \ + .operation = SDM_RES_W, \ + .type = FLUF_DATA_TYPE_INT \ + }; \ + sdm_res_spec_t res_spec_7 = { \ + .rid = 7, \ + .operation = SDM_RES_RW, \ + .type = FLUF_DATA_TYPE_STRING \ + }; \ + sdm_res_t res_0[] = { \ + { \ + .res_spec = &res_spec_0, \ + .res_handlers = &res_handlers \ + }, \ + { \ + .res_spec = &res_spec_6, \ + .res_handlers = &res_handlers \ + } \ + }; \ + sdm_res_inst_t res_inst_1 = { \ + .riid = 1, \ + .res_value.value.int_value = 44 \ + }; \ + sdm_res_inst_t res_inst_3 = { \ + .riid = 3, \ + .res_value.value.int_value = 44 \ + }; \ + sdm_res_inst_t *res_insts[9] = { &res_inst_1, &res_inst_3 }; \ + sdm_res_inst_t *res_insts_5[1] = { &res_inst_1 }; \ + char res_7_buff[50] = { 0 }; \ + sdm_res_t res_1[] = { \ + { \ + .res_spec = &res_spec_0, \ + .res_handlers = &res_handlers \ + }, \ + { \ + .res_spec = &res_spec_1, \ + .value.res_value.value.int_value = 17 \ + }, \ + { \ + .res_spec = &res_spec_2, \ + .value.res_value.value.double_value = 18.0 \ + }, \ + { \ + .res_spec = &res_spec_3, \ + .value.res_inst.max_inst_count = 9, \ + .value.res_inst.inst_count = 0 \ + }, \ + { \ + .res_spec = &res_spec_4, \ + .value.res_inst.max_inst_count = 9, \ + .value.res_inst.inst_count = 2, \ + .value.res_inst.insts = res_insts, \ + .res_handlers = &res_handlers \ + }, \ + { \ + .res_spec = &res_spec_5, \ + .value.res_inst.max_inst_count = 2, \ + .value.res_inst.insts = res_insts_5, \ + .value.res_inst.inst_count = 1, \ + }, \ + { \ + .res_spec = &res_spec_6, \ + .value.res_value.value.int_value = 17 \ + }, \ + { \ + .res_spec = &res_spec_7, \ + .value.res_value.value.bytes_or_string.data = res_7_buff, \ + .value.res_value.resource_buffer_size = 50 \ + } \ + }; \ + sdm_obj_inst_t obj_inst_0 = { \ + .iid = 0, \ + .res_count = 2, \ + .resources = res_0 \ + }; \ + sdm_obj_inst_t obj_inst_1 = { \ + .iid = 1, \ + .res_count = 8, \ + .resources = res_1 \ + }; \ + sdm_obj_inst_t obj_inst_2 = { \ + .iid = 2, \ + .res_count = 0 \ + }; \ + sdm_obj_inst_t *obj_insts[3] = { &obj_inst_0, &obj_inst_1, &obj_inst_2 }; \ + sdm_obj_handlers_t handlers = { \ + .operation_begin = operation_begin, \ + .operation_end = operation_end, \ + .operation_validate = operation_validate, \ + }; \ + sdm_obj_t Obj = { \ + .oid = 1, \ + .insts = obj_insts, \ + .inst_count = 3, \ + .obj_handlers = &handlers, \ + .max_inst_count = 3 \ + }; \ + sdm_data_model_t Dm; \ + sdm_obj_t *Objs[1]; \ + sdm_initialize(&Dm, Objs, 1); \ + AVS_UNIT_ASSERT_SUCCESS(sdm_add_obj(&Dm, &Obj)); \ + call_counter_begin = 0; \ + call_counter_end = 0; \ + call_counter_validate = 0; \ + call_counter_res_write = 0; \ + call_value = NULL; \ + call_res = NULL; \ + call_res_inst = NULL; \ + call_result = 4; + +AVS_UNIT_TEST(sdm_write_update, write_handler) { + TEST_INIT(dm, obj); + + fluf_io_out_entry_t record = { + .type = FLUF_DATA_TYPE_INT, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 0) + }; + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_PATH(1, 1, 0); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_begin( + &dm, FLUF_OP_DM_WRITE_PARTIAL_UPDATE, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_write, 1); + AVS_UNIT_ASSERT_TRUE(call_res == &res_1[0]); + AVS_UNIT_ASSERT_TRUE(call_res_inst == NULL); + AVS_UNIT_ASSERT_TRUE(call_value == &record.value); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_MODIFIED); +} + +AVS_UNIT_TEST(sdm_write_update, write_no_handler) { + TEST_INIT(dm, obj); + + fluf_uri_path_t path = FLUF_MAKE_INSTANCE_PATH(1, 1); + + fluf_io_out_entry_t record_1 = { + .type = FLUF_DATA_TYPE_INT, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 1), + .value.int_value = 77777 + }; + fluf_io_out_entry_t record_6 = { + .type = FLUF_DATA_TYPE_INT, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 6), + .value.int_value = 88888 + }; + + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_begin( + &dm, FLUF_OP_DM_WRITE_PARTIAL_UPDATE, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record_1)); + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record_6)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_write, 0); + AVS_UNIT_ASSERT_EQUAL(res_1[1].value.res_value.value.int_value, 77777); + AVS_UNIT_ASSERT_EQUAL(res_1[6].value.res_value.value.int_value, 88888); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_MODIFIED); +} + +AVS_UNIT_TEST(sdm_write_update, write_string_in_chunk) { + TEST_INIT(dm, obj); + + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_PATH(1, 1, 7); + + fluf_io_out_entry_t record_1 = { + .type = FLUF_DATA_TYPE_STRING, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 7), + .value.bytes_or_string.data = "123", + .value.bytes_or_string.chunk_length = 3, + }; + fluf_io_out_entry_t record_2 = { + .type = FLUF_DATA_TYPE_STRING, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 7), + .value.bytes_or_string.data = "ABC", + .value.bytes_or_string.offset = 3, + .value.bytes_or_string.chunk_length = 3 + }; + fluf_io_out_entry_t record_3 = { + .type = FLUF_DATA_TYPE_STRING, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 7), + .value.bytes_or_string.data = "DEF", + .value.bytes_or_string.offset = 6, + .value.bytes_or_string.chunk_length = 3, + .value.bytes_or_string.full_length_hint = 9 + }; + + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_begin( + &dm, FLUF_OP_DM_WRITE_PARTIAL_UPDATE, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record_1)); + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record_2)); + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record_3)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_write, 0); + AVS_UNIT_ASSERT_SUCCESS(strcmp(res_7_buff, "123ABCDEF")); + AVS_UNIT_ASSERT_EQUAL( + res_1[7].value.res_value.value.bytes_or_string.full_length_hint, 9); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_MODIFIED); +} + +AVS_UNIT_TEST(sdm_write_update, multi_res_write) { + TEST_INIT(dm, obj); + + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_PATH(1, 1, 4); + + fluf_io_out_entry_t record = { + .type = FLUF_DATA_TYPE_INT, + .path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 4, 1) + }; + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_begin( + &dm, FLUF_OP_DM_WRITE_PARTIAL_UPDATE, false, &path)); + + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record)); + AVS_UNIT_ASSERT_TRUE(call_res == &res_1[4]); + AVS_UNIT_ASSERT_TRUE(call_res_inst == &res_inst_1); + AVS_UNIT_ASSERT_TRUE(call_value == &record.value); + call_value = NULL; + call_res = NULL; + + record.path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 4, 3); + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + AVS_UNIT_ASSERT_TRUE(call_res == &res_1[4]); + AVS_UNIT_ASSERT_TRUE(call_res_inst == &res_inst_3); + AVS_UNIT_ASSERT_TRUE(call_value == &record.value); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_write, 2); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_MODIFIED); +} + +AVS_UNIT_TEST(sdm_write_update, multi_res_write_no_handler) { + TEST_INIT(dm, obj); + + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 5, 1); + fluf_io_out_entry_t record = { + .type = FLUF_DATA_TYPE_INT, + .value.int_value = 555555, + .path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 5, 1) + }; + + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_begin( + &dm, FLUF_OP_DM_WRITE_PARTIAL_UPDATE, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + + AVS_UNIT_ASSERT_EQUAL(res_inst_1.res_value.value.int_value, 555555); + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_write, 0); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_MODIFIED); +} + +AVS_UNIT_TEST(sdm_write_update, bootstrap_write) { + TEST_INIT(dm, obj); + + fluf_uri_path_t path = FLUF_MAKE_INSTANCE_PATH(1, 1); + + fluf_io_out_entry_t record_1 = { + .type = FLUF_DATA_TYPE_DOUBLE, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 2), + .value.double_value = 1.25 + }; + + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_begin( + &dm, FLUF_OP_DM_WRITE_PARTIAL_UPDATE, true, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record_1)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_write, 0); + AVS_UNIT_ASSERT_EQUAL(res_1[2].value.res_value.value.double_value, 1.25); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_MODIFIED); + + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_begin( + &dm, FLUF_OP_DM_WRITE_PARTIAL_UPDATE, true, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record_1)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_begin( + &dm, FLUF_OP_DM_WRITE_PARTIAL_UPDATE, false, &path)); + AVS_UNIT_ASSERT_EQUAL(sdm_write_entry(&dm, &record_1), SDM_ERR_BAD_REQUEST); +} + +AVS_UNIT_TEST(sdm_write_update, multi_res_write_create) { + TEST_INIT(dm, obj); + + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_PATH(1, 1, 4); + + fluf_io_out_entry_t record = { + .type = FLUF_DATA_TYPE_INT, + .path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 4, 0) + }; + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_begin( + &dm, FLUF_OP_DM_WRITE_PARTIAL_UPDATE, false, &path)); + + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record)); + AVS_UNIT_ASSERT_TRUE(call_res == &res_1[4]); + AVS_UNIT_ASSERT_TRUE(call_res_inst == &res_inst_new_1); + AVS_UNIT_ASSERT_TRUE(call_value == &record.value); + AVS_UNIT_ASSERT_EQUAL(res_inst_new_1.riid, 0); + call_value = NULL; + call_res = NULL; + + record.path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 4, 2); + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record)); + AVS_UNIT_ASSERT_TRUE(call_res == &res_1[4]); + AVS_UNIT_ASSERT_TRUE(call_res_inst == &res_inst_new_2); + AVS_UNIT_ASSERT_TRUE(call_value == &record.value); + AVS_UNIT_ASSERT_EQUAL(res_inst_new_2.riid, 2); + call_value = NULL; + call_res = NULL; + + record.path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 4, 8); + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record)); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_end(&dm)); + AVS_UNIT_ASSERT_TRUE(call_res == &res_1[4]); + AVS_UNIT_ASSERT_TRUE(call_res_inst == &res_inst_new_3); + AVS_UNIT_ASSERT_TRUE(call_value == &record.value); + AVS_UNIT_ASSERT_EQUAL(res_inst_new_3.riid, 8); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_write, 3); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_SUCCESS_MODIFIED); + + AVS_UNIT_ASSERT_TRUE(res_insts[0] == &res_inst_new_1); + AVS_UNIT_ASSERT_TRUE(res_insts[1] == &res_inst_1); + AVS_UNIT_ASSERT_TRUE(res_insts[2] == &res_inst_new_2); + AVS_UNIT_ASSERT_TRUE(res_insts[3] == &res_inst_3); + AVS_UNIT_ASSERT_TRUE(res_insts[4] == &res_inst_new_3); + AVS_UNIT_ASSERT_TRUE(res_insts[0]->riid == 0 && res_insts[1]->riid == 1 + && res_insts[2]->riid == 2 && res_insts[3]->riid == 3 + && res_insts[4]->riid == 8); +} + +AVS_UNIT_TEST(sdm_write_update, error_type) { + TEST_INIT(dm, obj); + + fluf_io_out_entry_t record = { + .type = FLUF_DATA_TYPE_BOOL, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 0) + }; + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_PATH(1, 1, 0); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_begin( + &dm, FLUF_OP_DM_WRITE_PARTIAL_UPDATE, false, &path)); + AVS_UNIT_ASSERT_EQUAL(sdm_write_entry(&dm, &record), SDM_ERR_BAD_REQUEST); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), SDM_ERR_BAD_REQUEST); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_write, 0); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_FAILURE); +} + +AVS_UNIT_TEST(sdm_write_update, error_no_writable) { + TEST_INIT(dm, obj); + res_spec_0.operation = SDM_RES_R; + fluf_io_out_entry_t record = { + .type = FLUF_DATA_TYPE_INT, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 0) + }; + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_PATH(1, 1, 0); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_begin( + &dm, FLUF_OP_DM_WRITE_PARTIAL_UPDATE, false, &path)); + AVS_UNIT_ASSERT_EQUAL(sdm_write_entry(&dm, &record), SDM_ERR_BAD_REQUEST); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), SDM_ERR_BAD_REQUEST); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_write, 0); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_FAILURE); +} + +AVS_UNIT_TEST(sdm_write_update, error_path) { + TEST_INIT(dm, obj); + fluf_io_out_entry_t record = { + .type = FLUF_DATA_TYPE_INT, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 12) + }; + fluf_uri_path_t path = FLUF_MAKE_INSTANCE_PATH(1, 1); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_begin( + &dm, FLUF_OP_DM_WRITE_PARTIAL_UPDATE, false, &path)); + AVS_UNIT_ASSERT_EQUAL(sdm_write_entry(&dm, &record), SDM_ERR_NOT_FOUND); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), SDM_ERR_NOT_FOUND); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_write, 0); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_FAILURE); +} + +AVS_UNIT_TEST(sdm_write_update, error_path_multi_instance) { + TEST_INIT(dm, obj); + fluf_io_out_entry_t record = { + .type = FLUF_DATA_TYPE_INT, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 4) + }; + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_PATH(1, 1, 4); + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_begin( + &dm, FLUF_OP_DM_WRITE_PARTIAL_UPDATE, false, &path)); + AVS_UNIT_ASSERT_EQUAL(sdm_write_entry(&dm, &record), + SDM_ERR_METHOD_NOT_ALLOWED); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), SDM_ERR_METHOD_NOT_ALLOWED); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_write, 0); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_FAILURE); +} + +AVS_UNIT_TEST(sdm_write_update, handler_error) { + TEST_INIT(dm, obj); + + fluf_io_out_entry_t record = { + .type = FLUF_DATA_TYPE_INT, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 0) + }; + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_PATH(1, 1, 0); + res_write_operation_return_eror = true; + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_begin( + &dm, FLUF_OP_DM_WRITE_PARTIAL_UPDATE, false, &path)); + AVS_UNIT_ASSERT_EQUAL(sdm_write_entry(&dm, &record), -1); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), -1); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_write, 1); + AVS_UNIT_ASSERT_TRUE(call_res == &res_1[0]); + AVS_UNIT_ASSERT_TRUE(call_res_inst == NULL); + AVS_UNIT_ASSERT_TRUE(call_value == &record.value); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_FAILURE); + + res_write_operation_return_eror = false; +} + +AVS_UNIT_TEST(sdm_write_update, handler_error_2) { + TEST_INIT(dm, obj); + + fluf_io_out_entry_t record = { + .type = FLUF_DATA_TYPE_INT, + .path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(1, 1, 4, 0) + }; + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_PATH(1, 1, 4); + res_create_operation_return_eror = true; + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_begin( + &dm, FLUF_OP_DM_WRITE_PARTIAL_UPDATE, false, &path)); + AVS_UNIT_ASSERT_EQUAL(sdm_write_entry(&dm, &record), -2); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), -2); + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_write, 0); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_FAILURE); + + res_create_operation_return_eror = false; +} + +AVS_UNIT_TEST(sdm_write_update, string_in_chunk_error) { + TEST_INIT(dm, obj); + + fluf_uri_path_t path = FLUF_MAKE_RESOURCE_PATH(1, 1, 7); + + fluf_io_out_entry_t record_1 = { + .type = FLUF_DATA_TYPE_STRING, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 7), + .value.bytes_or_string.data = "123", + .value.bytes_or_string.chunk_length = 3, + }; + fluf_io_out_entry_t record_2 = { + .type = FLUF_DATA_TYPE_STRING, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 7), + .value.bytes_or_string.data = "ABC", + .value.bytes_or_string.offset = 3, + .value.bytes_or_string.chunk_length = 3 + }; + fluf_io_out_entry_t record_3 = { + .type = FLUF_DATA_TYPE_STRING, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 7), + .value.bytes_or_string.data = "DEF", + .value.bytes_or_string.offset = 6, + .value.bytes_or_string.chunk_length = 3 + }; + + res_1[7].value.res_value.resource_buffer_size = 7; + AVS_UNIT_ASSERT_SUCCESS(sdm_operation_begin( + &dm, FLUF_OP_DM_WRITE_PARTIAL_UPDATE, false, &path)); + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record_1)); + AVS_UNIT_ASSERT_SUCCESS(sdm_write_entry(&dm, &record_2)); + AVS_UNIT_ASSERT_EQUAL(sdm_write_entry(&dm, &record_3), SDM_ERR_MEMORY); + AVS_UNIT_ASSERT_EQUAL(sdm_operation_end(&dm), SDM_ERR_MEMORY); + res_1[7].value.res_value.resource_buffer_size = 50; + + AVS_UNIT_ASSERT_EQUAL(call_counter_begin, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_end, 1); + AVS_UNIT_ASSERT_EQUAL(call_counter_validate, 0); + AVS_UNIT_ASSERT_EQUAL(call_counter_res_write, 0); + AVS_UNIT_ASSERT_EQUAL(call_result, SDM_OP_RESULT_FAILURE); +} diff --git a/tests/fluf/bigdata.h b/tests/fluf/bigdata.h new file mode 100644 index 00000000..10f06685 --- /dev/null +++ b/tests/fluf/bigdata.h @@ -0,0 +1,36 @@ +/* + * Copyright 2023-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_TEST_BIGDATA_H +#define ANJAY_IO_TEST_BIGDATA_H + +// clang-format off +#define DATA10B "0123456789" +#define DATA100B \ + DATA10B DATA10B DATA10B DATA10B DATA10B \ + DATA10B DATA10B DATA10B DATA10B DATA10B +#define DATA1kB \ + DATA100B DATA100B DATA100B DATA100B DATA100B \ + DATA100B DATA100B DATA100B DATA100B DATA100B +#define DATA10kB \ + DATA1kB DATA1kB DATA1kB DATA1kB DATA1kB \ + DATA1kB DATA1kB DATA1kB DATA1kB DATA1kB +#define DATA100kB \ + DATA10kB DATA10kB DATA10kB DATA10kB DATA10kB \ + DATA10kB DATA10kB DATA10kB DATA10kB DATA10kB +#define DATA1MB \ + DATA100kB DATA100kB DATA100kB DATA100kB DATA100kB \ + DATA100kB DATA100kB DATA100kB DATA100kB DATA100kB +#define DATA10MB \ + DATA1MB DATA1MB DATA1MB DATA1MB DATA1MB \ + DATA1MB DATA1MB DATA1MB DATA1MB DATA1MB +#define DATA20MB DATA10MB DATA10MB +// clang-format on + +#endif /* ANJAY_IO_TEST_BIGDATA_H */ diff --git a/tests/fluf/bootstrap_discover_payload.c b/tests/fluf/bootstrap_discover_payload.c new file mode 100644 index 00000000..e7f6f61f --- /dev/null +++ b/tests/fluf/bootstrap_discover_payload.c @@ -0,0 +1,340 @@ +/* + * Copyright 2023-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 + +#define VERIFY_PAYLOAD(Payload, Buff, Len) \ + do { \ + AVS_UNIT_ASSERT_EQUAL(Len, strlen(Buff)); \ + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(Payload, Buff, Len); \ + } while (0) + +AVS_UNIT_TEST(bootstrap_discover_payload, object_0_call) { + fluf_io_bootstrap_discover_ctx_t ctx; + char out_buff[200] = { 0 }; + size_t copied_bytes = 0; + size_t msg_len = 0; + uint16_t ssid; + + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_init( + &ctx, &FLUF_MAKE_OBJECT_PATH(0))); + ssid = 101; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(0, 0), NULL, &ssid, + "coaps://server_1.example.com")); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 200, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(0, 1), NULL, NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 200, &copied_bytes)); + msg_len += copied_bytes; + ssid = 102; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(0, 2), NULL, &ssid, + "coaps://server_2.example.com")); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 200, &copied_bytes)); + msg_len += copied_bytes; +#ifdef FLUF_WITH_LWM2M12 + VERIFY_PAYLOAD(";lwm2m=1.2,;ssid=101;uri=\"coaps://" + "server_1.example.com\",," + ";ssid=102;uri=\"coaps://server_2.example.com\"", + out_buff, msg_len); +#else // FLUF_WITH_LWM2M12 + VERIFY_PAYLOAD(";lwm2m=1.1,;ssid=101;uri=\"coaps://" + "server_1.example.com\",," + ";ssid=102;uri=\"coaps://server_2.example.com\"", + out_buff, msg_len); +#endif // FLUF_WITH_LWM2M12 +} + +AVS_UNIT_TEST(bootstrap_discover_payload, object_root_call) { + fluf_io_bootstrap_discover_ctx_t ctx; + char out_buff[200] = { 0 }; + size_t copied_bytes = 0; + size_t msg_len = 0; + uint16_t ssid; + + AVS_UNIT_ASSERT_SUCCESS( + fluf_io_bootstrap_discover_ctx_init(&ctx, &FLUF_MAKE_ROOT_PATH())); + ssid = 101; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(0, 0), NULL, &ssid, + "coaps://server_1.example.com")); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 200, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(0, 1), NULL, NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 200, &copied_bytes)); + msg_len += copied_bytes; + ssid = 102; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(0, 2), NULL, &ssid, + "coaps://server_2.example.com")); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 200, &copied_bytes)); + msg_len += copied_bytes; + +#ifdef FLUF_WITH_LWM2M12 + VERIFY_PAYLOAD(";lwm2m=1.2,;ssid=101;uri=\"coaps://" + "server_1.example.com\",," + ";ssid=102;uri=\"coaps://server_2.example.com\"", + out_buff, msg_len); +#else // FLUF_WITH_LWM2M12 + VERIFY_PAYLOAD(";lwm2m=1.1,;ssid=101;uri=\"coaps://" + "server_1.example.com\",," + ";ssid=102;uri=\"coaps://server_2.example.com\"", + out_buff, msg_len); +#endif // FLUF_WITH_LWM2M12 +} + +AVS_UNIT_TEST(bootstrap_discover_payload, more_object_call) { + fluf_io_bootstrap_discover_ctx_t ctx; + char out_buff[200] = { 0 }; + size_t copied_bytes = 0; + size_t msg_len = 0; + uint16_t ssid; + + AVS_UNIT_ASSERT_SUCCESS( + fluf_io_bootstrap_discover_ctx_init(&ctx, &FLUF_MAKE_ROOT_PATH())); + ssid = 101; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(0, 0), NULL, &ssid, + "coaps://server_1.example.com")); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 200, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(0, 1), NULL, NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 200, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(1, 0), NULL, &ssid, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 200, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(3, 0), NULL, NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 200, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(4), NULL, NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 200, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(5), NULL, NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 200, &copied_bytes)); + msg_len += copied_bytes; + +#ifdef FLUF_WITH_LWM2M12 + VERIFY_PAYLOAD(";lwm2m=1.2,;ssid=101;uri=\"coaps://" + "server_1.example.com\",,;ssid=101," + ",,", + out_buff, msg_len); +#else // FLUF_WITH_LWM2M12 + VERIFY_PAYLOAD(";lwm2m=1.1,;ssid=101;uri=\"coaps://" + "server_1.example.com\",,;ssid=101," + ",,", + out_buff, msg_len); +#endif // FLUF_WITH_LWM2M12 +} + +AVS_UNIT_TEST(bootstrap_discover_payload, oscore) { + fluf_io_bootstrap_discover_ctx_t ctx; + char out_buff[200] = { 0 }; + size_t copied_bytes = 0; + size_t msg_len = 0; + fluf_uri_path_t base_path = FLUF_MAKE_ROOT_PATH(); + uint16_t ssid; + + AVS_UNIT_ASSERT_SUCCESS( + fluf_io_bootstrap_discover_ctx_init(&ctx, &base_path)); + ssid = 101; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(0, 0), NULL, &ssid, + "coaps://server_1.example.com")); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 200, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(0, 1), NULL, NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 200, &copied_bytes)); + msg_len += copied_bytes; + ssid = 102; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(0, 2), NULL, &ssid, + "coap://server_1.example.com")); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 200, &copied_bytes)); + msg_len += copied_bytes; + ssid = 101; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(21, 0), NULL, &ssid, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 200, &copied_bytes)); + msg_len += copied_bytes; + ssid = 102; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(21, 1), NULL, NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 200, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(21, 2), NULL, &ssid, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 200, &copied_bytes)); + msg_len += copied_bytes; +#ifdef FLUF_WITH_LWM2M12 + VERIFY_PAYLOAD(";lwm2m=1.2,;ssid=101;uri=\"coaps://" + "server_1.example.com\",," + ";ssid=102;uri=\"coap://server_1.example.com\",;ssid=101,,;ssid=102", + out_buff, msg_len); +#else // FLUF_WITH_LWM2M12 + VERIFY_PAYLOAD(";lwm2m=1.1,;ssid=101;uri=\"coaps://" + "server_1.example.com\",," + ";ssid=102;uri=\"coap://server_1.example.com\",;ssid=101,,;ssid=102", + out_buff, msg_len); +#endif // FLUF_WITH_LWM2M12 +} + +AVS_UNIT_TEST(bootstrap_discover_payload, version) { + fluf_io_bootstrap_discover_ctx_t ctx; + char out_buff[200] = { 0 }; + size_t copied_bytes = 0; + size_t msg_len = 0; + fluf_uri_path_t base_path = FLUF_MAKE_ROOT_PATH(); + uint16_t ssid; + + AVS_UNIT_ASSERT_SUCCESS( + fluf_io_bootstrap_discover_ctx_init(&ctx, &base_path)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(0, 0), NULL, NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 200, &copied_bytes)); + msg_len += copied_bytes; + ssid = 101; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(0, 1), NULL, &ssid, + "coaps://server_1.example.com")); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 200, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(1, 0), NULL, &ssid, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 200, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(3, 0), NULL, NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 200, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(4, 0), NULL, NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 200, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(5), NULL, NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 200, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(55), "1.9", NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 200, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(55, 0), NULL, NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 200, &copied_bytes)); + msg_len += copied_bytes; + +#ifdef FLUF_WITH_LWM2M12 + VERIFY_PAYLOAD(";lwm2m=1.2,,;ssid=101;uri=\"coaps://" + "server_1.example.com\",;ssid=101,,,,;ver=1.9,", + out_buff, msg_len); +#else // FLUF_WITH_LWM2M12 + VERIFY_PAYLOAD(";lwm2m=1.1,,;ssid=101;uri=\"coaps://" + "server_1.example.com\",;ssid=101,,,,;ver=1.9,", + out_buff, msg_len); +#endif // FLUF_WITH_LWM2M12 +} + +AVS_UNIT_TEST(bootstrap_discover_payload, errors) { + fluf_io_bootstrap_discover_ctx_t ctx; + fluf_uri_path_t base_path = FLUF_MAKE_OBJECT_PATH(1); + uint16_t ssid; + + AVS_UNIT_ASSERT_SUCCESS( + fluf_io_bootstrap_discover_ctx_init(&ctx, &base_path)); + + AVS_UNIT_ASSERT_FAILED(fluf_io_bootstrap_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(0), NULL, NULL, NULL)); + AVS_UNIT_ASSERT_FAILED(fluf_io_bootstrap_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(1), ".", NULL, NULL)); + + AVS_UNIT_ASSERT_FAILED(fluf_io_bootstrap_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(0, 0), NULL, NULL, NULL)); + AVS_UNIT_ASSERT_FAILED(fluf_io_bootstrap_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(1, 0), NULL, NULL, NULL)); + ssid = 101; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(1, 0), NULL, &ssid, NULL)); +} + +AVS_UNIT_TEST(bootstrap_discover_payload, block_transfer) { + for (size_t i = 5; i < 75; i++) { + fluf_io_bootstrap_discover_ctx_t ctx; + char out_buff[200] = { 0 }; + uint16_t ssid = 65534; + const char *uri = "coaps://server_1.example.com"; + fluf_io_bootstrap_discover_ctx_init(&ctx, &FLUF_MAKE_ROOT_PATH()); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_bootstrap_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(0, 65534), NULL, &ssid, uri)); + + int res = -1; + size_t copied_bytes = 0; + size_t msg_len = 0; + while (res) { + res = fluf_io_bootstrap_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], i, &copied_bytes); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_TRUE(res == 0 || res == FLUF_IO_NEED_NEXT_CALL); + } +#ifdef FLUF_WITH_LWM2M12 + VERIFY_PAYLOAD(";lwm2m=1.2,;ssid=65534;uri=\"coaps://" + "server_1.example.com\"", + out_buff, msg_len); +#else // FLUF_WITH_LWM2M12 + VERIFY_PAYLOAD(";lwm2m=1.1,;ssid=65534;uri=\"coaps://" + "server_1.example.com\"", + out_buff, msg_len); +#endif // FLUF_WITH_LWM2M12 + } +} diff --git a/tests/fluf/cbor_decoder_ll.c b/tests/fluf/cbor_decoder_ll.c new file mode 100644 index 00000000..f39d83e1 --- /dev/null +++ b/tests/fluf/cbor_decoder_ll.c @@ -0,0 +1,2374 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#if defined(FLUF_WITH_SENML_CBOR) || defined(FLUF_WITH_LWM2M_CBOR) \ + || defined(FLUF_WITH_CBOR) + +# include + +# include +# include + +# include + +# include "../../src/fluf/fluf_internal.h" + +typedef struct { + const char *data; + size_t size; +} test_data_t; + +# define TEST_DATA_INITIALIZER(Data) \ + { \ + .data = Data, \ + .size = sizeof(Data) - 1 \ + } + +/** + * Convenience macro used with test_decode_uint(), e.g.: + * > test_decode_uint(MAKE_TEST_DATA("\x00"), 0); + */ +# define MAKE_TEST_DATA(Data) ((test_data_t) TEST_DATA_INITIALIZER(Data)) + +AVS_UNIT_TEST(cbor_decoder_ll, tags_are_ignored) { + static const test_data_t inputs[] = { + // tag with 1 byte extended length, with one byte of follow up + TEST_DATA_INITIALIZER("\xD8\x01\x0F"), + // tag with 2 bytes extended length, with one byte of follow up + TEST_DATA_INITIALIZER("\xD9\x01\x02\x0F"), + // tag with 4 bytes extended length, with one byte of follow up + TEST_DATA_INITIALIZER("\xDA\x01\x02\x03\x04\x0F"), + // tag with 8 bytes extended length, with one byte of follow up + TEST_DATA_INITIALIZER("\xDB\x01\x02\x03\x04\x05\x06\x07\x08\x0F"), + }; + for (size_t i = 0; i < AVS_ARRAY_SIZE(inputs); ++i) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, inputs[i].data, inputs[i].size, true)); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_errno(&ctx)); + } +} + +AVS_UNIT_TEST(cbor_decoder_ll, eof_while_parsing_tag) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + static const char data[] = "\xDB\x01\x02\x03\x04\x05\x06\x07"; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_ERR_FORMAT); +} + +AVS_UNIT_TEST(cbor_decoder_ll, tags_without_following_bytes_are_invalid) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_feed_payload(&ctx, "\xC6", 1, true)); + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_ERR_FORMAT); +} + +AVS_UNIT_TEST(cbor_decoder_ll, + tag_followed_by_tag_without_following_bytes_are_invalid) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + static const char data[] = "\xC6\xC6"; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_ERR_FORMAT); +} + +AVS_UNIT_TEST(cbor_decoder_ll, feed_payload_invalid_state_1) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_feed_payload(&ctx, "\x00", 1, false)); + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_feed_payload(&ctx, "\x00", 1, + false), + FLUF_IO_ERR_LOGIC); +} + +AVS_UNIT_TEST(cbor_decoder_ll, feed_payload_invalid_state_2) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_feed_payload(&ctx, "\x00", 1, true)); + + fluf_cbor_ll_number_t decoded_number; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &decoded_number)); + AVS_UNIT_ASSERT_EQUAL(decoded_number.type, FLUF_CBOR_LL_VALUE_UINT); + AVS_UNIT_ASSERT_EQUAL(decoded_number.value.u64, 0); + + // payload already finished + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_feed_payload(&ctx, "\x00", 1, + true), + FLUF_IO_ERR_LOGIC); +} + +static const uint64_t DECODE_UINT_FAILURE = UINT64_MAX; + +static int test_decode_uint(test_data_t test_data, uint64_t expected_value) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, test_data.data, test_data.size, true)); + fluf_cbor_ll_value_type_t type; + fluf_cbor_ll_number_t decoded_number; + int result; + if ((result = fluf_cbor_ll_decoder_current_value_type(&ctx, &type)) + || (result = fluf_cbor_ll_decoder_number(&ctx, &decoded_number))) { + return result; + } + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_UINT); + AVS_UNIT_ASSERT_EQUAL(decoded_number.type, FLUF_CBOR_LL_VALUE_UINT); + if (expected_value == DECODE_UINT_FAILURE) { + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), + FLUF_IO_ERR_FORMAT); + } else { + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_EOF); + AVS_UNIT_ASSERT_EQUAL(decoded_number.value.u64, expected_value); + } + return 0; +} + +AVS_UNIT_TEST(cbor_decoder_ll, uint_small) { + for (unsigned small_value = 0; small_value < 24; ++small_value) { + const uint8_t data = + (uint8_t) ((CBOR_MAJOR_TYPE_UINT << 5) | small_value); + AVS_UNIT_ASSERT_SUCCESS(test_decode_uint( + (test_data_t) { &data, sizeof(data) }, small_value)); + } +} + +AVS_UNIT_TEST(cbor_decoder_ll, uint_extended_length_of_1_byte) { + AVS_UNIT_ASSERT_SUCCESS(test_decode_uint(MAKE_TEST_DATA("\x18\xFF"), 0xFF)); + AVS_UNIT_ASSERT_EQUAL(test_decode_uint(MAKE_TEST_DATA("\x18"), + DECODE_UINT_FAILURE), + FLUF_IO_ERR_FORMAT); +} + +AVS_UNIT_TEST(cbor_decoder_ll, uint_extended_length_of_2_byte) { + AVS_UNIT_ASSERT_SUCCESS( + test_decode_uint(MAKE_TEST_DATA("\x19\xAA\xBB"), 0xAABB)); + AVS_UNIT_ASSERT_EQUAL(test_decode_uint(MAKE_TEST_DATA("\x19"), + DECODE_UINT_FAILURE), + FLUF_IO_ERR_FORMAT); + AVS_UNIT_ASSERT_EQUAL(test_decode_uint(MAKE_TEST_DATA("\x19\xAA"), + DECODE_UINT_FAILURE), + FLUF_IO_ERR_FORMAT); +} + +AVS_UNIT_TEST(cbor_decoder_ll, uint_extended_length_of_4_byte) { + AVS_UNIT_ASSERT_SUCCESS(test_decode_uint( + MAKE_TEST_DATA("\x1A\xAA\xBB\xCC\xDD"), 0xAABBCCDD)); + AVS_UNIT_ASSERT_EQUAL(test_decode_uint(MAKE_TEST_DATA("\x1A\xAA"), + DECODE_UINT_FAILURE), + FLUF_IO_ERR_FORMAT); + AVS_UNIT_ASSERT_EQUAL(test_decode_uint(MAKE_TEST_DATA("\x1A\xAA\xBB"), + DECODE_UINT_FAILURE), + FLUF_IO_ERR_FORMAT); + AVS_UNIT_ASSERT_EQUAL(test_decode_uint(MAKE_TEST_DATA("\x1A\xAA\xBB\xCC"), + DECODE_UINT_FAILURE), + FLUF_IO_ERR_FORMAT); +} + +AVS_UNIT_TEST(cbor_decoder_ll, uint_extended_length_of_8_byte) { + AVS_UNIT_ASSERT_SUCCESS(test_decode_uint( + MAKE_TEST_DATA("\x1B\xAA\xBB\xCC\xDD\x00\x11\x22\x33"), + 0xAABBCCDD00112233ULL)); + AVS_UNIT_ASSERT_EQUAL( + test_decode_uint(MAKE_TEST_DATA("\x1B\xAA\xBB\xCC\xDD\x00\x11\x22"), + DECODE_UINT_FAILURE), + FLUF_IO_ERR_FORMAT); + AVS_UNIT_ASSERT_EQUAL( + test_decode_uint(MAKE_TEST_DATA("\x1B\xAA\xBB\xCC\xDD\x00\x11"), + DECODE_UINT_FAILURE), + FLUF_IO_ERR_FORMAT); + AVS_UNIT_ASSERT_EQUAL( + test_decode_uint(MAKE_TEST_DATA("\x1B\xAA\xBB\xCC\xDD\x00"), + DECODE_UINT_FAILURE), + FLUF_IO_ERR_FORMAT); + AVS_UNIT_ASSERT_EQUAL( + test_decode_uint(MAKE_TEST_DATA("\x1B\xAA\xBB\xCC\xDD"), + DECODE_UINT_FAILURE), + FLUF_IO_ERR_FORMAT); + AVS_UNIT_ASSERT_EQUAL(test_decode_uint(MAKE_TEST_DATA("\x1B\xAA\xBB\xCC"), + DECODE_UINT_FAILURE), + FLUF_IO_ERR_FORMAT); + AVS_UNIT_ASSERT_EQUAL(test_decode_uint(MAKE_TEST_DATA("\x1B\xAA\xBB"), + DECODE_UINT_FAILURE), + FLUF_IO_ERR_FORMAT); + AVS_UNIT_ASSERT_EQUAL(test_decode_uint(MAKE_TEST_DATA("\x1B\xAA"), + DECODE_UINT_FAILURE), + FLUF_IO_ERR_FORMAT); + AVS_UNIT_ASSERT_EQUAL(test_decode_uint(MAKE_TEST_DATA("\x1B"), + DECODE_UINT_FAILURE), + FLUF_IO_ERR_FORMAT); +} + +AVS_UNIT_TEST(cbor_decoder_ll, large_uint_with_large_tag_unfinished_payload) { + static const char data[] = "\xDB\x01\x02\x03\x04\x05\x06\x07\x08" + "\x1B\xAA\xBB\xCC\xDD\x00\x11\x22\x33"; + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, false)); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_errno(&ctx)); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_UINT); + AVS_UNIT_ASSERT_EQUAL(value.value.u64, 0xAABBCCDD00112233ULL); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), + FLUF_IO_WANT_NEXT_PAYLOAD); + + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_feed_payload(&ctx, NULL, 0, true)); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_EOF); +} + +AVS_UNIT_TEST(cbor_decoder_ll, large_uint_with_large_tag_split_payload) { + static const char data[] = "\xDB\x01\x02\x03\x04\x05\x06\x07\x08" + "\x1B\xAA\xBB\xCC\xDD\x00\x11\x22\x33"; + for (size_t split = 0; split < sizeof(data) - 1; ++split) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_feed_payload(&ctx, data, split, false)); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), + FLUF_IO_WANT_NEXT_PAYLOAD); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data + split, sizeof(data) - 1 - split, true)); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_errno(&ctx)); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_UINT); + AVS_UNIT_ASSERT_EQUAL(value.value.u64, 0xAABBCCDD00112233ULL); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_EOF); + } +} + +static const int64_t DECODE_NEGATIVE_INT_FAILURE = INT64_MAX; + +static int test_decode_negative_int(test_data_t test_data, + int64_t expected_value) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, test_data.data, test_data.size, true)); + fluf_cbor_ll_value_type_t type; + fluf_cbor_ll_number_t decoded_number; + int result; + if ((result = fluf_cbor_ll_decoder_current_value_type(&ctx, &type)) + || (result = fluf_cbor_ll_decoder_number(&ctx, &decoded_number))) { + return result; + } + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_NEGATIVE_INT); + AVS_UNIT_ASSERT_EQUAL(decoded_number.type, FLUF_CBOR_LL_VALUE_NEGATIVE_INT); + if (expected_value == DECODE_NEGATIVE_INT_FAILURE) { + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), + FLUF_IO_ERR_FORMAT); + } else { + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_EOF); + AVS_UNIT_ASSERT_EQUAL(decoded_number.value.i64, expected_value); + } + return 0; +} + +AVS_UNIT_TEST(cbor_decoder_ll, neg_int_small) { + for (int small_value = 0; small_value < 24; ++small_value) { + const uint8_t data = + (uint8_t) ((CBOR_MAJOR_TYPE_NEGATIVE_INT << 5) | small_value); + AVS_UNIT_ASSERT_SUCCESS(test_decode_negative_int( + (test_data_t) { &data, sizeof(data) }, -small_value - 1)); + } + const uint8_t data = (uint8_t) ((CBOR_MAJOR_TYPE_NEGATIVE_INT << 5) | 24); + AVS_UNIT_ASSERT_EQUAL( + test_decode_negative_int((test_data_t) { &data, sizeof(data) }, + DECODE_NEGATIVE_INT_FAILURE), + FLUF_IO_ERR_FORMAT); +} + +AVS_UNIT_TEST(cbor_decoder_ll, neg_int_extended_length_of_1_byte) { + AVS_UNIT_ASSERT_SUCCESS( + test_decode_negative_int(MAKE_TEST_DATA("\x38\xFF"), -256)); + AVS_UNIT_ASSERT_EQUAL(test_decode_negative_int(MAKE_TEST_DATA("\x38"), + DECODE_NEGATIVE_INT_FAILURE), + FLUF_IO_ERR_FORMAT); +} + +AVS_UNIT_TEST(cbor_decoder_ll, neg_int_extended_length_of_2_byte) { + AVS_UNIT_ASSERT_SUCCESS( + test_decode_negative_int(MAKE_TEST_DATA("\x39\x00\x01"), -2)); + AVS_UNIT_ASSERT_EQUAL(test_decode_negative_int(MAKE_TEST_DATA("\x39\x00"), + DECODE_NEGATIVE_INT_FAILURE), + FLUF_IO_ERR_FORMAT); + AVS_UNIT_ASSERT_EQUAL(test_decode_negative_int(MAKE_TEST_DATA("\x39"), + DECODE_NEGATIVE_INT_FAILURE), + FLUF_IO_ERR_FORMAT); +} + +AVS_UNIT_TEST(cbor_decoder_ll, neg_int_boundary) { + AVS_UNIT_ASSERT_SUCCESS(test_decode_negative_int( + MAKE_TEST_DATA("\x3B\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF"), INT64_MIN)); + // Overflow. + AVS_UNIT_ASSERT_EQUAL( + test_decode_negative_int( + MAKE_TEST_DATA("\x3B\x80\x00\x00\x00\x00\x00\x00\x00"), + DECODE_NEGATIVE_INT_FAILURE), + FLUF_IO_ERR_FORMAT); +} + +AVS_UNIT_TEST(cbor_decoder_ll, bytes_short) { + // - 1st byte: code, + // - maximum 23 bytes of payload. + // - last byte: small integer + uint8_t input_bytes[1 + 23 + 1]; + + for (size_t short_len = 0; short_len < 24; ++short_len) { + input_bytes[0] = + (uint8_t) ((CBOR_MAJOR_TYPE_BYTE_STRING << 5) | short_len); + for (size_t i = 0; i < short_len; ++i) { + input_bytes[i + 1] = (uint8_t) rand(); + } + uint8_t small_int = (uint8_t) (rand() % 24); + input_bytes[short_len + 1] = + (uint8_t) ((CBOR_MAJOR_TYPE_UINT << 5) | small_int); + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, input_bytes, short_len + 2, true)); + + fluf_cbor_ll_decoder_bytes_ctx_t *bytes_ctx = NULL; + ptrdiff_t total_size = 0; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_bytes(&ctx, &bytes_ctx, &total_size)); + AVS_UNIT_ASSERT_EQUAL(total_size, (ptrdiff_t) short_len); + const void *output_bytes = NULL; + size_t output_bytes_size; + bool message_finished; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_bytes_get_some(bytes_ctx, + &output_bytes, + &output_bytes_size, + &message_finished)); + AVS_UNIT_ASSERT_TRUE(message_finished); + AVS_UNIT_ASSERT_EQUAL(output_bytes_size, short_len); + // fluf_cbor_ll_decoder_bytes_get_some() returns pointers + // inside the input buffer + AVS_UNIT_ASSERT_TRUE(output_bytes == &input_bytes[1]); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_errno(&ctx)); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_UINT); + + fluf_cbor_ll_number_t decoded_number; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_number(&ctx, &decoded_number)); + AVS_UNIT_ASSERT_EQUAL(decoded_number.type, FLUF_CBOR_LL_VALUE_UINT); + AVS_UNIT_ASSERT_EQUAL(decoded_number.value.u64, small_int); + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_EOF); + } +} + +AVS_UNIT_TEST(cbor_decoder_ll, bytes_indefinite) { + // (_ h'AABBCCDD', h'EEFF99'), 7 + uint8_t input_bytes[] = { 0x5F, 0x44, 0xAA, 0xBB, 0xCC, 0xDD, + 0x43, 0xEE, 0xFF, 0x99, 0xFF, 0x07 }; + + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, input_bytes, sizeof(input_bytes), true)); + + fluf_cbor_ll_decoder_bytes_ctx_t *bytes_ctx = NULL; + ptrdiff_t total_size = 0; +# ifdef FLUF_WITH_CBOR_INDEFINITE_BYTES + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_bytes(&ctx, &bytes_ctx, &total_size)); + AVS_UNIT_ASSERT_EQUAL(total_size, FLUF_CBOR_LL_DECODER_ITEMS_INDEFINITE); + const void *output_bytes = NULL; + size_t output_bytes_size; + bool message_finished; + // first chunk + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_bytes_get_some( + bytes_ctx, &output_bytes, &output_bytes_size, &message_finished)); + AVS_UNIT_ASSERT_TRUE(output_bytes == &input_bytes[2]); + AVS_UNIT_ASSERT_EQUAL(output_bytes_size, 4); + AVS_UNIT_ASSERT_FALSE(message_finished); + // second chunk + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_bytes_get_some( + bytes_ctx, &output_bytes, &output_bytes_size, &message_finished)); + AVS_UNIT_ASSERT_TRUE(output_bytes == &input_bytes[7]); + AVS_UNIT_ASSERT_EQUAL(output_bytes_size, 3); + AVS_UNIT_ASSERT_FALSE(message_finished); + // end of indefinite bytes + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_bytes_get_some( + bytes_ctx, &output_bytes, &output_bytes_size, &message_finished)); + AVS_UNIT_ASSERT_NULL(output_bytes); + AVS_UNIT_ASSERT_EQUAL(output_bytes_size, 0); + AVS_UNIT_ASSERT_TRUE(message_finished); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_errno(&ctx)); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_UINT); + + fluf_cbor_ll_number_t decoded_number; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &decoded_number)); + AVS_UNIT_ASSERT_EQUAL(decoded_number.type, FLUF_CBOR_LL_VALUE_UINT); + AVS_UNIT_ASSERT_EQUAL(decoded_number.value.u64, 7); + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_EOF); +# else // FLUF_WITH_CBOR_INDEFINITE_BYTES + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_bytes(&ctx, &bytes_ctx, + &total_size), + FLUF_IO_ERR_FORMAT); +# endif // FLUF_WITH_CBOR_INDEFINITE_BYTES +} + +AVS_UNIT_TEST(cbor_decoder_ll, bytes_indefinite_empty) { + // (_ ), 7 + uint8_t input_bytes[] = { 0x5F, 0xFF, 0x07 }; + + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, input_bytes, sizeof(input_bytes), true)); + + fluf_cbor_ll_decoder_bytes_ctx_t *bytes_ctx = NULL; + ptrdiff_t total_size = 0; +# ifdef FLUF_WITH_CBOR_INDEFINITE_BYTES + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_bytes(&ctx, &bytes_ctx, &total_size)); + AVS_UNIT_ASSERT_EQUAL(total_size, FLUF_CBOR_LL_DECODER_ITEMS_INDEFINITE); + const void *output_bytes = NULL; + size_t output_bytes_size; + bool message_finished; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_bytes_get_some( + bytes_ctx, &output_bytes, &output_bytes_size, &message_finished)); + AVS_UNIT_ASSERT_EQUAL(output_bytes_size, 0); + AVS_UNIT_ASSERT_TRUE(message_finished); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_errno(&ctx)); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_UINT); + + fluf_cbor_ll_number_t decoded_number; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &decoded_number)); + AVS_UNIT_ASSERT_EQUAL(decoded_number.type, FLUF_CBOR_LL_VALUE_UINT); + AVS_UNIT_ASSERT_EQUAL(decoded_number.value.u64, 7); + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_EOF); +# else // FLUF_WITH_CBOR_INDEFINITE_BYTES + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_bytes(&ctx, &bytes_ctx, + &total_size), + FLUF_IO_ERR_FORMAT); +# endif // FLUF_WITH_CBOR_INDEFINITE_BYTES +} + +AVS_UNIT_TEST(cbor_decoder_ll, bytes_indefinite_invalid_integer_inside) { + // (_ 21 ) + uint8_t input_bytes[] = { 0x5F, 0x15, 0xFF }; + + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, input_bytes, sizeof(input_bytes), true)); + + fluf_cbor_ll_decoder_bytes_ctx_t *bytes_ctx = NULL; + ptrdiff_t total_size = 0; +# ifdef FLUF_WITH_CBOR_INDEFINITE_BYTES + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_bytes(&ctx, &bytes_ctx, &total_size)); + AVS_UNIT_ASSERT_EQUAL(total_size, FLUF_CBOR_LL_DECODER_ITEMS_INDEFINITE); + const void *output_bytes = NULL; + size_t output_bytes_size; + bool message_finished; + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_bytes_get_some( + bytes_ctx, &output_bytes, &output_bytes_size, + &message_finished), + FLUF_IO_ERR_FORMAT); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_ERR_FORMAT); +# else // FLUF_WITH_CBOR_INDEFINITE_BYTES + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_bytes(&ctx, &bytes_ctx, + &total_size), + FLUF_IO_ERR_FORMAT); +# endif // FLUF_WITH_CBOR_INDEFINITE_BYTES +} + +AVS_UNIT_TEST(cbor_decoder_ll, bytes_indefinite_invalid_map_inside) { + // (_ {2: 5} ) + uint8_t input_bytes[] = { 0x5F, 0xA1, 0x02, 0x05, 0xFF }; + + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, input_bytes, sizeof(input_bytes), true)); + + fluf_cbor_ll_decoder_bytes_ctx_t *bytes_ctx = NULL; + ptrdiff_t total_size = 0; +# ifdef FLUF_WITH_CBOR_INDEFINITE_BYTES + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_bytes(&ctx, &bytes_ctx, &total_size)); + AVS_UNIT_ASSERT_EQUAL(total_size, FLUF_CBOR_LL_DECODER_ITEMS_INDEFINITE); + const void *output_bytes = NULL; + size_t output_bytes_size; + bool message_finished; + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_bytes_get_some( + bytes_ctx, &output_bytes, &output_bytes_size, + &message_finished), + FLUF_IO_ERR_FORMAT); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_ERR_FORMAT); +# else // FLUF_WITH_CBOR_INDEFINITE_BYTES + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_bytes(&ctx, &bytes_ctx, + &total_size), + FLUF_IO_ERR_FORMAT); +# endif // FLUF_WITH_CBOR_INDEFINITE_BYTES +} + +AVS_UNIT_TEST(cbor_decoder_ll, bytes_indefinite_invalid_bytes_and_map_inside) { + // (_ h'001122', {2: 5} ) + uint8_t input_bytes[] = { 0x5F, 0x43, 0x00, 0x11, 0x22, + 0xA1, 0x02, 0x05, 0xFF }; + + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, input_bytes, sizeof(input_bytes), true)); + + fluf_cbor_ll_decoder_bytes_ctx_t *bytes_ctx = NULL; + ptrdiff_t total_size = 0; +# ifdef FLUF_WITH_CBOR_INDEFINITE_BYTES + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_bytes(&ctx, &bytes_ctx, &total_size)); + AVS_UNIT_ASSERT_EQUAL(total_size, FLUF_CBOR_LL_DECODER_ITEMS_INDEFINITE); + const void *output_bytes = NULL; + size_t output_bytes_size; + bool message_finished; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_bytes_get_some( + bytes_ctx, &output_bytes, &output_bytes_size, &message_finished)); + AVS_UNIT_ASSERT_TRUE(output_bytes == &input_bytes[2]); + AVS_UNIT_ASSERT_EQUAL(output_bytes_size, 3); + AVS_UNIT_ASSERT_FALSE(message_finished); + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_bytes_get_some( + bytes_ctx, &output_bytes, &output_bytes_size, + &message_finished), + FLUF_IO_ERR_FORMAT); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_ERR_FORMAT); +# else // FLUF_WITH_CBOR_INDEFINITE_BYTES + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_bytes(&ctx, &bytes_ctx, + &total_size), + FLUF_IO_ERR_FORMAT); +# endif // FLUF_WITH_CBOR_INDEFINITE_BYTES +} + +AVS_UNIT_TEST(cbor_decoder_ll, bytes_nested_indefinite) { + uint8_t input_bytes[] = { 0x5F, 0x5F, 0xFF, 0xFF }; + + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, input_bytes, sizeof(input_bytes), true)); + + fluf_cbor_ll_decoder_bytes_ctx_t *bytes_ctx = NULL; + ptrdiff_t total_size = 0; +# ifdef FLUF_WITH_CBOR_INDEFINITE_BYTES + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_bytes(&ctx, &bytes_ctx, &total_size)); + AVS_UNIT_ASSERT_EQUAL(total_size, FLUF_CBOR_LL_DECODER_ITEMS_INDEFINITE); + const void *output_bytes = NULL; + size_t output_bytes_size; + bool message_finished; + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_bytes_get_some( + bytes_ctx, &output_bytes, &output_bytes_size, + &message_finished), + FLUF_IO_ERR_FORMAT); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_ERR_FORMAT); +# else // FLUF_WITH_CBOR_INDEFINITE_BYTES + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_bytes(&ctx, &bytes_ctx, + &total_size), + FLUF_IO_ERR_FORMAT); +# endif // FLUF_WITH_CBOR_INDEFINITE_BYTES +} + +AVS_UNIT_TEST(cbor_decoder_ll, bytes_long) { + // - 1st byte: code, + // - 2nd byte: extended length high byte, + // - 3rd byte: extended length low byte, + // - rest: 256 bytes of payload. + enum { PAYLOAD_LEN = 256 }; + uint8_t input_bytes[3 + PAYLOAD_LEN]; + + input_bytes[0] = 0x59; // major-type=bytes, extended-length=2bytes + input_bytes[1] = PAYLOAD_LEN >> 8; + input_bytes[2] = PAYLOAD_LEN & 0xFF; + for (size_t i = 3; i < sizeof(input_bytes); ++i) { + input_bytes[i] = (uint8_t) rand(); + } + + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, input_bytes, sizeof(input_bytes), true)); + + fluf_cbor_ll_decoder_bytes_ctx_t *bytes_ctx = NULL; + ptrdiff_t total_size = 0; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_bytes(&ctx, &bytes_ctx, &total_size)); + AVS_UNIT_ASSERT_EQUAL(total_size, PAYLOAD_LEN); + const void *output_bytes = NULL; + size_t output_bytes_size; + bool message_finished; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_bytes_get_some( + bytes_ctx, &output_bytes, &output_bytes_size, &message_finished)); + AVS_UNIT_ASSERT_TRUE(output_bytes == &input_bytes[3]); + AVS_UNIT_ASSERT_EQUAL(output_bytes_size, PAYLOAD_LEN); + AVS_UNIT_ASSERT_TRUE(message_finished); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_EOF); +} + +# if FLUF_MAX_CBOR_NEST_STACK_SIZE > 0 +AVS_UNIT_TEST(cbor_decoder_ll, bytes_long_split_payload) { + // - 1st byte: code, + // - 2nd byte: extended length high byte, + // - 3rd byte: extended length low byte, + // - rest: 256 bytes of payload. + enum { PAYLOAD_LEN = 256 }; + uint8_t input_bytes[3 + PAYLOAD_LEN]; + + input_bytes[0] = 0x59; // major-type=bytes, extended-length=2bytes + input_bytes[1] = PAYLOAD_LEN >> 8; + input_bytes[2] = PAYLOAD_LEN & 0xFF; + for (size_t i = 3; i < sizeof(input_bytes); ++i) { + input_bytes[i] = (uint8_t) rand(); + } + + for (size_t split = 0; split < 4; ++split) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, input_bytes, split, false)); + + fluf_cbor_ll_decoder_bytes_ctx_t *bytes_ctx = NULL; + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_bytes(&ctx, &bytes_ctx, + NULL), + FLUF_IO_WANT_NEXT_PAYLOAD); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, input_bytes + split, sizeof(input_bytes) - split, true)); + + size_t nesting_level; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 0); + + ptrdiff_t total_size; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_bytes(&ctx, &bytes_ctx, &total_size)); + AVS_UNIT_ASSERT_EQUAL(total_size, PAYLOAD_LEN); + const void *output_bytes = NULL; + size_t output_bytes_size; + bool message_finished; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_bytes_get_some(bytes_ctx, + &output_bytes, + &output_bytes_size, + &message_finished)); + AVS_UNIT_ASSERT_TRUE(output_bytes == &input_bytes[3]); + AVS_UNIT_ASSERT_EQUAL(output_bytes_size, PAYLOAD_LEN); + AVS_UNIT_ASSERT_TRUE(message_finished); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_EOF); + } + for (size_t split = 4; split < 9; ++split) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, input_bytes, split, false)); + + fluf_cbor_ll_decoder_bytes_ctx_t *bytes_ctx = NULL; + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_bytes(&ctx, &bytes_ctx, + NULL), + FLUF_IO_WANT_NEXT_PAYLOAD); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, input_bytes + split, sizeof(input_bytes) - split, true)); + + size_t nesting_level; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 0); + + ptrdiff_t total_size; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_bytes(&ctx, &bytes_ctx, &total_size)); + AVS_UNIT_ASSERT_EQUAL(total_size, PAYLOAD_LEN); + const void *output_bytes = NULL; + size_t output_bytes_size; + bool message_finished; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_bytes_get_some(bytes_ctx, + &output_bytes, + &output_bytes_size, + &message_finished)); + AVS_UNIT_ASSERT_TRUE(output_bytes == &ctx.prebuffer[3]); + AVS_UNIT_ASSERT_EQUAL(output_bytes_size, sizeof(ctx.prebuffer) - 3); + AVS_UNIT_ASSERT_FALSE(message_finished); + + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_bytes_get_some(bytes_ctx, + &output_bytes, + &output_bytes_size, + &message_finished)); + AVS_UNIT_ASSERT_TRUE(output_bytes + == &input_bytes[sizeof(ctx.prebuffer)]); + AVS_UNIT_ASSERT_EQUAL(output_bytes_size, + PAYLOAD_LEN - sizeof(ctx.prebuffer) + 3); + AVS_UNIT_ASSERT_TRUE(message_finished); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_EOF); + } + for (size_t split = 9; split < sizeof(input_bytes); ++split) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, input_bytes, split, false)); + + fluf_cbor_ll_decoder_bytes_ctx_t *bytes_ctx = NULL; + ptrdiff_t total_size; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_bytes(&ctx, &bytes_ctx, &total_size)); + AVS_UNIT_ASSERT_EQUAL(total_size, PAYLOAD_LEN); + + const void *output_bytes = NULL; + size_t output_bytes_size; + bool message_finished; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_bytes_get_some(bytes_ctx, + &output_bytes, + &output_bytes_size, + &message_finished)); + AVS_UNIT_ASSERT_TRUE(output_bytes == &input_bytes[3]); + AVS_UNIT_ASSERT_EQUAL(output_bytes_size, split - 3); + AVS_UNIT_ASSERT_FALSE(message_finished); + + AVS_UNIT_ASSERT_EQUAL( + fluf_cbor_ll_decoder_bytes_get_some(bytes_ctx, + &output_bytes, + &output_bytes_size, + &message_finished), + FLUF_IO_WANT_NEXT_PAYLOAD); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, input_bytes + split, sizeof(input_bytes) - split, true)); + + size_t nesting_level; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 0); + + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_bytes_get_some(bytes_ctx, + &output_bytes, + &output_bytes_size, + &message_finished)); + AVS_UNIT_ASSERT_TRUE(output_bytes == &input_bytes[split]); + AVS_UNIT_ASSERT_EQUAL(output_bytes_size, PAYLOAD_LEN - split + 3); + AVS_UNIT_ASSERT_TRUE(message_finished); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_EOF); + } +} +# endif // FLUF_MAX_CBOR_NEST_STACK_SIZE > 0 + +# ifdef FLUF_WITH_CBOR_INDEFINITE_BYTES +AVS_UNIT_TEST(cbor_decoder_ll, bytes_indefinite_and_then_value_split) { + uint8_t input_bytes[256]; + uint8_t compare_buffer[256]; + for (size_t chunk1_size = 9; chunk1_size <= sizeof(input_bytes) - 7; + ++chunk1_size) { + size_t chunk2_size = sizeof(input_bytes) - chunk1_size - 7; + input_bytes[0] = 0x5F; // major-type=bytes, extended-length=indefinite + input_bytes[1] = 0x58; // major-type=bytes, extended-length=1byte + input_bytes[2] = (uint8_t) chunk1_size; + for (size_t i = 0; i < chunk1_size; ++i) { + input_bytes[i + 3] = (uint8_t) rand(); + } + // major-type=bytes, extended-length=1byte + input_bytes[chunk1_size + 3] = 0x58; + input_bytes[chunk1_size + 4] = (uint8_t) chunk2_size; + for (size_t i = 0; i < chunk2_size; ++i) { + input_bytes[chunk1_size + i + 5] = (uint8_t) rand(); + } + input_bytes[sizeof(input_bytes) - 2] = 0xFF; // indefinite end + input_bytes[sizeof(input_bytes) - 1] = 0x01; // integer + + for (size_t split = 0; split < sizeof(input_bytes); ++split) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, input_bytes, split, false)); + bool second_chunk_fed = false; + + fluf_cbor_ll_decoder_bytes_ctx_t *bytes_ctx = NULL; + ptrdiff_t total_size; + int result = + fluf_cbor_ll_decoder_bytes(&ctx, &bytes_ctx, &total_size); + if (result == FLUF_IO_WANT_NEXT_PAYLOAD) { + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, input_bytes + split, sizeof(input_bytes) - split, + true)); + second_chunk_fed = true; + result = fluf_cbor_ll_decoder_bytes(&ctx, &bytes_ctx, + &total_size); + } + AVS_UNIT_ASSERT_SUCCESS(result); + AVS_UNIT_ASSERT_EQUAL(total_size, + FLUF_CBOR_LL_DECODER_ITEMS_INDEFINITE); + + const void *output_bytes = NULL; + size_t output_bytes_size = 0; + bool message_finished = false; + uint8_t *compare_buffer_ptr = compare_buffer; + while (!message_finished) { + if ((result = fluf_cbor_ll_decoder_bytes_get_some( + bytes_ctx, &output_bytes, &output_bytes_size, + &message_finished)) + == FLUF_IO_WANT_NEXT_PAYLOAD) { + AVS_UNIT_ASSERT_FALSE(second_chunk_fed); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, input_bytes + split, + sizeof(input_bytes) - split, true)); + second_chunk_fed = true; + + size_t nesting_level; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_nesting_level( + &ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 0); + + result = fluf_cbor_ll_decoder_bytes_get_some( + bytes_ctx, &output_bytes, &output_bytes_size, + &message_finished); + } + AVS_UNIT_ASSERT_SUCCESS(result); + if (output_bytes_size) { + memcpy(compare_buffer_ptr, output_bytes, output_bytes_size); + compare_buffer_ptr += output_bytes_size; + } + } + + AVS_UNIT_ASSERT_EQUAL(compare_buffer_ptr - compare_buffer, + chunk1_size + chunk2_size); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(input_bytes + 3, compare_buffer, + chunk1_size); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(input_bytes + chunk1_size + 5, + compare_buffer + chunk1_size, + chunk2_size); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_UINT); + AVS_UNIT_ASSERT_EQUAL(value.value.u64, 1); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), + FLUF_IO_EOF); + } + } +} +# endif // FLUF_WITH_CBOR_INDEFINITE_BYTES + +# if FLUF_MAX_CBOR_NEST_STACK_SIZE > 0 +AVS_UNIT_TEST(cbor_decoder_ll, flat_array) { + // array [1u, 2u, 3u] + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + static const char data[] = "\x83\x01\x02\x03"; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(FLUF_CBOR_LL_VALUE_ARRAY, type); + + size_t nesting_level; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 0); + ptrdiff_t array_size = 0; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_enter_array(&ctx, &array_size)); + AVS_UNIT_ASSERT_EQUAL(array_size, 3); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 1); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_UINT); + AVS_UNIT_ASSERT_EQUAL(value.value.u64, 1); + + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 1); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_UINT); + AVS_UNIT_ASSERT_EQUAL(value.value.u64, 2); + + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 1); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_UINT); + AVS_UNIT_ASSERT_EQUAL(value.value.u64, 3); + + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 0); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_EOF); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_number(&ctx, &value), + FLUF_IO_ERR_LOGIC); +} + +AVS_UNIT_TEST(cbor_decoder_ll, flat_empty_array) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_feed_payload(&ctx, "\x80", 1, true)); + + size_t nesting_level; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 0); + ptrdiff_t array_size = 0; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_enter_array(&ctx, &array_size)); + AVS_UNIT_ASSERT_EQUAL(array_size, 0); + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 0); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_EOF); +} + +AVS_UNIT_TEST(cbor_decoder_ll, flat_empty_array_with_uint_afterwards) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_feed_payload(&ctx, "\x80\x01", 2, true)); + + size_t nesting_level; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 0); + ptrdiff_t array_size = 0; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_enter_array(&ctx, &array_size)); + AVS_UNIT_ASSERT_EQUAL(array_size, 0); + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 0); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_UINT); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_UINT); + AVS_UNIT_ASSERT_EQUAL(value.value.u64, 1); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_EOF); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_number(&ctx, &value), + FLUF_IO_ERR_LOGIC); +} +# endif // FLUF_MAX_CBOR_NEST_STACK_SIZE > 0 + +# if FLUF_MAX_CBOR_NEST_STACK_SIZE >= 2 +AVS_UNIT_TEST(cbor_decoder_ll, nested_array) { + // array [[1u,2u,3u], 4] + { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + static const char data_array_first[] = "\x82\x83\x01\x02\x03\x04"; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data_array_first, sizeof(data_array_first) - 1, true)); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_ARRAY); + + size_t nesting_level; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 0); + ptrdiff_t array_size = 0; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_enter_array(&ctx, &array_size)); + AVS_UNIT_ASSERT_EQUAL(array_size, 2); + + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 1); + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_enter_array(&ctx, &array_size)); + AVS_UNIT_ASSERT_EQUAL(array_size, 3); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 2); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_UINT); + AVS_UNIT_ASSERT_EQUAL(value.value.u64, 1); + + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 2); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_UINT); + AVS_UNIT_ASSERT_EQUAL(value.value.u64, 2); + + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 2); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_UINT); + AVS_UNIT_ASSERT_EQUAL(value.value.u64, 3); + + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 1); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_UINT); + AVS_UNIT_ASSERT_EQUAL(value.value.u64, 4); + + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 0); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_EOF); + } + + { + // array [1u, [2u, 3u, 4u]] + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + static const char data_array_last[] = "\x82\x01\x83\x02\x03\x04"; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data_array_last, sizeof(data_array_last) - 1, true)); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_ARRAY); + + size_t nesting_level; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 0); + ptrdiff_t array_size = 0; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_enter_array(&ctx, &array_size)); + AVS_UNIT_ASSERT_EQUAL(array_size, 2); + + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 1); + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_UINT); + AVS_UNIT_ASSERT_EQUAL(value.value.u64, 1); + + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 1); + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_enter_array(&ctx, &array_size)); + AVS_UNIT_ASSERT_EQUAL(array_size, 3); + + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 2); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_UINT); + AVS_UNIT_ASSERT_EQUAL(value.value.u64, 2); + + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 2); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_UINT); + AVS_UNIT_ASSERT_EQUAL(value.value.u64, 3); + + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 2); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_UINT); + AVS_UNIT_ASSERT_EQUAL(value.value.u64, 4); + + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 0); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_EOF); + } +} +# endif // FLUF_MAX_CBOR_NEST_STACK_SIZE >= 2 + +# if FLUF_MAX_CBOR_NEST_STACK_SIZE == 5 +AVS_UNIT_TEST(cbor_decoder_ll, array_too_many_nest_levels) { + // array [[[[[[[]]]]]]] + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + static const char data[] = "\x81\x81\x81\x81\x81\x81\x80"; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + + size_t nesting_level; + ptrdiff_t array_size = 0; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 0); + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_enter_array(&ctx, &array_size)); + AVS_UNIT_ASSERT_EQUAL(array_size, 1); + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 1); + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_enter_array(&ctx, &array_size)); + AVS_UNIT_ASSERT_EQUAL(array_size, 1); + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 2); + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_enter_array(&ctx, &array_size)); + AVS_UNIT_ASSERT_EQUAL(array_size, 1); + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 3); + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_enter_array(&ctx, &array_size)); + AVS_UNIT_ASSERT_EQUAL(array_size, 1); + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 4); + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_enter_array(&ctx, &array_size)); + AVS_UNIT_ASSERT_EQUAL(array_size, 1); + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 5); + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_enter_array(&ctx, &array_size), + FLUF_IO_ERR_FORMAT); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_ERR_FORMAT); +} +# endif // FLUF_MAX_CBOR_NEST_STACK_SIZE == 5 + +# if FLUF_MAX_CBOR_NEST_STACK_SIZE > 0 +AVS_UNIT_TEST(cbor_decoder_ll, array_too_large_size) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + // array(2^63) + static const char data[] = "\x9B\x80\x00\x00\x00\x00\x00\x00\x00"; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_enter_array(&ctx, NULL), + FLUF_IO_ERR_FORMAT); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_ERR_FORMAT); +} + +static const char *read_short_string(fluf_cbor_ll_decoder_t *ctx) { + static char short_string[128]; + fluf_cbor_ll_decoder_bytes_ctx_t *bytes_ctx = NULL; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_bytes(ctx, &bytes_ctx, NULL)); + const void *data = NULL; + size_t data_size; + bool message_finished; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_bytes_get_some( + bytes_ctx, &data, &data_size, &message_finished)); + AVS_UNIT_ASSERT_NOT_NULL(data); + AVS_UNIT_ASSERT_TRUE(data_size < sizeof(short_string)); + AVS_UNIT_ASSERT_TRUE(message_finished); + memcpy(short_string, data, data_size); + short_string[data_size] = '\0'; + return short_string; +} + +AVS_UNIT_TEST(cbor_decoder_ll, array_indefinite) { + // indefinite_array [ + // "Fun", + // "Stuff", + // ] + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + static const char data[] = "\x9F\x63" + "Fun" + "\x65" + "Stuff" + "\xFF"; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_ARRAY); + + ptrdiff_t array_size; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_enter_array(&ctx, &array_size)); + AVS_UNIT_ASSERT_EQUAL(array_size, FLUF_CBOR_LL_DECODER_ITEMS_INDEFINITE); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(read_short_string(&ctx), "Fun", + sizeof("Fun")); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(read_short_string(&ctx), "Stuff", + sizeof("Stuff")); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_EOF); +} + +AVS_UNIT_TEST(cbor_decoder_ll, indefinite_break_in_definite_array) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + static const char data[] = "\x81\xFF"; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(FLUF_CBOR_LL_VALUE_ARRAY, type); + + size_t nesting_level; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 0); + ptrdiff_t array_size; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_enter_array(&ctx, &array_size)); + AVS_UNIT_ASSERT_EQUAL(array_size, 1); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_current_value_type(&ctx, &type), + FLUF_IO_ERR_FORMAT); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_ERR_FORMAT); +} + +AVS_UNIT_TEST(cbor_decoder_ll, flat_map) { + // map { 42: 300 } + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + static const char data[] = "\xA1\x18\x2A\x19\x01\x2C"; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(FLUF_CBOR_LL_VALUE_MAP, type); + + size_t nesting_level; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 0); + ptrdiff_t pair_count; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_enter_map(&ctx, &pair_count)); + AVS_UNIT_ASSERT_EQUAL(pair_count, 1); + + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 1); + + fluf_cbor_ll_number_t key; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &key)); + AVS_UNIT_ASSERT_EQUAL(key.type, FLUF_CBOR_LL_VALUE_UINT); + AVS_UNIT_ASSERT_EQUAL(key.value.u64, 42); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_UINT); + AVS_UNIT_ASSERT_EQUAL(value.value.u64, 300); + + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 0); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_EOF); +} + +AVS_UNIT_TEST(cbor_decoder_ll, empty_map) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_feed_payload(&ctx, "\xA0", 1, true)); + ptrdiff_t pair_count; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_enter_map(&ctx, &pair_count)); + AVS_UNIT_ASSERT_EQUAL(pair_count, 0); + // We enter the map, and then we immediately exit it, because it is empty. + size_t nesting_level; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level)); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 0); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_EOF); +} +# endif // FLUF_MAX_CBOR_NEST_STACK_SIZE > 0 + +# ifdef FLUF_WITH_CBOR_HALF_FLOAT +# define TEST_HALF(Name, Value, Expected) \ + AVS_UNIT_TEST(cbor_decoder_ll, Name) { \ + fluf_cbor_ll_decoder_t ctx; \ + fluf_cbor_ll_decoder_init(&ctx); \ + static const char data[] = "\xF9" Value; \ + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( \ + &ctx, data, sizeof(data) - 1, true)); \ + fluf_cbor_ll_number_t value; \ + AVS_UNIT_ASSERT_SUCCESS( \ + fluf_cbor_ll_decoder_number(&ctx, &value)); \ + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_FLOAT); \ + AVS_UNIT_ASSERT_EQUAL(value.value.f32, Expected); \ + } + +TEST_HALF(half_float_value, "\x50\x00", 32.0f); +TEST_HALF(half_float_subnormal_value, "\x03\xFF", 6.097555160522461e-05f); +TEST_HALF(half_float_nan, "\x7E\x00", NAN); +TEST_HALF(half_float_inf, "\x7C\x00", INFINITY); +# endif // FLUF_WITH_CBOR_HALF_FLOAT + +AVS_UNIT_TEST(cbor_decoder_ll, half_float_premature_eof) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + static const char data[] = "\xF9\x50"; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_number(&ctx, &value), + FLUF_IO_ERR_FORMAT); +} + +# define TEST_FLOAT(Name, Value) \ + AVS_UNIT_TEST(cbor_decoder_ll, Name) { \ + fluf_cbor_ll_decoder_t ctx; \ + fluf_cbor_ll_decoder_init(&ctx); \ + char data[sizeof(float) + 1] = "\xFA"; \ + uint32_t encoded = avs_htonf(Value); \ + memcpy(data + 1, &encoded, sizeof(encoded)); \ + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( \ + &ctx, data, sizeof(data), true)); \ + fluf_cbor_ll_number_t value; \ + AVS_UNIT_ASSERT_SUCCESS( \ + fluf_cbor_ll_decoder_number(&ctx, &value)); \ + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_FLOAT); \ + AVS_UNIT_ASSERT_EQUAL(value.value.f32, Value); \ + } + +TEST_FLOAT(float_value, 32.0f); +TEST_FLOAT(float_nan, NAN); +TEST_FLOAT(float_inf, INFINITY); + +AVS_UNIT_TEST(cbor_decoder_ll, float_premature_eof) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + static const char data[] = "\xFA\x50"; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_number(&ctx, &value), + FLUF_IO_ERR_FORMAT); +} + +# define TEST_DOUBLE(Name, Value) \ + AVS_UNIT_TEST(cbor_decoder_ll, Name) { \ + fluf_cbor_ll_decoder_t ctx; \ + fluf_cbor_ll_decoder_init(&ctx); \ + char data[sizeof(double) + 1] = "\xFB"; \ + uint64_t encoded = avs_htond(Value); \ + memcpy(data + 1, &encoded, sizeof(encoded)); \ + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( \ + &ctx, data, sizeof(data), true)); \ + fluf_cbor_ll_number_t value; \ + AVS_UNIT_ASSERT_SUCCESS( \ + fluf_cbor_ll_decoder_number(&ctx, &value)); \ + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_DOUBLE); \ + AVS_UNIT_ASSERT_EQUAL(value.value.f64, Value); \ + } + +TEST_DOUBLE(double_value, 32.0); +TEST_DOUBLE(double_nan, NAN); +TEST_DOUBLE(double_inf, INFINITY); + +AVS_UNIT_TEST(cbor_decoder_ll, double_premature_eof) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + static const char data[] = "\xFB\x50"; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_number(&ctx, &value), + FLUF_IO_ERR_FORMAT); +} + +AVS_UNIT_TEST(cbor_decoder_ll, boolean_true_and_false) { + { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + static const char data[] = "\xF5"; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + bool value; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_bool(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value, true); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_EOF); + } + { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + static const char data[] = "\xF4"; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + bool value; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_bool(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value, false); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_EOF); + } +} + +AVS_UNIT_TEST(cbor_decoder_ll, boolean_integers_are_not_real_booleans) { + { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + static const char data[] = "\x00"; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + bool value; + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_bool(&ctx, &value), + FLUF_IO_ERR_FORMAT); + } + { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + static const char data[] = "\x01"; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + bool value; + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_bool(&ctx, &value), + FLUF_IO_ERR_FORMAT); + } +} + +AVS_UNIT_TEST(cbor_decoder_ll, null_value) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + static const char data[] = "\xF6"; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_null(&ctx)); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_EOF); +} + +AVS_UNIT_TEST(cbor_decoder_ll, undefined_value) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + static const char data[] = "\xF7"; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_current_value_type(&ctx, &type), + FLUF_IO_ERR_FORMAT); +} + +AVS_UNIT_TEST(cbor_decoder_ll, invalid_simple_value) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + static const char data[] = "\xF8"; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_current_value_type(&ctx, &type), + FLUF_IO_ERR_FORMAT); +} + +AVS_UNIT_TEST(cbor_decoder_ll, loose_indefinite_break) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + static const char data[] = "\xFF"; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_current_value_type(&ctx, &type), + FLUF_IO_ERR_FORMAT); +} + +# ifdef FLUF_WITH_CBOR_DECIMAL_FRACTIONS +static uint8_t make_header(cbor_major_type_t major_type, uint8_t value) { + return (uint8_t) ((((uint8_t) major_type) << 5) | value); +} + +static void encode_int(char **out_buffer, int64_t value) { + const uint64_t encoded = + avs_convert_be64((uint64_t) (value < 0 ? -(value + 1) : value)); + const uint8_t major_type = + value < 0 ? CBOR_MAJOR_TYPE_NEGATIVE_INT : CBOR_MAJOR_TYPE_UINT; + const uint8_t header = make_header(major_type, CBOR_EXT_LENGTH_8BYTE); + + memcpy(*out_buffer, &header, 1); + *out_buffer += 1; + memcpy(*out_buffer, &encoded, sizeof(encoded)); + *out_buffer += sizeof(encoded); +} + +# define TEST_TYPICAL_DECIMAL_FRACTION(Name, Exponent, Mantissa) \ + AVS_UNIT_TEST(cbor_decoder_ll, typical_decimal_##Name) { \ + /* Tag(4), Array [ Exponent, Mantissa ] */ \ + char data[2 + 2 * (sizeof(uint8_t) + sizeof(uint64_t))] = \ + "\xC4\x82"; \ + char *integers = &data[2]; \ + encode_int(&integers, (Exponent)); \ + encode_int(&integers, (Mantissa)); \ + fluf_cbor_ll_decoder_t ctx; \ + fluf_cbor_ll_decoder_init(&ctx); \ + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( \ + &ctx, data, sizeof(data), true)); \ + fluf_cbor_ll_number_t value; \ + AVS_UNIT_ASSERT_SUCCESS( \ + fluf_cbor_ll_decoder_number(&ctx, &value)); \ + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_DOUBLE); \ + AVS_UNIT_ASSERT_EQUAL(value.value.f64, \ + (Mantissa) *pow(10.0, (Exponent))); \ + } + +TEST_TYPICAL_DECIMAL_FRACTION(small, 2, 3); +TEST_TYPICAL_DECIMAL_FRACTION(small_negative_mantissa, 2, -3); +TEST_TYPICAL_DECIMAL_FRACTION(small_negative_exponent, -2, 3); +TEST_TYPICAL_DECIMAL_FRACTION(small_negative_exponent_and_mantissa, -2, -3); +TEST_TYPICAL_DECIMAL_FRACTION(big_exponent, 100, 2); +TEST_TYPICAL_DECIMAL_FRACTION(big_negative_exponent, -100, 2); +TEST_TYPICAL_DECIMAL_FRACTION(big_negative_exponent_and_mantissa, -100, -2); + +AVS_UNIT_TEST(cbor_decoder_ll, decimal_fraction_and_then_value) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + static const char data[] = "\xC4\x82\x02\x03\x04"; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_DOUBLE); + AVS_UNIT_ASSERT_EQUAL(value.value.f64, 300.0); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_UINT); + AVS_UNIT_ASSERT_EQUAL(value.value.u64, 4); +} + +AVS_UNIT_TEST(cbor_decoder_ll, decimal_fraction_and_then_value_split_payload) { + static const char data[] = "\xC4\x82" + "\x1B\x00\x00\x00\x00\x00\x00\x00\x02" + "\x1B\x00\x00\x00\x00\x00\x00\x00\x03" + "\x1B\x00\x00\x00\x00\x00\x00\x00\x04"; + for (size_t split = 0; split < sizeof(data) - 1; ++split) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_feed_payload(&ctx, data, split, false)); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_number(&ctx, &value), + FLUF_IO_WANT_NEXT_PAYLOAD); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data + split, sizeof(data) - 1 - split, true)); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_DOUBLE); + + size_t nesting_level; + fluf_cbor_ll_decoder_nesting_level(&ctx, &nesting_level); + AVS_UNIT_ASSERT_EQUAL(nesting_level, 0); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_DOUBLE); + AVS_UNIT_ASSERT_EQUAL(value.value.f64, 300.0); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_UINT); + AVS_UNIT_ASSERT_EQUAL(value.value.u64, 4); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_EOF); + } +} +# endif // FLUF_WITH_CBOR_DECIMAL_FRACTIONS + +AVS_UNIT_TEST(cbor_decoder_ll, decimal_fraction_invalid_length_1) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + static const char data[] = "\xC4\x81\x02"; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_number(&ctx, &value), + FLUF_IO_ERR_FORMAT); +} + +AVS_UNIT_TEST(cbor_decoder_ll, decimal_fraction_invalid_length_2) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + static const char data[] = "\xC4\x83\x02\x03\x04"; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_number(&ctx, &value), + FLUF_IO_ERR_FORMAT); +} + +AVS_UNIT_TEST(cbor_decoder_ll, decimal_fraction_invalid_length_and_then_value) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + static const char data[] = "\xC4\x81\x02\x03"; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_number(&ctx, &value), + FLUF_IO_ERR_FORMAT); +} + +AVS_UNIT_TEST(cbor_decoder_ll, decimal_fraction_invalid_inner_type) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + static const char data[] = "\xC4\x82\xF9\x03\xFF\x03"; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_number(&ctx, &value), + FLUF_IO_ERR_FORMAT); +} + +AVS_UNIT_TEST(cbor_decoder_ll, decimal_fraction_tag_after_tag) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + static const char data[] = "\xC4\xC4\x82\x02\x03"; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_number(&ctx, &value), + FLUF_IO_ERR_FORMAT); +} + +# ifdef FLUF_WITH_CBOR_DECIMAL_FRACTIONS +AVS_UNIT_TEST(cbor_decoder_ll, decimal_fraction_tag_but_no_data) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + static const char data[] = "\xC4"; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + + fluf_cbor_ll_value_type_t value_type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &value_type)); + AVS_UNIT_ASSERT_EQUAL(value_type, FLUF_CBOR_LL_VALUE_DOUBLE); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_number(&ctx, &value), + FLUF_IO_ERR_FORMAT); +} +# endif // FLUF_WITH_CBOR_DECIMAL_FRACTIONS + +# if FLUF_MAX_CBOR_NEST_STACK_SIZE > 0 +AVS_UNIT_TEST(cbor_decoder_ll, indefinite_map) { + // indefinite_map { + // "Fun": true, + // "Stuff": -2, + // } + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + static const char data[] = "\xBF\x63" + "Fun" + "\xF5\x65" + "Stuff" + "\x21\xFF"; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_MAP); + + ptrdiff_t total_size; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_enter_map(&ctx, &total_size)); + AVS_UNIT_ASSERT_EQUAL(total_size, FLUF_CBOR_LL_DECODER_ITEMS_INDEFINITE); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(read_short_string(&ctx), "Fun", + sizeof("Fun")); + bool value; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_bool(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value, true); + + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(read_short_string(&ctx), "Stuff", + sizeof("Stuff")); + fluf_cbor_ll_number_t number; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &number)); + AVS_UNIT_ASSERT_EQUAL(number.type, FLUF_CBOR_LL_VALUE_NEGATIVE_INT); + AVS_UNIT_ASSERT_EQUAL(number.value.i64, -2); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_EOF); +} + +AVS_UNIT_TEST(cbor_decoder_ll, indefinite_map_with_odd_number_of_items) { + // indefinite_map { + // "Fun": true, + // "Stuff": + // } + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + static const char data[] = "\xBF\x63" + "Fun" + "\xF5\x65" + "Stuff" + "\xFF"; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + + ptrdiff_t total_size; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_enter_map(&ctx, &total_size)); + AVS_UNIT_ASSERT_EQUAL(total_size, FLUF_CBOR_LL_DECODER_ITEMS_INDEFINITE); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(read_short_string(&ctx), "Fun", + sizeof("Fun")); + bool value; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_bool(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value, true); + + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(read_short_string(&ctx), "Stuff", + sizeof("Stuff")); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_ERR_FORMAT); +} + +AVS_UNIT_TEST(cbor_decoder_ll, map_too_large_size_1) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + // map(2^63) + static const char data[] = "\xBB\x80\x00\x00\x00\x00\x00\x00\x00"; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_enter_map(&ctx, NULL), + FLUF_IO_ERR_FORMAT); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_ERR_FORMAT); +} + +AVS_UNIT_TEST(cbor_decoder_ll, map_too_large_size_2) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + // map(2^62) + static const char data[] = "\xBB\x40\x00\x00\x00\x00\x00\x00\x00"; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_enter_map(&ctx, NULL), + FLUF_IO_ERR_FORMAT); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_ERR_FORMAT); +} +# endif // FLUF_MAX_CBOR_NEST_STACK_SIZE > 0 + +AVS_UNIT_TEST(cbor_decoder_ll, timestamp_uint) { + static const char data[] = "\xC1\x1B\xAA\xBB\xCC\xDD\x00\x11\x22\x33"; + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_errno(&ctx)); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_TIMESTAMP); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_UINT); + AVS_UNIT_ASSERT_EQUAL(value.value.u64, 0xAABBCCDD00112233ULL); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_EOF); +} + +AVS_UNIT_TEST(cbor_decoder_ll, timestamp_uint_split) { + static const char data[] = "\xC1\x1B\xAA\xBB\xCC\xDD\x00\x11\x22\x33"; + for (size_t split = 0; split < 9; ++split) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_feed_payload(&ctx, data, split, false)); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), + FLUF_IO_WANT_NEXT_PAYLOAD); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data + split, sizeof(data) - 1 - split, true)); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_errno(&ctx)); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_TIMESTAMP); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_UINT); + AVS_UNIT_ASSERT_EQUAL(value.value.u64, 0xAABBCCDD00112233ULL); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_EOF); + } + { + // split == 9 + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 2, false)); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_errno(&ctx)); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_TIMESTAMP); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_number(&ctx, &value), + FLUF_IO_WANT_NEXT_PAYLOAD); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data + sizeof(data) - 2, 1, true)); + + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_TIMESTAMP); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_UINT); + AVS_UNIT_ASSERT_EQUAL(value.value.u64, 0xAABBCCDD00112233ULL); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_EOF); + } + { + // split == 10 + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, false)); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_errno(&ctx)); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_TIMESTAMP); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_UINT); + AVS_UNIT_ASSERT_EQUAL(value.value.u64, 0xAABBCCDD00112233ULL); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), + FLUF_IO_WANT_NEXT_PAYLOAD); + + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_feed_payload(&ctx, NULL, 0, true)); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_EOF); + } +} + +AVS_UNIT_TEST(cbor_decoder_ll, timestamp_float) { + static const char data[] = "\xC1\xF9\x50\x00"; + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_errno(&ctx)); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_TIMESTAMP); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_FLOAT); + AVS_UNIT_ASSERT_EQUAL(value.value.f32, 32.0f); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_EOF); +} + +# ifdef FLUF_WITH_CBOR_DECIMAL_FRACTIONS +AVS_UNIT_TEST(cbor_decoder_ll, timestamp_in_decimal_fraction_illegal) { + static const char data[] = "\xC4\x82\x02\xC1\x03"; + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_errno(&ctx)); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_DOUBLE); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_number(&ctx, &value), + FLUF_IO_ERR_FORMAT); +} +# endif // FLUF_WITH_CBOR_DECIMAL_FRACTIONS + +AVS_UNIT_TEST(cbor_decoder_ll, decimal_fraction_in_timestamp_illegal) { + static const char data[] = "\xC1\xC4\x82\x02\x03"; + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_errno(&ctx)); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_TIMESTAMP); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_number(&ctx, &value), + FLUF_IO_ERR_FORMAT); +} + +# ifdef FLUF_WITH_CBOR_STRING_TIME +AVS_UNIT_TEST(cbor_decoder_ll, string_time_simple) { + static const char data[] = "\xC0\x74" + "2003-12-13T18:30:02Z"; + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_errno(&ctx)); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_TIMESTAMP); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_UINT); + AVS_UNIT_ASSERT_EQUAL(value.value.u64, 1071340202); +} + +AVS_UNIT_TEST(cbor_decoder_ll, string_time_with_fraction) { + static const char data[] = "\xC0\x77" + "2003-12-13T18:30:02.25Z"; + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_errno(&ctx)); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_TIMESTAMP); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_DOUBLE); + AVS_UNIT_ASSERT_EQUAL(value.value.f64, 1071340202.25); +} + +AVS_UNIT_TEST(cbor_decoder_ll, string_time_with_timezone) { + static const char data[] = "\xC0\x78\x19" + "2003-12-13T18:30:02+01:00"; + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_errno(&ctx)); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_TIMESTAMP); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_UINT); + AVS_UNIT_ASSERT_EQUAL(value.value.u64, 1071336602); +} + +AVS_UNIT_TEST(cbor_decoder_ll, string_time_with_fraction_and_timezone) { + static const char data[] = "\xC0\x78\x1C" + "2003-12-13T18:30:02.25+01:00"; + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_errno(&ctx)); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_TIMESTAMP); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_DOUBLE); + AVS_UNIT_ASSERT_EQUAL(value.value.f64, 1071336602.25); +} + +AVS_UNIT_TEST(cbor_decoder_ll, string_time_leap_year) { + static const char data[] = "\xC0\x74" + "2004-12-13T18:30:02Z"; + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_errno(&ctx)); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_TIMESTAMP); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_UINT); + AVS_UNIT_ASSERT_EQUAL(value.value.u64, 1102962602); +} + +AVS_UNIT_TEST(cbor_decoder_ll, string_time_max_length) { + static const char data[] = "\xC0\x78\x23" + "2024-01-16T13:22:40.763933581+01:00"; + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_errno(&ctx)); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_TIMESTAMP); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_DOUBLE); + AVS_UNIT_ASSERT_EQUAL(value.value.f64, 1705407760.763933581); +} + +AVS_UNIT_TEST(cbor_decoder_ll, string_time_too_long) { + static const char data[] = "\xC0\x78\x24" + "2024-01-16T13:22:40.7639335809+01:00"; + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_errno(&ctx)); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_TIMESTAMP); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_number(&ctx, &value), + FLUF_IO_ERR_FORMAT); +} + +AVS_UNIT_TEST(cbor_decoder_ll, string_time_garbled_input) { + static const char data[] = "\xC0\x78\x23" + "2024-01-16T13:22:40.763933581+01:00"; + for (size_t i = 3; i < sizeof(data) - 1; ++i) { + char garbled_data[sizeof(data)]; + memcpy(garbled_data, data, sizeof(data)); + garbled_data[i] = 'x'; + + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, garbled_data, sizeof(garbled_data) - 1, true)); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_errno(&ctx)); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_TIMESTAMP); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_number(&ctx, &value), + FLUF_IO_ERR_FORMAT); + } +} + +AVS_UNIT_TEST(cbor_decoder_ll, string_time_limits) { + struct { + const char *data; + fluf_cbor_ll_value_type_t type; + double value; + } data[] = { { "0000-01-01T00:00:00.000000000-99:59", + FLUF_CBOR_LL_VALUE_NEGATIVE_INT, -62166859260 }, + { "0000-01-01T00:00:00.000000000-00:00", + FLUF_CBOR_LL_VALUE_NEGATIVE_INT, -62167219200 }, + { "0000-01-01T00:00:00.000000000+00:00", + FLUF_CBOR_LL_VALUE_NEGATIVE_INT, -62167219200 }, + { "0000-01-01T00:00:00.000000000+99:59", + FLUF_CBOR_LL_VALUE_NEGATIVE_INT, -62167579140 }, + { "9999-12-31T23:59:60.999999999-99:59", + FLUF_CBOR_LL_VALUE_DOUBLE, 253402660740.999999999 }, + { "9999-12-31T23:59:60.999999999-00:00", + FLUF_CBOR_LL_VALUE_DOUBLE, 253402300800.999999999 }, + { "9999-12-31T23:59:60.999999999+00:00", + FLUF_CBOR_LL_VALUE_DOUBLE, 253402300800.999999999 }, + { "9999-12-31T23:59:60.999999999+99:59", + FLUF_CBOR_LL_VALUE_DOUBLE, 253401940860.999999999 } }; + for (size_t i = 0; i < AVS_ARRAY_SIZE(data); ++i) { + uint8_t buf[258] = "\xC0\x78"; + buf[2] = (uint8_t) strlen(data[i].data); + memcpy(buf + 3, data[i].data, buf[2]); + + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_feed_payload(&ctx, buf, buf[2] + 3, true)); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_errno(&ctx)); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_TIMESTAMP); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, data[i].type); + switch (value.type) { + case FLUF_CBOR_LL_VALUE_NEGATIVE_INT: + AVS_UNIT_ASSERT_EQUAL(value.value.i64, (int64_t) data[i].value); + break; + case FLUF_CBOR_LL_VALUE_DOUBLE: + AVS_UNIT_ASSERT_EQUAL(value.value.f64, data[i].value); + break; + default: + AVS_UNIT_ASSERT_TRUE(false); + } + } +} + +AVS_UNIT_TEST(cbor_decoder_ll, string_time_out_of_limits) { + const char *data[] = { "2024-00-16T13:22:40.763933581+01:00", + "2024-13-16T13:22:40.763933581+01:00", + "2024-01-00T13:22:40.763933581+01:00", + "2024-01-32T13:22:40.763933581+01:00", + "2024-01-16T25:22:40.763933581+01:00", + "2024-01-16T13:60:40.763933581+01:00", + "2024-01-16T13:22:61.763933581+01:00", + "2024-01-16T13:22:40.763933581+01:60" }; + for (size_t i = 0; i < AVS_ARRAY_SIZE(data); ++i) { + uint8_t buf[258] = "\xC0\x78"; + buf[2] = (uint8_t) strlen(data[i]); + memcpy(buf + 3, data[i], buf[2]); + + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_feed_payload(&ctx, buf, buf[2] + 3, true)); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_errno(&ctx)); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_TIMESTAMP); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_number(&ctx, &value), + FLUF_IO_ERR_FORMAT); + } +} + +AVS_UNIT_TEST(cbor_decoder_ll, string_time_superfluous_data) { + static const char data[] = "\xC0\x75" + "2003-12-13T18:30:02Z0"; + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_errno(&ctx)); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_TIMESTAMP); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_number(&ctx, &value), + FLUF_IO_ERR_FORMAT); +} + +AVS_UNIT_TEST(cbor_decoder_ll, string_time_split) { + static const char data[] = "\xC0\x78\x23" + "2024-01-16T13:22:40.763933581+01:00"; + for (size_t split = 0; split < 9; ++split) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_feed_payload(&ctx, data, split, false)); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), + FLUF_IO_WANT_NEXT_PAYLOAD); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data + split, sizeof(data) - 1 - split, true)); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_errno(&ctx)); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_TIMESTAMP); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_DOUBLE); + AVS_UNIT_ASSERT_EQUAL(value.value.f64, 1705407760.763933581); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_EOF); + } + for (size_t split = 9; split < 38; ++split) { + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_feed_payload(&ctx, data, split, false)); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_errno(&ctx)); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_TIMESTAMP); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_number(&ctx, &value), + FLUF_IO_WANT_NEXT_PAYLOAD); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data + split, sizeof(data) - 1 - split, true)); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_DOUBLE); + AVS_UNIT_ASSERT_EQUAL(value.value.f64, 1705407760.763933581); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_EOF); + } + { + // split == sizeof(data) - 1 + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, false)); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_errno(&ctx)); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_TIMESTAMP); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_number(&ctx, &value)); + AVS_UNIT_ASSERT_EQUAL(value.type, FLUF_CBOR_LL_VALUE_DOUBLE); + AVS_UNIT_ASSERT_EQUAL(value.value.f64, 1705407760.763933581); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), + FLUF_IO_WANT_NEXT_PAYLOAD); + + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_feed_payload(&ctx, NULL, 0, true)); + + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_errno(&ctx), FLUF_IO_EOF); + } +} + +AVS_UNIT_TEST(cbor_decoder_ll, string_time_wrong_type) { + static const char data[] = "\xC0\x54" + "2003-12-13T18:30:02Z"; + fluf_cbor_ll_decoder_t ctx; + fluf_cbor_ll_decoder_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_feed_payload( + &ctx, data, sizeof(data) - 1, true)); + + AVS_UNIT_ASSERT_SUCCESS(fluf_cbor_ll_decoder_errno(&ctx)); + + fluf_cbor_ll_value_type_t type; + AVS_UNIT_ASSERT_SUCCESS( + fluf_cbor_ll_decoder_current_value_type(&ctx, &type)); + AVS_UNIT_ASSERT_EQUAL(type, FLUF_CBOR_LL_VALUE_TIMESTAMP); + + fluf_cbor_ll_number_t value; + AVS_UNIT_ASSERT_EQUAL(fluf_cbor_ll_decoder_number(&ctx, &value), + FLUF_IO_ERR_FORMAT); +} +# endif // FLUF_WITH_CBOR_STRING_TIME + +#endif // defined(FLUF_WITH_SENML_CBOR) || defined(FLUF_WITH_LWM2M_CBOR) || + // defined(FLUF_WITH_CBOR) diff --git a/tests/fluf/cbor_encoder.c b/tests/fluf/cbor_encoder.c new file mode 100644 index 00000000..1989bffb --- /dev/null +++ b/tests/fluf/cbor_encoder.c @@ -0,0 +1,480 @@ +/* + * Copyright 2023-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 + +typedef struct { + fluf_io_out_ctx_t ctx; + fluf_io_out_entry_t entry; + char buf[300]; + size_t buffer_length; + size_t out_length; +} cbor_test_env_t; + +static void cbor_test_setup(cbor_test_env_t *env) { + memset(env, 0, sizeof(cbor_test_env_t)); + env->buffer_length = sizeof(env->buf); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_out_ctx_init( + &env->ctx, FLUF_OP_DM_READ, NULL, 1, FLUF_COAP_FORMAT_NOT_DEFINED)); + AVS_UNIT_ASSERT_EQUAL(fluf_io_out_ctx_get_format(&env->ctx), + FLUF_COAP_FORMAT_CBOR); +} + +typedef struct { + const char *data; + size_t size; +} test_data_t; + +#define MAKE_TEST_DATA(Data) \ + (test_data_t) { \ + .data = Data, \ + .size = sizeof(Data) - 1 \ + } + +static void verify_bytes(cbor_test_env_t *env, test_data_t *data) { + AVS_UNIT_ASSERT_EQUAL(env->out_length, data->size); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(env->buf, data->data, data->size); +} + +static void test_simple_variable(test_data_t *data, + fluf_io_out_entry_t *value) { + cbor_test_env_t env; + cbor_test_setup(&env); + + env.entry = *value; + + AVS_UNIT_ASSERT_SUCCESS(fluf_io_out_ctx_new_entry(&env.ctx, &env.entry)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_out_ctx_get_payload( + &env.ctx, env.buf, env.buffer_length, &env.out_length)); + verify_bytes(&env, data); +} + +#define TEST_INT_IMPL(Name, Num, Data) \ + AVS_UNIT_TEST(cbor_encoder, Name) { \ + test_data_t data = MAKE_TEST_DATA(Data); \ + fluf_io_out_entry_t value = { 0 }; \ + value.type = FLUF_DATA_TYPE_INT; \ + value.value.int_value = Num; \ + test_simple_variable(&data, &value); \ + } + +#define TEST_INT(Num, Data) TEST_INT_IMPL(AVS_CONCAT(int, __LINE__), Num, Data); + +TEST_INT(0, "\x00") +TEST_INT(1, "\x01") +TEST_INT(10, "\x0A") +TEST_INT(23, "\x17") +TEST_INT(24, "\x18\x18") +TEST_INT(25, "\x18\x19") +TEST_INT(100, "\x18\x64") +TEST_INT(221, "\x18\xDD") +TEST_INT(1000, "\x19\x03\xE8") +TEST_INT(INT16_MAX, "\x19\x7F\xFF") +TEST_INT(INT16_MAX + 1, "\x19\x80\x00") +TEST_INT(UINT16_MAX, "\x19\xFF\xFF") +TEST_INT(UINT16_MAX + 1, "\x1A\x00\x01\x00\x00") +TEST_INT(1000000, "\x1A\x00\x0F\x42\x40") +TEST_INT(INT32_MAX, "\x1A\x7F\xFF\xFF\xFF") +TEST_INT((int64_t) INT32_MAX + 1, "\x1A\x80\x00\x00\x00") +TEST_INT(UINT32_MAX, "\x1A\xFF\xFF\xFF\xFF") +TEST_INT((int64_t) UINT32_MAX + 1, "\x1B\x00\x00\x00\x01\x00\x00\x00\x00") +TEST_INT(INT64_MAX, "\x1B\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF") + +TEST_INT(-1, "\x20") +TEST_INT(-10, "\x29") +TEST_INT(-24, "\x37") +TEST_INT(-25, "\x38\x18") +TEST_INT(-100, "\x38\x63") +TEST_INT(-256, "\x38\xFF") +TEST_INT(-257, "\x39\x01\x00") +TEST_INT(-1000, "\x39\x03\xE7") +TEST_INT(INT64_MIN, "\x3B\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF") + +#define TEST_TIME_IMPL(Name, Num, Data) \ + AVS_UNIT_TEST(cbor_encoder, Name) { \ + test_data_t data = MAKE_TEST_DATA(Data); \ + fluf_io_out_entry_t value = { 0 }; \ + value.type = FLUF_DATA_TYPE_TIME; \ + value.value.time_value = Num; \ + test_simple_variable(&data, &value); \ + } + +#define TEST_TIME(Num, Data) \ + TEST_TIME_IMPL(AVS_CONCAT(time_, __LINE__), Num, Data); + +TEST_TIME(24, "\xC1\x18\x18") +TEST_TIME(UINT32_MAX, "\xC1\x1A\xFF\xFF\xFF\xFF") + +#define TEST_BOOL_IMPL(Name, Num, Data) \ + AVS_UNIT_TEST(cbor_encoder, Name) { \ + test_data_t data = MAKE_TEST_DATA(Data); \ + fluf_io_out_entry_t value = { 0 }; \ + value.type = FLUF_DATA_TYPE_BOOL; \ + value.value.bool_value = Num; \ + test_simple_variable(&data, &value); \ + } + +#define TEST_BOOL(Num, Data) \ + TEST_BOOL_IMPL(AVS_CONCAT(bool, __LINE__), Num, Data); + +TEST_BOOL(true, "\xF5") +TEST_BOOL(false, "\xF4") + +#define TEST_DOUBLE_IMPL(Name, Num, Data) \ + AVS_UNIT_TEST(cbor_encoder, Name) { \ + test_data_t data = MAKE_TEST_DATA(Data); \ + fluf_io_out_entry_t value = { 0 }; \ + value.type = FLUF_DATA_TYPE_DOUBLE; \ + value.value.double_value = Num; \ + test_simple_variable(&data, &value); \ + } + +#define TEST_DOUBLE(Num, Data) \ + TEST_DOUBLE_IMPL(AVS_CONCAT(double, __LINE__), Num, Data); + +TEST_DOUBLE(-0.0, "\xFA\x80\x00\x00\x00") +TEST_DOUBLE(100000.0, "\xFA\x47\xC3\x50\x00") + +TEST_DOUBLE(1.1, "\xFB\x3F\xF1\x99\x99\x99\x99\x99\x9A") +TEST_DOUBLE(100000.0, "\xFA\x47\xC3\x50\x00") +TEST_DOUBLE(1.0e+300, "\xFB\x7E\x37\xE4\x3C\x88\x00\x75\x9C") +TEST_DOUBLE(-4.1, "\xFB\xC0\x10\x66\x66\x66\x66\x66\x66") + +#define TEST_OBJLINK_IMPL(Name, Oid, Iid, Data) \ + AVS_UNIT_TEST(cbor_encoder, Name) { \ + test_data_t data = MAKE_TEST_DATA(Data); \ + fluf_io_out_entry_t value = { 0 }; \ + value.type = FLUF_DATA_TYPE_OBJLNK; \ + value.value.objlnk.oid = Oid; \ + value.value.objlnk.iid = Iid; \ + test_simple_variable(&data, &value); \ + } + +#define TEST_OBJLINK(Oid, Iid, Data) \ + TEST_OBJLINK_IMPL(AVS_CONCAT(objlink, __LINE__), Oid, Iid, Data); + +TEST_OBJLINK(0, 0, "\x63\x30\x3A\x30"); +TEST_OBJLINK(1, 1, "\x63\x31\x3A\x31"); +TEST_OBJLINK(2, 0, "\x63\x32\x3A\x30"); +TEST_OBJLINK(0, 5, "\x63\x30\x3A\x35"); +TEST_OBJLINK(2, 13, "\x64\x32\x3A\x31\x33"); +TEST_OBJLINK(21, 37, "\x65\x32\x31\x3A\x33\x37"); +TEST_OBJLINK(2137, 1, "\x66\x32\x31\x33\x37\x3A\x31"); +TEST_OBJLINK(1111, 2222, "\x69\x31\x31\x31\x31\x3A\x32\x32\x32\x32"); +TEST_OBJLINK(11111, 50001, "\x6B\x31\x31\x31\x31\x31\x3A\x35\x30\x30\x30\x31"); +TEST_OBJLINK(0, 60001, "\x67\x30\x3A\x36\x30\x30\x30\x31"); + +static void test_string(test_data_t *data, fluf_io_out_entry_t *value) { + cbor_test_env_t env; + cbor_test_setup(&env); + + env.entry = *value; + + AVS_UNIT_ASSERT_SUCCESS(fluf_io_out_ctx_new_entry(&env.ctx, &env.entry)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_out_ctx_get_payload( + &env.ctx, env.buf, env.buffer_length, &env.out_length)); + verify_bytes(&env, data); +}; + +#define TEST_STRING_NAMED(Name, Text, ExpectedHeader) \ + AVS_UNIT_TEST(cbor_encoder, string_##Name) { \ + test_data_t expected = MAKE_TEST_DATA(ExpectedHeader Text); \ + fluf_io_out_entry_t value = { 0 }; \ + value.type = FLUF_DATA_TYPE_STRING; \ + value.value.bytes_or_string.data = Text; \ + test_string(&expected, &value); \ + } + +#define TEST_STRING(Text, ExpectedHeader) \ + TEST_STRING_NAMED(Text, AVS_QUOTE(Text), ExpectedHeader) + +TEST_STRING(, "\x60") +TEST_STRING(a, "\x61") +TEST_STRING(1111, "\x64") +TEST_STRING_NAMED(dzborg, "DZBORG:DD", "\x69") +TEST_STRING_NAMED(escaped, "\"\\", "\x62") +TEST_STRING_NAMED( + 255chars, + "oxazxnwrmthhloqwchkumektviptdztidxeelvgffcdoodpijsbikkkvrmtrxddmpidudj" + "ptfmqqgfkjlrsqrmagculcyjjbmxombbiqdhimwafcfaswhmmykezictjpidmxtoqnjmja" + "xzgvqdybtgneqsmlzhxqeuhibjopnregwykgpcdogguszhhffdeixispwfnwcufnmsxycy" + "qxquiqsuqwgkwafkeedsacxvvjwhpokaabxelqxzqutwa", + "\x78\xFF") +TEST_STRING_NAMED( + 256chars, + "oqndmcvrgmvswuvcskllakhhersslftmmuwwwzirelnbtnlmvmezrqktqqnlpldqwyvtbv" + "yryqcurqxnhzxoladzzmnumrifhqbcywuetmuyyjxpiwquzrekjxzgiknqcmwzwuzxvrxb" + "zycnfrhyigwgkmbtlfyrhkolnsikvdelvkztkvonimtmvrivrnevgyxvjdjzvobsiufbwt" + "atfqeavhvfdfbnsumtletbaheyacrkwgectlrdrizenuvi", + "\x79\x01\x00") + +#define TEST_BYTES(Name, Data, ExpectedHeader) \ + AVS_UNIT_TEST(cbor_encoder, bytes_##Name) { \ + fluf_io_out_entry_t value = { 0 }; \ + value.type = FLUF_DATA_TYPE_BYTES; \ + value.value.bytes_or_string.chunk_length = sizeof(Data) - 1; \ + value.value.bytes_or_string.data = Data; \ + test_data_t expected = MAKE_TEST_DATA(ExpectedHeader Data); \ + test_string(&expected, &value); \ + } + +TEST_BYTES(0bytes, "", "\x40") +TEST_BYTES(4bytes, "\x01\x02\x03\x04", "\x44") +TEST_BYTES(5bytes, "\x64\x49\x45\x54\x46", "\x45") +TEST_BYTES(23bytes, + "\x84\x11\xDB\xB8\xAA\xF7\xC3\xEF\xBA\xC0\x2F\x50\xC2\x88\xAF\x1B" + "\x8F\xD2\xE4\xC9\x5A\xD7\xEC", + "\x57") +TEST_BYTES(24bytes, + "\x46\x0A\x00\x2D\xC0\x68\xD4\xE5\x8D\xDC\x37\x5D\xF0\x83\xCD\xD8" + "\x3F\xAC\x35\x03\x16\x1E\x32\x0A", + "\x58\x18") +TEST_BYTES( + 255bytes, + "\xD6\xFB\x20\x80\xCE\x44\x31\x3B\xE1\x63\xD9\x89\x36\x90\x06\x56\x9C" + "\xF6\x4C\x24\x04\x34\xEA\x8D\xF3\xF1\x40\xEA\x3A\x41\xE1\x57\xFF\x92" + "\xCC\xAE\x42\x10\x27\x48\x47\x6E\x7C\x11\x9B\x5A\x21\x5A\x51\xF7\x45" + "\xB0\x5E\x3B\x81\x26\xE9\xB0\x8A\xF1\x93\xCA\xA6\xB3\xD7\xE0\x16\xEC" + "\xBF\xF5\x21\x16\xC7\x50\x6C\x9A\xA8\x8E\x49\xA9\xF1\x59\x8C\xC3\x80" + "\x0F\x34\x21\x26\xCD\xB5\x30\xEE\xC5\x48\xBB\x6F\x03\x62\xC2\x7B\x21" + "\x60\x08\xE2\x58\xD3\xE0\x64\x3A\x4B\x59\x16\xFD\x8E\x05\x41\x46\xBD" + "\xFB\xC8\x7B\x4D\xC3\x38\x01\x94\x31\x50\xFC\xE7\xBE\x7A\xDA\xD6\x56" + "\x74\x1C\x7F\x75\xB1\x59\x15\x4E\x86\x8E\x71\xB0\xFF\x69\x60\xDC\xBC" + "\x52\xB6\xEA\xFA\x4E\x09\xD3\xB8\x40\x85\x7D\xDA\xB1\xC8\xFF\x65\xB7" + "\xFF\xA9\xAB\x9E\x67\x04\x0A\x3A\x1B\xE7\x77\x53\x9A\xA1\x6D\xDA\xA0" + "\xBB\xC0\x91\xA1\x38\x93\x0E\x33\xDF\x4B\x9E\x83\x0C\xF4\x73\x1E\xD6" + "\x83\x92\x54\x3D\x73\x1F\xEC\xCA\xD9\x1F\xE2\x3D\x57\xD1\x7C\x54\x88" + "\xFB\x3E\xCF\x7E\x8A\x29\x98\x89\x4A\xBB\x2F\xE5\xB1\x36\x2B\x8B\x8F" + "\xBF\x46\x19\x74\x1D\xC4\x7B\xFB\x52\xA4\x32\x47\xA7\x5C\xA1\x5C\x1A", + "\x58\xFF") +TEST_BYTES(256bytes, + "\xD8\xE2\xE6\xED\x90\x05\x29\x3B\x17\xAC\x8D\x33\x93\x52\xD9\x6B" + "\xF2\xFB\x20\x74\x3E\x9C\xEF\xAD\xBB\x03\xCE\x0E\xC5\xBD\x0D\x2F" + "\x42\x6D\x1C\xD6\xDB\x29\xF8\xF6\xA4\x96\x3D\x7A\x8A\xEE\xE6\xF2" + "\x56\x1C\xBE\xCE\x71\x30\x3B\xEC\xC9\x86\x71\x96\x86\x51\xA2\xCA" + "\x23\x8A\x0B\x1D\x67\x3C\x50\xB8\x66\x4C\x64\x8C\x31\xCD\x11\x05" + "\xCA\x56\x4B\xBB\x79\x18\x8F\x5B\xF1\xE0\x1E\x85\x38\xBE\x7A\x6F" + "\x30\x4A\xFD\xB3\x1B\xA9\x52\xB4\x0E\x95\x73\x83\xA5\x33\x9F\x0C" + "\x04\x2E\x33\xB3\xD5\x0B\x6E\x02\x0C\xC7\x0D\x1A\x1A\x48\x0C\x92" + "\x1B\x62\x83\xCF\xC1\x5C\x90\xBC\x83\x3B\x92\xBF\x8E\xCE\x7C\xD6" + "\x99\x77\xF2\x66\x92\x0C\xC6\x0A\x11\x80\xBE\x03\x59\x23\x89\xF6" + "\xEF\x3A\x5A\x07\xEB\xEF\x47\xF0\x1F\xF0\xB4\x96\x01\x1B\xE9\x51" + "\x40\x70\x16\xDD\xB2\x9B\xEB\x42\xAC\x6E\x45\xE6\xAE\x8F\xCE\x9A" + "\xC4\xCB\x09\xE7\x2C\xE4\x48\x86\xF0\x9C\x56\x2C\xEF\x1B\xD0\x8E" + "\x92\xD4\x61\x15\x46\x76\x19\x32\xDF\x9F\x98\xC0\x0A\xF7\xAE\xA9" + "\xD7\x61\xEC\x8B\x78\xE5\xAA\xC6\x0B\x5D\x98\x1D\x86\xE6\x57\x67" + "\x97\x56\x82\x29\xFF\x8F\x61\x6C\xA5\xD0\x08\x20\xAE\x49\x5B\x04", + "\x59\x01\x00") + +static char *ptr_for_callback = NULL; +static int external_data_handler(void *buffer, + size_t bytes_to_copy, + size_t offset, + void *args) { + (void) args; + memcpy(buffer, &ptr_for_callback[offset], bytes_to_copy); + return 0; +} + +#define TEST_STRING_EXT(Name, Text, ExpectedHeader) \ + AVS_UNIT_TEST(cbor_encoder, string_ext_##Name) { \ + test_data_t expected = MAKE_TEST_DATA(ExpectedHeader Text); \ + fluf_io_out_entry_t value = { 0 }; \ + value.type = FLUF_DATA_TYPE_EXTERNAL_STRING; \ + value.value.external_data.get_external_data = external_data_handler; \ + value.value.external_data.length = sizeof(Text) - 1; \ + ptr_for_callback = Text; \ + test_string(&expected, &value); \ + } + +#define TEST_BYTES_EXT(Name, Text, ExpectedHeader) \ + AVS_UNIT_TEST(cbor_encoder, bytes_ext_##Name) { \ + test_data_t expected = MAKE_TEST_DATA(ExpectedHeader Text); \ + fluf_io_out_entry_t value = { 0 }; \ + value.type = FLUF_DATA_TYPE_EXTERNAL_BYTES; \ + value.value.external_data.get_external_data = external_data_handler; \ + value.value.external_data.length = sizeof(Text) - 1; \ + ptr_for_callback = Text; \ + test_string(&expected, &value); \ + } + +TEST_STRING_EXT(empty, "", "\x60") +TEST_STRING_EXT(a, "a", "\x61") +TEST_STRING_EXT(ononeone, "1111", "\x64") +TEST_STRING_EXT(dzborg, "DZBORG:DD", "\x69") +TEST_STRING_EXT(escaped, "\"\\", "\x62") +TEST_STRING_EXT( + 255chars, + "oxazxnwrmthhloqwchkumektviptdztidxeelvgffcdoodpijsbikkkvrmtrxddmpidudj" + "ptfmqqgfkjlrsqrmagculcyjjbmxombbiqdhimwafcfaswhmmykezictjpidmxtoqnjmja" + "xzgvqdybtgneqsmlzhxqeuhibjopnregwykgpcdogguszhhffdeixispwfnwcufnmsxycy" + "qxquiqsuqwgkwafkeedsacxvvjwhpokaabxelqxzqutwa", + "\x78\xFF") +TEST_STRING_EXT( + 256chars, + "oqndmcvrgmvswuvcskllakhhersslftmmuwwwzirelnbtnlmvmezrqktqqnlpldqwyvtbv" + "yryqcurqxnhzxoladzzmnumrifhqbcywuetmuyyjxpiwquzrekjxzgiknqcmwzwuzxvrxb" + "zycnfrhyigwgkmbtlfyrhkolnsikvdelvkztkvonimtmvrivrnevgyxvjdjzvobsiufbwt" + "atfqeavhvfdfbnsumtletbaheyacrkwgectlrdrizenuvi", + "\x79\x01\x00") + +TEST_BYTES_EXT(0bytes, "", "\x40") +TEST_BYTES_EXT(4bytes, "\x01\x02\x03\x04", "\x44") +TEST_BYTES_EXT(5bytes, "\x64\x49\x45\x54\x46", "\x45") +TEST_BYTES_EXT( + 23bytes, + "\x84\x11\xDB\xB8\xAA\xF7\xC3\xEF\xBA\xC0\x2F\x50\xC2\x88\xAF\x1B" + "\x8F\xD2\xE4\xC9\x5A\xD7\xEC", + "\x57") +TEST_BYTES_EXT( + 24bytes, + "\x46\x0A\x00\x2D\xC0\x68\xD4\xE5\x8D\xDC\x37\x5D\xF0\x83\xCD\xD8" + "\x3F\xAC\x35\x03\x16\x1E\x32\x0A", + "\x58\x18") +TEST_BYTES_EXT( + 255bytes, + "\xD6\xFB\x20\x80\xCE\x44\x31\x3B\xE1\x63\xD9\x89\x36\x90\x06\x56\x9C" + "\xF6\x4C\x24\x04\x34\xEA\x8D\xF3\xF1\x40\xEA\x3A\x41\xE1\x57\xFF\x92" + "\xCC\xAE\x42\x10\x27\x48\x47\x6E\x7C\x11\x9B\x5A\x21\x5A\x51\xF7\x45" + "\xB0\x5E\x3B\x81\x26\xE9\xB0\x8A\xF1\x93\xCA\xA6\xB3\xD7\xE0\x16\xEC" + "\xBF\xF5\x21\x16\xC7\x50\x6C\x9A\xA8\x8E\x49\xA9\xF1\x59\x8C\xC3\x80" + "\x0F\x34\x21\x26\xCD\xB5\x30\xEE\xC5\x48\xBB\x6F\x03\x62\xC2\x7B\x21" + "\x60\x08\xE2\x58\xD3\xE0\x64\x3A\x4B\x59\x16\xFD\x8E\x05\x41\x46\xBD" + "\xFB\xC8\x7B\x4D\xC3\x38\x01\x94\x31\x50\xFC\xE7\xBE\x7A\xDA\xD6\x56" + "\x74\x1C\x7F\x75\xB1\x59\x15\x4E\x86\x8E\x71\xB0\xFF\x69\x60\xDC\xBC" + "\x52\xB6\xEA\xFA\x4E\x09\xD3\xB8\x40\x85\x7D\xDA\xB1\xC8\xFF\x65\xB7" + "\xFF\xA9\xAB\x9E\x67\x04\x0A\x3A\x1B\xE7\x77\x53\x9A\xA1\x6D\xDA\xA0" + "\xBB\xC0\x91\xA1\x38\x93\x0E\x33\xDF\x4B\x9E\x83\x0C\xF4\x73\x1E\xD6" + "\x83\x92\x54\x3D\x73\x1F\xEC\xCA\xD9\x1F\xE2\x3D\x57\xD1\x7C\x54\x88" + "\xFB\x3E\xCF\x7E\x8A\x29\x98\x89\x4A\xBB\x2F\xE5\xB1\x36\x2B\x8B\x8F" + "\xBF\x46\x19\x74\x1D\xC4\x7B\xFB\x52\xA4\x32\x47\xA7\x5C\xA1\x5C\x1A", + "\x58\xFF") +TEST_BYTES_EXT( + 256bytes, + "\xD8\xE2\xE6\xED\x90\x05\x29\x3B\x17\xAC\x8D\x33\x93\x52\xD9\x6B" + "\xF2\xFB\x20\x74\x3E\x9C\xEF\xAD\xBB\x03\xCE\x0E\xC5\xBD\x0D\x2F" + "\x42\x6D\x1C\xD6\xDB\x29\xF8\xF6\xA4\x96\x3D\x7A\x8A\xEE\xE6\xF2" + "\x56\x1C\xBE\xCE\x71\x30\x3B\xEC\xC9\x86\x71\x96\x86\x51\xA2\xCA" + "\x23\x8A\x0B\x1D\x67\x3C\x50\xB8\x66\x4C\x64\x8C\x31\xCD\x11\x05" + "\xCA\x56\x4B\xBB\x79\x18\x8F\x5B\xF1\xE0\x1E\x85\x38\xBE\x7A\x6F" + "\x30\x4A\xFD\xB3\x1B\xA9\x52\xB4\x0E\x95\x73\x83\xA5\x33\x9F\x0C" + "\x04\x2E\x33\xB3\xD5\x0B\x6E\x02\x0C\xC7\x0D\x1A\x1A\x48\x0C\x92" + "\x1B\x62\x83\xCF\xC1\x5C\x90\xBC\x83\x3B\x92\xBF\x8E\xCE\x7C\xD6" + "\x99\x77\xF2\x66\x92\x0C\xC6\x0A\x11\x80\xBE\x03\x59\x23\x89\xF6" + "\xEF\x3A\x5A\x07\xEB\xEF\x47\xF0\x1F\xF0\xB4\x96\x01\x1B\xE9\x51" + "\x40\x70\x16\xDD\xB2\x9B\xEB\x42\xAC\x6E\x45\xE6\xAE\x8F\xCE\x9A" + "\xC4\xCB\x09\xE7\x2C\xE4\x48\x86\xF0\x9C\x56\x2C\xEF\x1B\xD0\x8E" + "\x92\xD4\x61\x15\x46\x76\x19\x32\xDF\x9F\x98\xC0\x0A\xF7\xAE\xA9" + "\xD7\x61\xEC\x8B\x78\xE5\xAA\xC6\x0B\x5D\x98\x1D\x86\xE6\x57\x67" + "\x97\x56\x82\x29\xFF\x8F\x61\x6C\xA5\xD0\x08\x20\xAE\x49\x5B\x04", + "\x59\x01\x00") + +AVS_UNIT_TEST(cbor_encoder, partial_read_string) { + cbor_test_env_t env; + cbor_test_setup(&env); + + char *test_str = "oqndmcvrgmvswuvcskllakhhersslftmmuwwwzirelnbtnlmvmezrqktq" + "qnlpldqwyvtbv" + "yryqcurqxnhzxoladzzmnumrifhqbcywuetmuyyjxpiwquzrekjxzgikn" + "qcmwzwuzxvrxb" + "zycnfrhyigwgkmbtlfyrhkolnsikvdelvkztkvonimtmvrivrnevgyxvj" + "djzvobsiufbwt" + "atfqeavhvfdfbnsumtletbaheyacrkwgectlrdrizenuvi"; + char *target_str = "\x79\x01\x00" + "oqndmcvrgmvswuvcskllakhhersslftmmuwwwzirelnbtnlmvmezrqk" + "tqqnlpldqwyvtbv" + "yryqcurqxnhzxoladzzmnumrifhqbcywuetmuyyjxpiwquzrekjxzgi" + "knqcmwzwuzxvrxb" + "zycnfrhyigwgkmbtlfyrhkolnsikvdelvkztkvonimtmvrivrnevgyx" + "vjdjzvobsiufbwt" + "atfqeavhvfdfbnsumtletbaheyacrkwgectlrdrizenuvi"; + + env.entry.type = FLUF_DATA_TYPE_STRING; + env.entry.value.bytes_or_string.data = test_str; + + size_t total_len = 0; + int res = -1; + for (env.buffer_length = 10; env.buffer_length < sizeof(env.buf); + env.buffer_length += 10) { + AVS_UNIT_ASSERT_SUCCESS( + fluf_io_out_ctx_new_entry(&env.ctx, &env.entry)); + while (res) { + res = fluf_io_out_ctx_get_payload(&env.ctx, &env.buf[total_len], + env.buffer_length, + &env.out_length); + AVS_UNIT_ASSERT_TRUE(res == 0 || res == FLUF_IO_NEED_NEXT_CALL); + AVS_UNIT_ASSERT_TRUE(env.out_length <= env.buffer_length); + total_len += env.out_length; + } + AVS_UNIT_ASSERT_EQUAL(total_len, strlen(test_str) + 3); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(env.buf, target_str, total_len); + + total_len = 0; + memset(env.buf, 0, sizeof(env.buf)); + memset(&env.ctx, 0, sizeof(fluf_io_out_ctx_t)); + env.ctx._format = FLUF_COAP_FORMAT_CBOR; + res = -1; + env.ctx._encoder._cbor.entry_added = false; + } +} + +AVS_UNIT_TEST(cbor_encoder, partial_read_ext) { + cbor_test_env_t env; + cbor_test_setup(&env); + + char *test_str = "oqndmcvrgmvswuvcskllakhhersslftmmuwwwzirelnbtnlmvmezrqktq" + "qnlpldqwyvtbv" + "yryqcurqxnhzxoladzzmnumrifhqbcywuetmuyyjxpiwquzrekjxzgikn" + "qcmwzwuzxvrxb" + "zycnfrhyigwgkmbtlfyrhkolnsikvdelvkztkvonimtmvrivrnevgyxvj" + "djzvobsiufbwt" + "atfqeavhvfdfbnsumtletbaheyacrkwgectlrdrizenuvi"; + char *target_str = "\x79\x01\x00" + "oqndmcvrgmvswuvcskllakhhersslftmmuwwwzirelnbtnlmvmezrqk" + "tqqnlpldqwyvtbv" + "yryqcurqxnhzxoladzzmnumrifhqbcywuetmuyyjxpiwquzrekjxzgi" + "knqcmwzwuzxvrxb" + "zycnfrhyigwgkmbtlfyrhkolnsikvdelvkztkvonimtmvrivrnevgyx" + "vjdjzvobsiufbwt" + "atfqeavhvfdfbnsumtletbaheyacrkwgectlrdrizenuvi"; + + env.entry.type = FLUF_DATA_TYPE_EXTERNAL_STRING; + env.entry.value.external_data.get_external_data = external_data_handler; + env.entry.value.external_data.length = strlen(test_str); + ptr_for_callback = test_str; + + size_t total_len = 0; + int res = -1; + for (env.buffer_length = 10; env.buffer_length < sizeof(env.buf); + env.buffer_length += 10) { + AVS_UNIT_ASSERT_SUCCESS( + fluf_io_out_ctx_new_entry(&env.ctx, &env.entry)); + while (res) { + res = fluf_io_out_ctx_get_payload(&env.ctx, &env.buf[total_len], + env.buffer_length, + &env.out_length); + AVS_UNIT_ASSERT_TRUE(res == 0 || res == FLUF_IO_NEED_NEXT_CALL); + AVS_UNIT_ASSERT_TRUE(env.out_length <= env.buffer_length); + total_len += env.out_length; + } + AVS_UNIT_ASSERT_EQUAL(total_len, strlen(test_str) + 3); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(env.buf, target_str, total_len); + + total_len = 0; + memset(env.buf, 0, sizeof(env.buf)); + memset(&env.ctx, 0, sizeof(fluf_io_out_ctx_t)); + env.ctx._format = FLUF_COAP_FORMAT_CBOR; + res = -1; + env.ctx._encoder._cbor.entry_added = false; + } +} diff --git a/tests/fluf/coap_udp_msg.c b/tests/fluf/coap_udp_msg.c new file mode 100644 index 00000000..303f0fe8 --- /dev/null +++ b/tests/fluf/coap_udp_msg.c @@ -0,0 +1,339 @@ +/* + * Copyright 2023-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 "../../src/fluf/fluf_coap_udp_header.h" +#include "../../src/fluf/fluf_coap_udp_msg.h" +#include "../../src/fluf/fluf_options.h" + +AVS_UNIT_TEST(coap_udp_msg, base_msg_serialize) { + _FLUF_COAP_OPTIONS_INIT_EMPTY(opts, 2); + char *payload = "xxx"; + fluf_coap_udp_msg_t msg = { + .header = _fluf_coap_udp_header_init(FLUF_COAP_UDP_TYPE_NON_CONFIRMABLE, + 4, FLUF_COAP_CODE_VALID, 0x2137), + .options = &opts, + .payload = payload, + .payload_size = strlen(payload), + .token = { + .size = 4, + .bytes = { 0x12, 0x34, 0x56, 0x78 } + }, + }; + + uint8_t msg_buff[100] = { 0 }; + size_t out_bytes_written; + + AVS_UNIT_ASSERT_SUCCESS( + _fluf_coap_udp_header_serialize(&msg, msg_buff, sizeof(msg_buff))); + + AVS_UNIT_ASSERT_SUCCESS(_fluf_coap_options_add_data(&opts, 5, "0", 1)); + AVS_UNIT_ASSERT_SUCCESS(_fluf_coap_options_add_string(&opts, 10, "123")); + + AVS_UNIT_ASSERT_SUCCESS(_fluf_coap_udp_msg_serialize( + &msg, msg_buff, sizeof(msg_buff), &out_bytes_written)); + AVS_UNIT_ASSERT_EQUAL(out_bytes_written, 18); + + const uint8_t EXPECTED[] = "\x54" // header v 0x01, Non-confirmable, tkl 4 + "\x43\x21\x37" // code 2.3, msg id 2137 + "\x12\x34\x56\x78" // token + "\x51\x30" // opt 1 + "\x53\x31\x32\x33" // opt2 + "\xFF" // payload marker + "\x78\x78\x78" // payload + ; + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(msg_buff, EXPECTED, sizeof(EXPECTED) - 1); +} + +AVS_UNIT_TEST(coap_udp_msg, no_payload_serialize) { + _FLUF_COAP_OPTIONS_INIT_EMPTY(opts, 3); + fluf_coap_udp_msg_t msg = { + .header = _fluf_coap_udp_header_init(FLUF_COAP_UDP_TYPE_CONFIRMABLE, 5, + FLUF_COAP_CODE_GET, 0x2137), + .options = &opts, + .payload = NULL, + .payload_size = 0, + .token = { + .size = 5, + .bytes = "\x12\x34\x56\x78\x90" + }, + }; + + uint8_t msg_buff[100] = { 0 }; + size_t out_bytes_written; + + AVS_UNIT_ASSERT_SUCCESS( + _fluf_coap_udp_header_serialize(&msg, msg_buff, sizeof(msg_buff))); + + AVS_UNIT_ASSERT_SUCCESS(_fluf_coap_options_add_data(&opts, 5, "0", 1)); + AVS_UNIT_ASSERT_SUCCESS(_fluf_coap_options_add_string(&opts, 10, "123")); + AVS_UNIT_ASSERT_SUCCESS(_fluf_coap_options_add_string(&opts, 10, "123")); + + AVS_UNIT_ASSERT_SUCCESS(_fluf_coap_udp_msg_serialize( + &msg, msg_buff, sizeof(msg_buff), &out_bytes_written)); + AVS_UNIT_ASSERT_EQUAL(out_bytes_written, 19); + + uint8_t EXPECTED[] = "\x45" // header v 0x01, Non-confirmable, tkl 5 + "\x01\x21\x37" // code 0.1, msg id 2137 + "\x12\x34\x56\x78\x90" // token + "\x51\x30" // opt 1 + "\x53\x31\x32\x33" // opt2 + "\x03\x31\x32\x33" // opt3 + ; + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(msg_buff, EXPECTED, sizeof(EXPECTED) - 1); +} + +AVS_UNIT_TEST(coap_udp_msg, zero_len_options_serialize) { + _FLUF_COAP_OPTIONS_INIT_EMPTY(opts, 3); + char *payload = "xxxxx"; + fluf_coap_udp_msg_t msg = { + .header = _fluf_coap_udp_header_init(FLUF_COAP_UDP_TYPE_NON_CONFIRMABLE, + 4, FLUF_COAP_CODE_VALID, 0x2137), + .options = &opts, + .payload = payload, + .payload_size = strlen(payload), + .token = { + .size = 4, + .bytes = { 0x12, 0x34, 0x56, 0x78 } + }, + }; + + uint8_t msg_buff[100] = { 0 }; + size_t out_bytes_written; + + AVS_UNIT_ASSERT_SUCCESS( + _fluf_coap_udp_header_serialize(&msg, msg_buff, sizeof(msg_buff))); + AVS_UNIT_ASSERT_SUCCESS(_fluf_coap_udp_msg_serialize( + &msg, msg_buff, sizeof(msg_buff), &out_bytes_written)); + AVS_UNIT_ASSERT_EQUAL(out_bytes_written, 14); + + const uint8_t EXPECTED[] = "\x54" // header v 0x01, Non-confirmable, tkl 4 + "\x43\x21\x37" // code 2.3, msg id 2137 + "\x12\x34\x56\x78" // token + "\xFF" // payload marker + "\x78\x78\x78\x78\x78" // payload + ; + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(msg_buff, EXPECTED, sizeof(EXPECTED) - 1); +} + +AVS_UNIT_TEST(coap_udp_msg, no_options_serialize) { + char *payload = "xxxxx"; + fluf_coap_udp_msg_t msg = { + .header = _fluf_coap_udp_header_init(FLUF_COAP_UDP_TYPE_NON_CONFIRMABLE, + 4, FLUF_COAP_CODE_VALID, 0x2137), + .options = NULL, + .payload = payload, + .payload_size = strlen(payload), + .token = { + .size = 4, + .bytes = { 0x12, 0x34, 0x56, 0x78 } + }, + }; + + uint8_t msg_buff[100] = { 0 }; + size_t out_bytes_written; + + AVS_UNIT_ASSERT_SUCCESS( + _fluf_coap_udp_header_serialize(&msg, msg_buff, sizeof(msg_buff))); + AVS_UNIT_ASSERT_SUCCESS(_fluf_coap_udp_msg_serialize( + &msg, msg_buff, sizeof(msg_buff), &out_bytes_written)); + AVS_UNIT_ASSERT_EQUAL(out_bytes_written, 14); + + const uint8_t EXPECTED[] = "\x54" // header v 0x01, Non-confirmable, tkl 4 + "\x43\x21\x37" // code 2.3, msg id 2137 + "\x12\x34\x56\x78" // token + "\xFF" // payload marker + "\x78\x78\x78\x78\x78" // payload + ; + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(msg_buff, EXPECTED, sizeof(EXPECTED) - 1); +} + +AVS_UNIT_TEST(coap_udp_msg, serialize_error) { + _FLUF_COAP_OPTIONS_INIT_EMPTY(opts, 2); + char *payload = "xxx"; + fluf_coap_udp_msg_t msg = { + .header = _fluf_coap_udp_header_init(FLUF_COAP_UDP_TYPE_NON_CONFIRMABLE, + 4, FLUF_COAP_CODE_VALID, 0x2137), + .options = &opts, + .payload = payload, + .payload_size = strlen(payload), + .token = { + .size = 4, + .bytes = { 0x12, 0x34, 0x56, 0x78 } + }, + }; + + uint8_t msg_buff[100] = { 0 }; + size_t out_bytes_written; + + AVS_UNIT_ASSERT_FAILED(_fluf_coap_udp_header_serialize(&msg, msg_buff, 4)); + AVS_UNIT_ASSERT_SUCCESS( + _fluf_coap_udp_header_serialize(&msg, msg_buff, 15)); + + AVS_UNIT_ASSERT_SUCCESS(_fluf_coap_options_add_data(&opts, 5, "0", 1)); + AVS_UNIT_ASSERT_SUCCESS(_fluf_coap_options_add_string(&opts, 10, "123")); + + // msg len = 18 + msg_buff[16] = 0xFE; + AVS_UNIT_ASSERT_FAILED(_fluf_coap_udp_msg_serialize(&msg, msg_buff, 15, + &out_bytes_written)); + AVS_UNIT_ASSERT_EQUAL(msg_buff[16], 0xFE); // buffer is not overwritten +} + +AVS_UNIT_TEST(coap_udp_msg, base_msg_parse) { + + uint8_t MSG[] = "\x54" // header v 0x01, Non-confirmable, tkl 4 + "\x43\x21\x37" // code 2.3, msg id 2137 + "\x12\x34\x56\x78" // token + "\x51\x30" // opt 1 + "\x53\x31\x32\x33" // opt2 + "\xFF" // payload marker + "\x78\x78\x78" // payload + ; + fluf_coap_udp_msg_t out_msg; + _FLUF_COAP_OPTIONS_INIT_EMPTY(opts, 4); + out_msg.options = &opts; + + AVS_UNIT_ASSERT_SUCCESS( + _fluf_coap_udp_msg_decode(&out_msg, MSG, sizeof(MSG) - 1)); + + AVS_UNIT_ASSERT_EQUAL(out_msg.header.version_type_token_length, 0x54); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(out_msg.header.message_id, "\x21\x37", 2); + AVS_UNIT_ASSERT_EQUAL(out_msg.header.code, FLUF_COAP_CODE_VALID); + AVS_UNIT_ASSERT_EQUAL(out_msg.token.size, 4); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(out_msg.token.bytes, "\x12\x34\x56\x78", + 4); + + AVS_UNIT_ASSERT_EQUAL(out_msg.options->options_number, 2); + AVS_UNIT_ASSERT_EQUAL(out_msg.options->options[0].option_number, 5); + AVS_UNIT_ASSERT_EQUAL(out_msg.options->options[0].payload_len, 1); + AVS_UNIT_ASSERT_EQUAL( + ((const uint8_t *) out_msg.options->options[0].payload)[0], 0x30); + AVS_UNIT_ASSERT_EQUAL(out_msg.options->options[1].option_number, 10); + AVS_UNIT_ASSERT_EQUAL(out_msg.options->options[1].payload_len, 3); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(out_msg.options->options[1].payload, + "123", 3); + + AVS_UNIT_ASSERT_EQUAL(out_msg.payload_size, 3); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(out_msg.payload, "xxx", + out_msg.payload_size); +} + +AVS_UNIT_TEST(coap_udp_msg, no_options_parse) { + + uint8_t MSG[] = "\x54" // header v 0x01, Non-confirmable, tkl 4 + "\x43\x21\x37" // code 2.3, msg id 2137 + "\x12\x34\x56\x78" // token + "\xFF" // payload marker + "\x78\x78\x78\x78\x78" // payload + ; + fluf_coap_udp_msg_t out_msg; + _FLUF_COAP_OPTIONS_INIT_EMPTY(opts, 1); + out_msg.options = &opts; + + AVS_UNIT_ASSERT_SUCCESS( + _fluf_coap_udp_msg_decode(&out_msg, MSG, sizeof(MSG) - 1)); + + AVS_UNIT_ASSERT_EQUAL(out_msg.header.version_type_token_length, 0x54); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(out_msg.header.message_id, "\x21\x37", 2); + AVS_UNIT_ASSERT_EQUAL(out_msg.header.code, FLUF_COAP_CODE_VALID); + AVS_UNIT_ASSERT_EQUAL(out_msg.token.size, 4); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(out_msg.token.bytes, "\x12\x34\x56\x78", + 4); + + AVS_UNIT_ASSERT_EQUAL(out_msg.payload_size, 5); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(out_msg.payload, "xxxxx", + out_msg.payload_size); +} + +AVS_UNIT_TEST(coap_udp_msg, no_payload_parse) { + + uint8_t MSG[] = "\x45" // header v 0x01, Non-confirmable, tkl 5 + "\x01\x21\x37" // code 0.1, msg id 2137 + "\x12\x34\x56\x78\x90" // token + "\x51\x30" // opt 1 + "\x53\x31\x32\x33" // opt2 + "\x03\x31\x32\x33" // opt3 + ; + fluf_coap_udp_msg_t out_msg; + _FLUF_COAP_OPTIONS_INIT_EMPTY(opts, 4); + out_msg.options = &opts; + + AVS_UNIT_ASSERT_SUCCESS( + _fluf_coap_udp_msg_decode(&out_msg, MSG, sizeof(MSG) - 1)); + + AVS_UNIT_ASSERT_EQUAL(out_msg.header.version_type_token_length, 0x45); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(out_msg.header.message_id, "\x21\x37", 2); + AVS_UNIT_ASSERT_EQUAL(out_msg.header.code, FLUF_COAP_CODE_GET); + AVS_UNIT_ASSERT_EQUAL(out_msg.token.size, 5); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(out_msg.token.bytes, + "\x12\x34\x56\x78\x90", 5); + + AVS_UNIT_ASSERT_EQUAL(out_msg.options->options_number, 3); + AVS_UNIT_ASSERT_EQUAL(out_msg.options->options[0].option_number, 5); + AVS_UNIT_ASSERT_EQUAL(out_msg.options->options[0].payload_len, 1); + AVS_UNIT_ASSERT_EQUAL( + ((const uint8_t *) out_msg.options->options[0].payload)[0], 0x30); + AVS_UNIT_ASSERT_EQUAL(out_msg.options->options[1].option_number, 10); + AVS_UNIT_ASSERT_EQUAL(out_msg.options->options[1].payload_len, 3); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(out_msg.options->options[1].payload, + "123", 3); + AVS_UNIT_ASSERT_EQUAL(out_msg.options->options[2].option_number, 10); + AVS_UNIT_ASSERT_EQUAL(out_msg.options->options[2].payload_len, 3); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(out_msg.options->options[2].payload, + "123", 3); + + AVS_UNIT_ASSERT_EQUAL(out_msg.payload_size, 0); +} + +AVS_UNIT_TEST(coap_udp_msg, parse_error) { + + uint8_t MSG[] = "\x54" // header v 0x01, Non-confirmable, tkl 4 + "\x43\x21\x37" // code 2.3, msg id 2137 + "\x12\x34\x56\x78" // token + "\x51\x30" // opt 1 + "\x53\x31\x32\x33" // opt2 + "\xFF" // payload marker + "\x78\x78\x78" // payload + ; + fluf_coap_udp_msg_t out_msg; + _FLUF_COAP_OPTIONS_INIT_EMPTY(opts, 2); + out_msg.options = &opts; + + // incorrect version number + uint8_t err1[sizeof(MSG) - 1]; + memcpy(err1, MSG, sizeof(err1)); + err1[0] = 0xD4; + AVS_UNIT_ASSERT_FAILED( + _fluf_coap_udp_msg_decode(&out_msg, err1, sizeof(err1))); + + // not enough space for options + opts.options_size = 1; + AVS_UNIT_ASSERT_FAILED( + _fluf_coap_udp_msg_decode(&out_msg, MSG, sizeof(MSG) - 1)); + opts.options_size = 2; + + // no payload marker + uint8_t err2[sizeof(MSG) - 1]; + memcpy(err2, MSG, sizeof(err2)); + err2[14] = 0x11; + AVS_UNIT_ASSERT_FAILED( + _fluf_coap_udp_msg_decode(&out_msg, err2, sizeof(err2))); + + // incorrect token length + uint8_t err3[sizeof(MSG) - 1]; + memcpy(err3, MSG, sizeof(err3)); + err3[0] = 0x52; + AVS_UNIT_ASSERT_FAILED( + _fluf_coap_udp_msg_decode(&out_msg, err3, sizeof(err3))); + + AVS_UNIT_ASSERT_SUCCESS( + _fluf_coap_udp_msg_decode(&out_msg, MSG, sizeof(MSG) - 1)); +} diff --git a/tests/fluf/discover_payload.c b/tests/fluf/discover_payload.c new file mode 100644 index 00000000..0007e745 --- /dev/null +++ b/tests/fluf/discover_payload.c @@ -0,0 +1,392 @@ +/* + * Copyright 2023-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 + +#define VERIFY_PAYLOAD(Payload, Buff, Len) \ + do { \ + AVS_UNIT_ASSERT_EQUAL(Len, strlen(Buff)); \ + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(Payload, Buff, Len); \ + } while (0) + +AVS_UNIT_TEST(discover_payload, first_example_from_specification) { + fluf_io_discover_ctx_t ctx; + char out_buff[300] = { 0 }; + size_t copied_bytes = 0; + size_t msg_len = 0; + + fluf_uri_path_t base_path = FLUF_MAKE_OBJECT_PATH(3); + fluf_attr_notification_t obj_attr = { 0 }; + obj_attr.has_min_period = true; + obj_attr.min_period = 10; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_init(&ctx, &base_path, NULL)); + + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &base_path, &obj_attr, NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + + fluf_attr_notification_t obj_inst_attr = { 0 }; + obj_inst_attr.has_max_period = true; + obj_inst_attr.max_period = 60; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(3, 0), &obj_inst_attr, NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_RESOURCE_PATH(3, 0, 1), NULL, NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_RESOURCE_PATH(3, 0, 2), NULL, NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_RESOURCE_PATH(3, 0, 3), NULL, NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_RESOURCE_PATH(3, 0, 4), NULL, NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + uint16_t dim = 2; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_RESOURCE_PATH(3, 0, 6), NULL, NULL, &dim)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + + fluf_attr_notification_t res_attr = { 0 }; + res_attr.has_greater_than = true; + res_attr.has_less_than = true; + res_attr.greater_than = 50; + res_attr.less_than = 42.2; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_RESOURCE_PATH(3, 0, 7), &res_attr, NULL, &dim)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_RESOURCE_PATH(3, 0, 8), NULL, NULL, &dim)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_RESOURCE_PATH(3, 0, 11), NULL, NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_RESOURCE_PATH(3, 0, 16), NULL, NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + + VERIFY_PAYLOAD(";pmin=10,;pmax=60,,,,,;dim=2,;dim=2;gt=50;lt=42.2,;dim=2,,", + out_buff, msg_len); +} + +AVS_UNIT_TEST(discover_payload, second_example_from_specification) { + fluf_io_discover_ctx_t ctx; + char out_buff[300] = { 0 }; + size_t copied_bytes = 0; + size_t msg_len = 0; + + fluf_uri_path_t base_path = FLUF_MAKE_OBJECT_PATH(1); + uint8_t depth = 1; + AVS_UNIT_ASSERT_SUCCESS( + fluf_io_discover_ctx_init(&ctx, &base_path, &depth)); + AVS_UNIT_ASSERT_SUCCESS( + fluf_io_discover_ctx_new_entry(&ctx, &base_path, NULL, NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(1, 0), NULL, NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + fluf_attr_notification_t obj_inst_attr = { 0 }; + obj_inst_attr.has_max_period = true; + obj_inst_attr.max_period = 300; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(1, 4), &obj_inst_attr, NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + + VERIFY_PAYLOAD(",,;pmax=300", out_buff, msg_len); +} + +AVS_UNIT_TEST(discover_payload, third_example_from_specification) { + fluf_io_discover_ctx_t ctx; + char out_buff[300] = { 0 }; + size_t copied_bytes = 0; + size_t msg_len = 0; + + fluf_uri_path_t base_path = FLUF_MAKE_INSTANCE_PATH(3, 0); + uint8_t depth = 3; + fluf_attr_notification_t obj_inst_attr = { 0 }; + obj_inst_attr.has_min_period = true; + obj_inst_attr.min_period = 10; + obj_inst_attr.has_max_period = true; + obj_inst_attr.max_period = 60; + AVS_UNIT_ASSERT_SUCCESS( + fluf_io_discover_ctx_init(&ctx, &base_path, &depth)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &base_path, &obj_inst_attr, NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_RESOURCE_PATH(3, 0, 1), NULL, NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_RESOURCE_PATH(3, 0, 2), NULL, NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_RESOURCE_PATH(3, 0, 3), NULL, NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_RESOURCE_PATH(3, 0, 4), NULL, NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + uint16_t dim = 2; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_RESOURCE_PATH(3, 0, 6), NULL, NULL, &dim)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_RESOURCE_INSTANCE_PATH(3, 0, 6, 0), NULL, NULL, + NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_RESOURCE_INSTANCE_PATH(3, 0, 6, 3), NULL, NULL, + NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + + fluf_attr_notification_t res_attr = { 0 }; + res_attr.has_greater_than = true; + res_attr.has_less_than = true; + res_attr.greater_than = 50; + res_attr.less_than = 42.2; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_RESOURCE_PATH(3, 0, 7), &res_attr, NULL, &dim)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_RESOURCE_INSTANCE_PATH(3, 0, 7, 0), NULL, NULL, + NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + + fluf_attr_notification_t res_inst_attr = { 0 }; + res_inst_attr.has_less_than = true; + res_inst_attr.less_than = 45.0; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_RESOURCE_INSTANCE_PATH(3, 0, 7, 1), &res_inst_attr, + NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_RESOURCE_PATH(3, 0, 8), NULL, NULL, &dim)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_RESOURCE_INSTANCE_PATH(3, 0, 8, 1), NULL, NULL, + NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_RESOURCE_INSTANCE_PATH(3, 0, 8, 2), NULL, NULL, + NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_RESOURCE_PATH(3, 0, 11), NULL, NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_RESOURCE_PATH(3, 0, 16), NULL, NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + + VERIFY_PAYLOAD(";pmin=10;pmax=60,,,,,;dim=2,,,;dim=2;gt=50;lt=42.2,,;lt=45,;dim=2,,,,", + out_buff, msg_len); +} + +AVS_UNIT_TEST(discover_payload, fourth_example_from_specification) { + fluf_io_discover_ctx_t ctx; + char out_buff[300] = { 0 }; + size_t copied_bytes = 0; + size_t msg_len = 0; + + fluf_uri_path_t base_path = FLUF_MAKE_INSTANCE_PATH(3, 0); + uint8_t depth = 0; + fluf_attr_notification_t attributes = { 0 }; + attributes.has_max_period = true; + attributes.has_min_period = true; + attributes.max_period = 60; + attributes.min_period = 10; + AVS_UNIT_ASSERT_SUCCESS( + fluf_io_discover_ctx_init(&ctx, &base_path, &depth)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &base_path, &attributes, NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + VERIFY_PAYLOAD(";pmin=10;pmax=60", out_buff, msg_len); +} + +AVS_UNIT_TEST(discover_payload, fifth_example_from_specification) { + fluf_io_discover_ctx_t ctx; + char out_buff[300] = { 0 }; + size_t copied_bytes = 0; + size_t msg_len = 0; + + fluf_uri_path_t base_path = FLUF_MAKE_RESOURCE_PATH(3, 0, 7); + fluf_attr_notification_t attributes = { 0 }; + attributes.has_max_period = true; + attributes.has_min_period = true; + attributes.max_period = 60; + attributes.min_period = 10; + attributes.has_greater_than = true; + attributes.has_less_than = true; + attributes.greater_than = 50; + attributes.less_than = 42e20; + uint16_t dim = 2; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_init(&ctx, &base_path, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &base_path, &attributes, NULL, &dim)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_RESOURCE_INSTANCE_PATH(3, 0, 7, 0), NULL, NULL, + NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + + fluf_attr_notification_t res_inst_attr = { 0 }; + res_inst_attr.has_less_than = true; + res_inst_attr.less_than = 45.0; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_RESOURCE_INSTANCE_PATH(3, 0, 7, 1), &res_inst_attr, + NULL, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_get_payload( + &ctx, &out_buff[msg_len], 300, &copied_bytes)); + msg_len += copied_bytes; + + VERIFY_PAYLOAD(";dim=2;pmin=10;pmax=60;gt=50;lt=4.2e21,,;lt=45", + out_buff, msg_len); +} + +AVS_UNIT_TEST(discover_payload, block_transfer) { + for (size_t i = 5; i < 75; i++) { + fluf_io_discover_ctx_t ctx; + char out_buff[300] = { 0 }; + size_t copied_bytes = 0; + size_t msg_len = 0; + + fluf_uri_path_t base_path = FLUF_MAKE_RESOURCE_PATH(3, 0, 7); + fluf_attr_notification_t attributes = { 0 }; + attributes.has_max_period = true; + attributes.has_min_period = true; + attributes.max_period = 60; + attributes.min_period = 10; + attributes.has_greater_than = true; + attributes.has_less_than = true; + attributes.greater_than = 50; + attributes.less_than = 42.2; + uint16_t dim = 2; + AVS_UNIT_ASSERT_SUCCESS( + fluf_io_discover_ctx_init(&ctx, &base_path, NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &base_path, &attributes, NULL, &dim)); + + int res = -1; + while (res) { + res = fluf_io_discover_ctx_get_payload(&ctx, &out_buff[msg_len], i, + &copied_bytes); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_TRUE(res == 0 || res == FLUF_IO_NEED_NEXT_CALL); + } + res = -1; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_RESOURCE_INSTANCE_PATH(3, 0, 7, 0), NULL, NULL, + NULL)); + while (res) { + res = fluf_io_discover_ctx_get_payload(&ctx, &out_buff[msg_len], i, + &copied_bytes); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_TRUE(res == 0 || res == FLUF_IO_NEED_NEXT_CALL); + } + res = -1; + fluf_attr_notification_t res_inst_attr = { 0 }; + res_inst_attr.has_less_than = true; + res_inst_attr.less_than = 45.0; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_discover_ctx_new_entry( + &ctx, &FLUF_MAKE_RESOURCE_INSTANCE_PATH(3, 0, 7, 1), + &res_inst_attr, NULL, NULL)); + while (res) { + res = fluf_io_discover_ctx_get_payload(&ctx, &out_buff[msg_len], i, + &copied_bytes); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_TRUE(res == 0 || res == FLUF_IO_NEED_NEXT_CALL); + } + + VERIFY_PAYLOAD(";dim=2;pmin=10;pmax=60;gt=50;lt=42.2,,;lt=45", + out_buff, msg_len); + } +} diff --git a/tests/fluf/lwm2m_decode.c b/tests/fluf/lwm2m_decode.c new file mode 100644 index 00000000..818d6fc0 --- /dev/null +++ b/tests/fluf/lwm2m_decode.c @@ -0,0 +1,679 @@ +/* + * Copyright 2023-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 + +AVS_UNIT_TEST(fluf_decode, decode_read) { + uint8_t MSG[] = "\x44" // header v 0x01, Confirmable, tkl 4 + "\x01\x21\x37" // GET code 0.1, msg id 3721 + "\x12\x34\x56\x78" // token + "\xB1\x33" // uri-path_1 URI_PATH 11 /3 + "\x01\x33" // uri-path_2 /3 + "\x02\x31\x31" // uri-path_3 /11 + "\x02\x31\x31" // uri-path_4 /11 + "\x62\x01\x40" // accept ACCEPT 17 SENML_ETCH_JSON 320 + ; + + fluf_data_t data; + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_decode(MSG, sizeof(MSG) - 1, FLUF_BINDING_UDP, &data)); + + AVS_UNIT_ASSERT_EQUAL(data.operation, FLUF_OP_DM_READ); + AVS_UNIT_ASSERT_EQUAL(data.uri.uri_len, 4); + AVS_UNIT_ASSERT_EQUAL(data.uri.ids[0], 3); + AVS_UNIT_ASSERT_EQUAL(data.uri.ids[1], 3); + AVS_UNIT_ASSERT_EQUAL(data.uri.ids[2], 11); + AVS_UNIT_ASSERT_EQUAL(data.uri.ids[3], 11); + AVS_UNIT_ASSERT_EQUAL(data.accept, FLUF_COAP_FORMAT_SENML_ETCH_JSON); + AVS_UNIT_ASSERT_EQUAL(data.content_format, FLUF_COAP_FORMAT_NOT_DEFINED); + AVS_UNIT_ASSERT_EQUAL(data.coap.coap_udp.message_id, 0x2137); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(data.coap.coap_udp.token.bytes, + ((uint8_t[]){ 0x12, 0x34, 0x56, 0x78 }), + 4); + AVS_UNIT_ASSERT_EQUAL(data.coap.coap_udp.token.size, 4); + AVS_UNIT_ASSERT_EQUAL(data.payload_size, 0); +} + +AVS_UNIT_TEST(fluf_decode, decode_write_replace) { + uint8_t MSG[] = "\x48" // header v 0x01, Confirmable, tkl 8 + "\x03\x37\x21" // PUT code 0.1, msg id 2137 + "\x12\x34\x56\x78\x11\x11\x11\x11" // token + "\xB1\x35" // uri-path_1 URI_PATH 11 /5 + "\x01\x30" // uri-path_2 /0 + "\x01\x31" // uri-path_3 /1 + "\x10" // content_format 12 PLAINTEXT 0 + "\xFF" // payload marker + "\x33\x44\x55" // payload + ; + + fluf_data_t data; + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_decode(MSG, sizeof(MSG) - 1, FLUF_BINDING_UDP, &data)); + + AVS_UNIT_ASSERT_EQUAL(data.operation, FLUF_OP_DM_WRITE_REPLACE); + AVS_UNIT_ASSERT_EQUAL(data.uri.uri_len, 3); + AVS_UNIT_ASSERT_EQUAL(data.uri.ids[0], 5); + AVS_UNIT_ASSERT_EQUAL(data.uri.ids[1], 0); + AVS_UNIT_ASSERT_EQUAL(data.uri.ids[2], 1); + AVS_UNIT_ASSERT_EQUAL(data.accept, FLUF_COAP_FORMAT_NOT_DEFINED); + AVS_UNIT_ASSERT_EQUAL(data.content_format, FLUF_COAP_FORMAT_PLAINTEXT); + AVS_UNIT_ASSERT_EQUAL(data.coap.coap_udp.message_id, 0x3721); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED( + data.coap.coap_udp.token.bytes, + ((uint8_t[]){ 0x12, 0x34, 0x56, 0x78, 0x11, 0x11, 0x11, 0x11 }), 8); + AVS_UNIT_ASSERT_EQUAL(data.coap.coap_udp.token.size, 8); + AVS_UNIT_ASSERT_EQUAL(data.payload_size, 3); + AVS_UNIT_ASSERT_EQUAL(((const uint8_t *) data.payload)[0], 0x33); + AVS_UNIT_ASSERT_EQUAL(data.binding, FLUF_BINDING_UDP); +} + +AVS_UNIT_TEST(fluf_decode, decode_write_with_block) { + uint8_t MSG[] = "\x48" // header v 0x01, Confirmable, tkl 8 + "\x03\x37\x21" // PUT code 0.1, msg id 2137 + "\x12\x34\x56\x78\x11\x11\x11\x11" // token + "\xB1\x35" // uri-path_1 URI_PATH 11 /5 + "\x01\x30" // uri-path_2 /0 + "\x01\x31" // uri-path_3 /1 + "\x10" // content_format 12 PLAINTEXT 0 + "\xd1\x02\xee" // BLOCK1 27 NUM:14 M:1 SZX:1024 + "\xFF" // payload marker + "\x33\x44\x55" // payload + ; + + fluf_data_t data; + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_decode(MSG, sizeof(MSG) - 1, FLUF_BINDING_UDP, &data)); + + AVS_UNIT_ASSERT_EQUAL(data.operation, FLUF_OP_DM_WRITE_REPLACE); + AVS_UNIT_ASSERT_EQUAL(data.uri.uri_len, 3); + AVS_UNIT_ASSERT_EQUAL(data.uri.ids[0], 5); + AVS_UNIT_ASSERT_EQUAL(data.uri.ids[1], 0); + AVS_UNIT_ASSERT_EQUAL(data.uri.ids[2], 1); + AVS_UNIT_ASSERT_EQUAL(data.accept, FLUF_COAP_FORMAT_NOT_DEFINED); + AVS_UNIT_ASSERT_EQUAL(data.content_format, FLUF_COAP_FORMAT_PLAINTEXT); + AVS_UNIT_ASSERT_EQUAL(data.coap.coap_udp.message_id, 0x3721); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED( + data.coap.coap_udp.token.bytes, + ((uint8_t[]){ 0x12, 0x34, 0x56, 0x78, 0x11, 0x11, 0x11, 0x11 }), 8); + AVS_UNIT_ASSERT_EQUAL(data.coap.coap_udp.token.size, 8); + AVS_UNIT_ASSERT_EQUAL(data.payload_size, 3); + AVS_UNIT_ASSERT_EQUAL(((const uint8_t *) data.payload)[0], 0x33); + AVS_UNIT_ASSERT_EQUAL(data.block.block_type, FLUF_OPTION_BLOCK_1); + AVS_UNIT_ASSERT_EQUAL(data.block.size, 1024); + AVS_UNIT_ASSERT_EQUAL(data.block.more_flag, true); + AVS_UNIT_ASSERT_EQUAL(data.block.number, 14); +} + +AVS_UNIT_TEST(fluf_decode, decode_discover) { + uint8_t MSG[] = "\x48" // header v 0x01, Confirmable, tkl 8 + "\x01\x37\x21" // GET code 0.1, msg id 2137 + "\x12\x34\x56\x78\x11\x11\x11\x11" // token + "\xB1\x35" // uri-path_1 URI_PATH 11 /5 + "\x01\x35" // uri-path_2 /5 + "\x61\x28" // accept ACCEPT 17 LINK_FORMAT 40 + ; + + fluf_data_t data; + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_decode(MSG, sizeof(MSG) - 1, FLUF_BINDING_UDP, &data)); + + AVS_UNIT_ASSERT_EQUAL(data.operation, FLUF_OP_DM_DISCOVER); + AVS_UNIT_ASSERT_EQUAL(data.uri.uri_len, 2); + AVS_UNIT_ASSERT_EQUAL(data.uri.ids[0], 5); + AVS_UNIT_ASSERT_EQUAL(data.uri.ids[1], 5); + AVS_UNIT_ASSERT_EQUAL(data.accept, FLUF_COAP_FORMAT_LINK_FORMAT); + AVS_UNIT_ASSERT_EQUAL(data.content_format, FLUF_COAP_FORMAT_NOT_DEFINED); + AVS_UNIT_ASSERT_EQUAL(data.coap.coap_udp.message_id, 0x3721); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED( + data.coap.coap_udp.token.bytes, + ((uint8_t[]){ 0x12, 0x34, 0x56, 0x78, 0x11, 0x11, 0x11, 0x11 }), 8); + AVS_UNIT_ASSERT_EQUAL(data.coap.coap_udp.token.size, 8); + AVS_UNIT_ASSERT_EQUAL(data.payload_size, 0); + AVS_UNIT_ASSERT_EQUAL(data.attr.discover_attr.has_depth, false); +} + +AVS_UNIT_TEST(fluf_decode, decode_discover_with_depth) { + uint8_t MSG[] = "\x48" // header v 0x01, Confirmable, tkl 8 + "\x01\x37\x21" // GET code 0.1, msg id 2137 + "\x12\x34\x56\x78\x11\x11\x11\x11" // token + "\xB1\x35" // uri-path_1 URI_PATH 11 /5 + "\x01\x35" // uri-path_2 /5 + "\x47\x64\x65\x70\x74\x68\x3d\x32" // URI_QUERY 15 depth=2 + "\x21\x28" // accept ACCEPT 17 LINK_FORMAT 40 + ; + + fluf_data_t data; + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_decode(MSG, sizeof(MSG) - 1, FLUF_BINDING_UDP, &data)); + + AVS_UNIT_ASSERT_EQUAL(data.operation, FLUF_OP_DM_DISCOVER); + AVS_UNIT_ASSERT_EQUAL(data.uri.uri_len, 2); + AVS_UNIT_ASSERT_EQUAL(data.uri.ids[0], 5); + AVS_UNIT_ASSERT_EQUAL(data.uri.ids[1], 5); + AVS_UNIT_ASSERT_EQUAL(data.accept, FLUF_COAP_FORMAT_LINK_FORMAT); + AVS_UNIT_ASSERT_EQUAL(data.content_format, FLUF_COAP_FORMAT_NOT_DEFINED); + AVS_UNIT_ASSERT_EQUAL(data.coap.coap_udp.message_id, 0x3721); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED( + data.coap.coap_udp.token.bytes, + ((uint8_t[]){ 0x12, 0x34, 0x56, 0x78, 0x11, 0x11, 0x11, 0x11 }), 8); + AVS_UNIT_ASSERT_EQUAL(data.coap.coap_udp.token.size, 8); + AVS_UNIT_ASSERT_EQUAL(data.payload_size, 0); + AVS_UNIT_ASSERT_EQUAL(data.attr.discover_attr.has_depth, true); + AVS_UNIT_ASSERT_EQUAL(data.attr.discover_attr.depth, 2); +} + +AVS_UNIT_TEST(fluf_decode, decode_bootstrap_finish) { + uint8_t MSG[] = "\x48" // header v 0x01, Confirmable, tkl 8 + "\x02\x37\x21" // POST code 0.2, msg id 2137 + "\x12\x34\x56\x78\x11\x11\x11\x11" // token + "\xB2\x62\x73" // uri-path_1 URI_PATH 11 /bs + ; + + fluf_data_t data; + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_decode(MSG, sizeof(MSG) - 1, FLUF_BINDING_UDP, &data)); + + AVS_UNIT_ASSERT_EQUAL(data.operation, FLUF_OP_BOOTSTRAP_FINISH); + AVS_UNIT_ASSERT_EQUAL(data.uri.uri_len, 0); + AVS_UNIT_ASSERT_EQUAL(data.accept, FLUF_COAP_FORMAT_NOT_DEFINED); + AVS_UNIT_ASSERT_EQUAL(data.content_format, FLUF_COAP_FORMAT_NOT_DEFINED); + AVS_UNIT_ASSERT_EQUAL(data.coap.coap_udp.message_id, 0x3721); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED( + data.coap.coap_udp.token.bytes, + ((uint8_t[]){ 0x12, 0x34, 0x56, 0x78, 0x11, 0x11, 0x11, 0x11 }), 8); + AVS_UNIT_ASSERT_EQUAL(data.coap.coap_udp.token.size, 8); + AVS_UNIT_ASSERT_EQUAL(data.payload_size, 0); +} + +AVS_UNIT_TEST(fluf_decode, decode_read_composite) { + uint8_t MSG[] = "\x48" // header v 0x01, Confirmable, tkl 8 + "\x05\x37\x21" // FETCH code 0.5, msg id 2137 + "\x12\x34\x56\x78\x11\x11\x11\x11" // token + "\xC0" // content_format 12 PLAINTEXT 0 + "\x52\x2D\x17" // accept 17 LWM2M_JSON 11543 + "\xFF" // payload marker + "\x33\x44\x55\x33\x44\x55" // payload + ; + + fluf_data_t data; + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_decode(MSG, sizeof(MSG) - 1, FLUF_BINDING_UDP, &data)); + + AVS_UNIT_ASSERT_EQUAL(data.operation, FLUF_OP_DM_READ_COMP); + AVS_UNIT_ASSERT_EQUAL(data.uri.uri_len, 0); + AVS_UNIT_ASSERT_EQUAL(data.accept, FLUF_COAP_FORMAT_OMA_LWM2M_JSON); + AVS_UNIT_ASSERT_EQUAL(data.content_format, FLUF_COAP_FORMAT_PLAINTEXT); + AVS_UNIT_ASSERT_EQUAL(data.coap.coap_udp.message_id, 0x3721); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED( + data.coap.coap_udp.token.bytes, + ((uint8_t[]){ 0x12, 0x34, 0x56, 0x78, 0x11, 0x11, 0x11, 0x11 }), 8); + AVS_UNIT_ASSERT_EQUAL(data.coap.coap_udp.token.size, 8); + AVS_UNIT_ASSERT_EQUAL(data.payload_size, 6); + AVS_UNIT_ASSERT_EQUAL(((const uint8_t *) data.payload)[0], 0x33); +} + +AVS_UNIT_TEST(fluf_decode, decode_observe_with_pmin_pmax) { + uint8_t MSG[] = + "\x48" // header v 0x01, Confirmable, tkl 8 + "\x01\x37\x21" // GET code 0.1, msg id 2137 + "\x12\x34\x56\x78\x11\x11\x11\x11" // token + "\x61\x00" // observe 6 = 0 + "\x51\x35" // uri-path_1 URI_PATH 11 /5 + "\x01\x35" // uri-path_2 /5 + "\x01\x31" // uri-path_3 /1 + "\x48\x70\x6d\x69\x6e\x3d\x32\x30\x30" // URI_QUERY 15 pmin=200 + "\x09\x70\x6d\x61\x78\x3d\x34\x32\x30\x30" // URI_QUERY 15 pmax=4200 + ; + + fluf_data_t data; + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_decode(MSG, sizeof(MSG) - 1, FLUF_BINDING_UDP, &data)); + + AVS_UNIT_ASSERT_EQUAL(data.operation, FLUF_OP_INF_OBSERVE); + AVS_UNIT_ASSERT_EQUAL(data.uri.uri_len, 3); + AVS_UNIT_ASSERT_EQUAL(data.uri.ids[0], 5); + AVS_UNIT_ASSERT_EQUAL(data.uri.ids[1], 5); + AVS_UNIT_ASSERT_EQUAL(data.uri.ids[2], 1); + AVS_UNIT_ASSERT_EQUAL(data.accept, FLUF_COAP_FORMAT_NOT_DEFINED); + AVS_UNIT_ASSERT_EQUAL(data.content_format, FLUF_COAP_FORMAT_NOT_DEFINED); + AVS_UNIT_ASSERT_EQUAL(data.coap.coap_udp.message_id, 0x3721); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED( + data.coap.coap_udp.token.bytes, + ((uint8_t[]){ 0x12, 0x34, 0x56, 0x78, 0x11, 0x11, 0x11, 0x11 }), 8); + AVS_UNIT_ASSERT_EQUAL(data.coap.coap_udp.token.size, 8); + AVS_UNIT_ASSERT_EQUAL(data.payload_size, 0); + AVS_UNIT_ASSERT_EQUAL(data.attr.notification_attr.has_con, false); + AVS_UNIT_ASSERT_EQUAL(data.attr.notification_attr.has_min_period, true); + AVS_UNIT_ASSERT_EQUAL(data.attr.notification_attr.has_max_period, true); + AVS_UNIT_ASSERT_EQUAL(data.attr.notification_attr.min_period, 200); + AVS_UNIT_ASSERT_EQUAL(data.attr.notification_attr.max_period, 4200); +} + +AVS_UNIT_TEST(fluf_decode, decode_observe_composite_with_params) { + uint8_t MSG[] = + "\x48" // header v 0x01, Confirmable, tkl 8 + "\x05\x37\x21" // FETCH code 0.5, msg id 2137 + "\x12\x34\x56\x78\x11\x11\x11\x11" // token + "\x61\x00" // observe 6 = 0 + "\x97\x70\x6d\x69\x6e\x3d\x32\x30" // URI_QUERY 15 pmin=20 + "\x07\x65\x70\x6d\x69\x6e\x3d\x31" // URI_QUERY 15 epmin=1 + "\x07\x65\x70\x6d\x61\x78\x3d\x32" // URI_QUERY 15 epmax=2 + "\x05\x63\x6f\x6e\x3d\x31" // URI_QUERY 15 con=1 + "\x09\x70\x6d\x61\x78\x3d\x31\x32\x30\x30" // URI_QUERY 15 pmax=1200 + "\xFF" // payload marker + "\x77\x44\x55\x33\x44\x55\x33\x33\x33\x33" // payload + ; + + fluf_data_t data; + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_decode(MSG, sizeof(MSG) - 1, FLUF_BINDING_UDP, &data)); + + AVS_UNIT_ASSERT_EQUAL(data.operation, FLUF_OP_INF_OBSERVE_COMP); + AVS_UNIT_ASSERT_EQUAL(data.uri.uri_len, 0); + AVS_UNIT_ASSERT_EQUAL(data.coap.coap_udp.message_id, 0x3721); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED( + data.coap.coap_udp.token.bytes, + ((uint8_t[]){ 0x12, 0x34, 0x56, 0x78, 0x11, 0x11, 0x11, 0x11 }), 8); + AVS_UNIT_ASSERT_EQUAL(data.coap.coap_udp.token.size, 8); + AVS_UNIT_ASSERT_EQUAL(data.payload_size, 10); + AVS_UNIT_ASSERT_EQUAL(((const uint8_t *) data.payload)[0], 0x77); + AVS_UNIT_ASSERT_EQUAL(data.attr.notification_attr.has_con, true); + AVS_UNIT_ASSERT_EQUAL(data.attr.notification_attr.has_min_period, true); + AVS_UNIT_ASSERT_EQUAL(data.attr.notification_attr.has_min_eval_period, + true); + AVS_UNIT_ASSERT_EQUAL(data.attr.notification_attr.has_max_period, true); + AVS_UNIT_ASSERT_EQUAL(data.attr.notification_attr.has_max_eval_period, + true); + AVS_UNIT_ASSERT_EQUAL(data.attr.notification_attr.min_period, 20); + AVS_UNIT_ASSERT_EQUAL(data.attr.notification_attr.max_period, 1200); + AVS_UNIT_ASSERT_EQUAL(data.attr.notification_attr.min_eval_period, 1); + AVS_UNIT_ASSERT_EQUAL(data.attr.notification_attr.max_eval_period, 2); + AVS_UNIT_ASSERT_EQUAL(data.attr.notification_attr.con, 1); +} + +AVS_UNIT_TEST(fluf_decode, decode_cancel_observation) { + uint8_t MSG[] = "\x48" // header v 0x01, Confirmable, tkl 8 + "\x01\x37\x21" // GET code 0.1, msg id 2137 + "\x12\x34\x56\x78\x11\x11\x11\x11" // token + "\x61\x01" // observe 6 = 1 + "\x51\x35" // uri-path_1 URI_PATH 11 /5 + "\x01\x35" // uri-path_2 /5 + "\x01\x31" // uri-path_3 /1 + ; + + fluf_data_t data; + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_decode(MSG, sizeof(MSG) - 1, FLUF_BINDING_UDP, &data)); + + AVS_UNIT_ASSERT_EQUAL(data.operation, FLUF_OP_INF_CANCEL_OBSERVE); + AVS_UNIT_ASSERT_EQUAL(data.uri.uri_len, 3); + AVS_UNIT_ASSERT_EQUAL(data.uri.ids[0], 5); + AVS_UNIT_ASSERT_EQUAL(data.uri.ids[1], 5); + AVS_UNIT_ASSERT_EQUAL(data.uri.ids[2], 1); +} + +AVS_UNIT_TEST(fluf_decode, decode_cancel_composite) { + uint8_t MSG[] = "\x48" // header v 0x01, Confirmable, tkl 8 + "\x05\x37\x21" // FETCH code 0.5, msg id 2137 + "\x12\x34\x56\x78\x11\x11\x11\x11" // token + "\x61\x01" // observe 6 = 1 + "\xFF" // payload marker + "\x77\x44\x55\x33\x44\x55\x33\x33\x33\x33" // payload + ; + + fluf_data_t data; + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_decode(MSG, sizeof(MSG) - 1, FLUF_BINDING_UDP, &data)); + + AVS_UNIT_ASSERT_EQUAL(data.operation, FLUF_OP_INF_CANCEL_OBSERVE_COMP); + AVS_UNIT_ASSERT_EQUAL(data.uri.uri_len, 0); + AVS_UNIT_ASSERT_EQUAL(data.payload_size, 10); + AVS_UNIT_ASSERT_EQUAL(((const uint8_t *) data.payload)[0], 0x77); +} + +AVS_UNIT_TEST(fluf_decode, decode_write_partial) { + uint8_t MSG[] = "\x48" // header v 0x01, Confirmable, tkl 8 + "\x02\x37\x21" // POST code 0.2, msg id 2137 + "\x12\x34\x56\x78\x11\x11\x11\x11" // token + "\xB2\x31\x35" // uri-path_1 URI_PATH 11 /15 + "\x01\x32" // uri-path_2 /2 + "\x10" // content_format 12 PLAINTEXT 0 + "\xFF" // payload marker + "\x33\x44\x55" // payload + ; + + fluf_data_t data; + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_decode(MSG, sizeof(MSG) - 1, FLUF_BINDING_UDP, &data)); + + AVS_UNIT_ASSERT_EQUAL(data.operation, FLUF_OP_DM_WRITE_PARTIAL_UPDATE); + AVS_UNIT_ASSERT_EQUAL(data.uri.uri_len, 2); + AVS_UNIT_ASSERT_EQUAL(data.uri.ids[0], 15); + AVS_UNIT_ASSERT_EQUAL(data.uri.ids[1], 2); + AVS_UNIT_ASSERT_EQUAL(data.accept, FLUF_COAP_FORMAT_NOT_DEFINED); + AVS_UNIT_ASSERT_EQUAL(data.content_format, FLUF_COAP_FORMAT_PLAINTEXT); + AVS_UNIT_ASSERT_EQUAL(data.coap.coap_udp.message_id, 0x3721); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED( + data.coap.coap_udp.token.bytes, + ((uint8_t[]){ 0x12, 0x34, 0x56, 0x78, 0x11, 0x11, 0x11, 0x11 }), 8); + AVS_UNIT_ASSERT_EQUAL(data.coap.coap_udp.token.size, 8); + AVS_UNIT_ASSERT_EQUAL(data.payload_size, 3); + AVS_UNIT_ASSERT_EQUAL(((const uint8_t *) data.payload)[0], 0x33); +} + +AVS_UNIT_TEST(fluf_decode, decode_write_attributes) { + uint8_t MSG[] = + "\x48" // header v 0x01, Confirmable, tkl 8 + "\x03\x37\x21" // PUT code 0.3, msg id 2137 + "\x12\x34\x56\x78\x11\x11\x11\x11" // token + "\xB2\x31\x35" // uri-path_1 URI_PATH 11 /15 + "\x01\x32" // uri-path_2 /2 + "\x02\x31\x32" // uri-path_3 /12 + "\x47\x70\x6d\x69\x6e\x3d\x32\x30" // URI_QUERY 15 pmin=20 + "\x07\x65\x70\x6d\x69\x6e\x3d\x31" // URI_QUERY 15 epmin=1 + "\x07\x65\x70\x6d\x61\x78\x3d\x32" // URI_QUERY 15 epmax=2 + "\x05\x63\x6f\x6e\x3d\x31" // URI_QUERY 15 con=1 + "\x07\x67\x74\x3d\x32\x2e\x38\x35" // URI_QUERY 15 gt=2.85 + "\x09\x6c\x74\x3d\x33\x33\x33\x33\x2e\x38" // URI_QUERY 15 lt=3333.8 + "\x07\x73\x74\x3d\x2D\x30\x2e\x38" // URI_QUERY 15 st=-0.8 + "\x06\x65\x64\x67\x65\x3d\x30" // URI_QUERY 15 edge=0 + "\x0A\x68\x71\x6d\x61\x78\x3d\x37\x37\x37\x37" // URI_QUERY 15 + // hqmax=7777 + "\x09\x70\x6d\x61\x78\x3d\x31\x32\x30\x30" // URI_QUERY 15 pmax=1200 + ; + + fluf_data_t data; + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_decode(MSG, sizeof(MSG) - 1, FLUF_BINDING_UDP, &data)); + + AVS_UNIT_ASSERT_EQUAL(data.operation, FLUF_OP_DM_WRITE_ATTR); + AVS_UNIT_ASSERT_EQUAL(data.uri.uri_len, 3); + AVS_UNIT_ASSERT_EQUAL(data.uri.ids[0], 15); + AVS_UNIT_ASSERT_EQUAL(data.uri.ids[1], 2); + AVS_UNIT_ASSERT_EQUAL(data.uri.ids[2], 12); + AVS_UNIT_ASSERT_EQUAL(data.payload_size, 0); + + AVS_UNIT_ASSERT_EQUAL(data.attr.notification_attr.has_min_period, true); + AVS_UNIT_ASSERT_EQUAL(data.attr.notification_attr.has_max_period, true); + AVS_UNIT_ASSERT_EQUAL(data.attr.notification_attr.has_greater_than, true); + AVS_UNIT_ASSERT_EQUAL(data.attr.notification_attr.has_less_than, true); + AVS_UNIT_ASSERT_EQUAL(data.attr.notification_attr.has_step, true); + AVS_UNIT_ASSERT_EQUAL(data.attr.notification_attr.has_min_eval_period, + true); + AVS_UNIT_ASSERT_EQUAL(data.attr.notification_attr.has_max_eval_period, + true); + AVS_UNIT_ASSERT_EQUAL(data.attr.notification_attr.has_edge, true); + AVS_UNIT_ASSERT_EQUAL(data.attr.notification_attr.has_con, true); + AVS_UNIT_ASSERT_EQUAL(data.attr.notification_attr.has_hqmax, true); + + AVS_UNIT_ASSERT_EQUAL(data.attr.notification_attr.min_period, 20); + AVS_UNIT_ASSERT_EQUAL(data.attr.notification_attr.max_period, 1200); + AVS_UNIT_ASSERT_EQUAL(data.attr.notification_attr.min_eval_period, 1); + AVS_UNIT_ASSERT_EQUAL(data.attr.notification_attr.max_eval_period, 2); + AVS_UNIT_ASSERT_EQUAL(data.attr.notification_attr.edge, 0); + AVS_UNIT_ASSERT_EQUAL(data.attr.notification_attr.con, 1); + AVS_UNIT_ASSERT_EQUAL(data.attr.notification_attr.hqmax, 7777); + + AVS_UNIT_ASSERT_EQUAL( + (int) (100 * data.attr.notification_attr.greater_than), 285); + AVS_UNIT_ASSERT_EQUAL((int) (100 * data.attr.notification_attr.less_than), + 333380); + AVS_UNIT_ASSERT_EQUAL((int) (100 * data.attr.notification_attr.step), -80); +} + +AVS_UNIT_TEST(fluf_decode, decode_write_composite) { + uint8_t MSG[] = "\x48" // header v 0x01, Confirmable, tkl 8 + "\x07\x37\x21" // IPATCH code 0.7, msg id 2137 + "\x12\x34\x56\x78\x11\x11\x11\x11" // token + "\xC1\x3C" // content_format 12 FORMAT_CBOR 60 + "\xFF" // payload marker + "\x77\x44\x55\x33\x44\x55\x33\x33\x33\x33" // payload + ; + + fluf_data_t data; + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_decode(MSG, sizeof(MSG) - 1, FLUF_BINDING_UDP, &data)); + + AVS_UNIT_ASSERT_EQUAL(data.operation, FLUF_OP_DM_WRITE_COMP); + AVS_UNIT_ASSERT_EQUAL(data.uri.uri_len, 0); + AVS_UNIT_ASSERT_EQUAL(data.content_format, FLUF_COAP_FORMAT_CBOR); + AVS_UNIT_ASSERT_EQUAL(data.payload_size, 10); + AVS_UNIT_ASSERT_EQUAL(((const uint8_t *) data.payload)[0], 0x77); +} + +AVS_UNIT_TEST(fluf_decode, decode_execute) { + uint8_t MSG[] = "\x48" // header v 0x01, Confirmable, tkl 8 + "\x02\x37\x21" // POST code 0.2, msg id 2137 + "\x12\x34\x56\x78\x11\x11\x11\x11" // token + "\xB2\x31\x35" // uri-path_1 URI_PATH 11 /15 + "\x01\x32" // uri-path_2 /2 + "\x02\x31\x32" // uri-path_3 /12 + "\x11\x3C" // content_format 12 FORMAT_CBOR 60 + "\xFF" // payload marker + "\x77\x44\x55\x33\x44\x55\x33\x33\x33\x33" // payload + ; + + fluf_data_t data; + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_decode(MSG, sizeof(MSG) - 1, FLUF_BINDING_UDP, &data)); + + AVS_UNIT_ASSERT_EQUAL(data.operation, FLUF_OP_DM_EXECUTE); + AVS_UNIT_ASSERT_EQUAL(data.uri.uri_len, 3); + AVS_UNIT_ASSERT_EQUAL(data.uri.ids[0], 15); + AVS_UNIT_ASSERT_EQUAL(data.uri.ids[1], 2); + AVS_UNIT_ASSERT_EQUAL(data.uri.ids[2], 12); + AVS_UNIT_ASSERT_EQUAL(data.content_format, FLUF_COAP_FORMAT_CBOR); + AVS_UNIT_ASSERT_EQUAL(data.payload_size, 10); + AVS_UNIT_ASSERT_EQUAL(((const uint8_t *) data.payload)[0], 0x77); +} + +AVS_UNIT_TEST(fluf_decode, decode_create) { + uint8_t MSG[] = "\x48" // header v 0x01, Confirmable, tkl 8 + "\x02\x37\x21" // POST code 0.2, msg id 2137 + "\x12\x34\x56\x78\x11\x11\x11\x11" // token + "\xB5\x33\x33\x36\x33\x39" // uri-path_1 URI_PATH 11 /33639 + "\x11\x3C" // content_format 12 FORMAT_CBOR 60 + "\xFF" // payload marker + "\x76\x44\x55\x33\x44\x55\x33\x33" // payload + ; + + fluf_data_t data; + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_decode(MSG, sizeof(MSG) - 1, FLUF_BINDING_UDP, &data)); + + AVS_UNIT_ASSERT_EQUAL(data.operation, FLUF_OP_DM_CREATE); + AVS_UNIT_ASSERT_EQUAL(data.uri.uri_len, 1); + AVS_UNIT_ASSERT_EQUAL(data.uri.ids[0], 33639); + AVS_UNIT_ASSERT_EQUAL(data.content_format, FLUF_COAP_FORMAT_CBOR); + AVS_UNIT_ASSERT_EQUAL(data.payload_size, 8); + AVS_UNIT_ASSERT_EQUAL(((const uint8_t *) data.payload)[0], 0x76); +} + +AVS_UNIT_TEST(fluf_decode, decode_delete) { + uint8_t MSG[] = "\x48" // header v 0x01, Confirmable, tkl 8 + "\x04\x37\x21" // DELETE code 0.4, msg id 2137 + "\x12\x34\x56\x78\x11\x11\x11\x11" // token + "\xB5\x33\x33\x36\x33\x39" // uri-path_1 URI_PATH 11 /33639 + "\x01\x31" // uri-path_1 URI_PATH 11 /1 + "\xFF" // payload marker + "\x76\x44\x55\x33\x44\x55\x33\x33" // payload + ; + + fluf_data_t data; + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_decode(MSG, sizeof(MSG) - 1, FLUF_BINDING_UDP, &data)); + + AVS_UNIT_ASSERT_EQUAL(data.operation, FLUF_OP_DM_DELETE); + AVS_UNIT_ASSERT_EQUAL(data.uri.uri_len, 2); + AVS_UNIT_ASSERT_EQUAL(data.uri.ids[0], 33639); + AVS_UNIT_ASSERT_EQUAL(data.uri.ids[1], 1); + AVS_UNIT_ASSERT_EQUAL(data.payload_size, 8); + AVS_UNIT_ASSERT_EQUAL(((const uint8_t *) data.payload)[0], 0x76); +} + +AVS_UNIT_TEST(fluf_decode, decode_response) { + uint8_t MSG[] = "\x68" // header v 0x01, Ack, tkl 8 + "\x44\x37\x21" // CHANGED code 2.4, msg id 2137 + "\x12\x34\x56\x78\x11\x11\x11\x11" // token + ; + + fluf_data_t data; + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_decode(MSG, sizeof(MSG) - 1, FLUF_BINDING_UDP, &data)); + + AVS_UNIT_ASSERT_EQUAL(data.operation, FLUF_OP_RESPONSE); + AVS_UNIT_ASSERT_EQUAL(data.coap.coap_udp.type, + FLUF_COAP_UDP_TYPE_ACKNOWLEDGEMENT); + AVS_UNIT_ASSERT_EQUAL(data.msg_code, FLUF_COAP_CODE_CHANGED); +} + +AVS_UNIT_TEST(fluf_decode, decode_empty_response) { + uint8_t MSG[] = "\x60" // header v 0x01, Ack, tkl 8 + "\x00\x37\x21" // empty code 2.4, msg id 2137 + ; + + fluf_data_t data; + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_decode(MSG, sizeof(MSG) - 1, FLUF_BINDING_UDP, &data)); + + AVS_UNIT_ASSERT_EQUAL(data.operation, FLUF_OP_RESPONSE); + AVS_UNIT_ASSERT_EQUAL(data.coap.coap_udp.type, + FLUF_COAP_UDP_TYPE_ACKNOWLEDGEMENT); + AVS_UNIT_ASSERT_EQUAL(data.msg_code, FLUF_COAP_CODE_EMPTY); +} + +AVS_UNIT_TEST(fluf_decode, decode_con_response) { + uint8_t MSG[] = "\x48" // header v 0x01, Con, tkl 8 + "\x44\x37\x21" // CHANGED code 2.4, msg id 2137 + "\x12\x34\x56\x78\x11\x11\x11\x11" // token + ; + + fluf_data_t data; + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_decode(MSG, sizeof(MSG) - 1, FLUF_BINDING_UDP, &data)); + + AVS_UNIT_ASSERT_EQUAL(data.operation, FLUF_OP_RESPONSE); + AVS_UNIT_ASSERT_EQUAL(data.coap.coap_udp.type, + FLUF_COAP_UDP_TYPE_CONFIRMABLE); + AVS_UNIT_ASSERT_EQUAL(data.msg_code, FLUF_COAP_CODE_CHANGED); +} + +AVS_UNIT_TEST(fluf_decode, decode_non_con_response) { + uint8_t MSG[] = "\x58" // header v 0x01, Con, tkl 8 + "\x44\x37\x21" // CHANGED code 2.4, msg id 2137 + "\x12\x34\x56\x78\x11\x11\x11\x11" // token + ; + + fluf_data_t data; + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_decode(MSG, sizeof(MSG) - 1, FLUF_BINDING_UDP, &data)); + + AVS_UNIT_ASSERT_EQUAL(data.operation, FLUF_OP_RESPONSE); + AVS_UNIT_ASSERT_EQUAL(data.coap.coap_udp.type, + FLUF_COAP_UDP_TYPE_NON_CONFIRMABLE); + AVS_UNIT_ASSERT_EQUAL(data.msg_code, FLUF_COAP_CODE_CHANGED); +} + +AVS_UNIT_TEST(fluf_decode, decode_ping) { + uint8_t MSG[] = "\x40" // header v 0x01, Con, tkl 8 + "\x00\x37\x21" // CHANGED code 2.4, msg id 2137 + ; + + fluf_data_t data; + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_decode(MSG, sizeof(MSG) - 1, FLUF_BINDING_UDP, &data)); + + AVS_UNIT_ASSERT_EQUAL(data.operation, FLUF_OP_COAP_PING); + AVS_UNIT_ASSERT_EQUAL(data.coap.coap_udp.type, + FLUF_COAP_UDP_TYPE_CONFIRMABLE); + AVS_UNIT_ASSERT_EQUAL(data.msg_code, FLUF_COAP_CODE_EMPTY); +} + +AVS_UNIT_TEST(fluf_decode, decode_response_with_etag) { + uint8_t MSG[] = "\x68" // header v 0x01, Ack, tkl 8 + "\x44\x37\x21" // CHANGED code 2.4, msg id 2137 + "\x12\x34\x56\x78\x11\x11\x11\x11" // token + "\x43\x33\x33\x32" // etag 3 332 + ; + + fluf_data_t data; + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_decode(MSG, sizeof(MSG) - 1, FLUF_BINDING_UDP, &data)); + + AVS_UNIT_ASSERT_EQUAL(data.operation, FLUF_OP_RESPONSE); + AVS_UNIT_ASSERT_EQUAL(data.etag.size, 3); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED("332", data.etag.bytes, 3); + AVS_UNIT_ASSERT_EQUAL(data.msg_code, FLUF_COAP_CODE_CHANGED); +} + +AVS_UNIT_TEST(fluf_decode, decode_response_with_location_path) { + uint8_t MSG[] = "\x68" // header v 0x01, Ack, tkl 8 + "\x41\x37\x21" // CREATED code 2.1, msg id 2137 + "\x12\x34\x56\x78\x11\x11\x11\x11" // token + "\x82\x72\x64" // LOCATION_PATH 8 /rd + "\x04\x35\x61\x33\x66" // LOCATION_PATH 8 /5a3f + ; + + fluf_data_t data; + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_decode(MSG, sizeof(MSG) - 1, FLUF_BINDING_UDP, &data)); + + AVS_UNIT_ASSERT_EQUAL(data.coap.coap_udp.message_id, 0x3721); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED( + data.coap.coap_udp.token.bytes, + ((uint8_t[]){ 0x12, 0x34, 0x56, 0x78, 0x11, 0x11, 0x11, 0x11 }), 8); + AVS_UNIT_ASSERT_EQUAL(data.coap.coap_udp.token.size, 8); + AVS_UNIT_ASSERT_EQUAL(data.operation, FLUF_OP_RESPONSE); + AVS_UNIT_ASSERT_EQUAL(data.msg_code, FLUF_COAP_CODE_CREATED); + AVS_UNIT_ASSERT_EQUAL(data.location_path.location_len[0], 4); + AVS_UNIT_ASSERT_EQUAL(data.location_path.location[0][0], '5'); + AVS_UNIT_ASSERT_EQUAL(data.location_path.location_count, 1); +} + +AVS_UNIT_TEST(fluf_decode, decode_error_to_long_uri) { + uint8_t MSG[] = "\x44" // header v 0x01, Confirmable, tkl 4 + "\x01\x21\x37" // GET code 0.1, msg id 3721 + "\x12\x34\x56\x78" // token + "\xB1\x33" // uri-path_1 URI_PATH 11 /3 + "\x01\x33" // uri-path_2 /3 + "\x02\x31\x31" // uri-path_3 /11 + "\x02\x31\x31" // uri-path_4 /11 + "\x02\x31\x31" // uri-path_5 /11 + ; + + fluf_data_t data; + AVS_UNIT_ASSERT_FAILED( + fluf_msg_decode(MSG, sizeof(MSG) - 1, FLUF_BINDING_UDP, &data)); +} + +AVS_UNIT_TEST(fluf_decode, decode_error_incorrect_post) { + uint8_t MSG[] = "\x44" // header v 0x01, Confirmable, tkl 4 + "\x02\x21\x37" // POST code 0.2, msg id 3721 + "\x12\x34\x56\x78" // token + ; + + fluf_data_t data; + AVS_UNIT_ASSERT_FAILED( + fluf_msg_decode(MSG, sizeof(MSG) - 1, FLUF_BINDING_UDP, &data)); +} + +AVS_UNIT_TEST(fluf_decode, decode_error_attr) { + uint8_t MSG[] = + "\x48" // header v 0x01, Confirmable, tkl 8 + "\x03\x37\x21" // PUT code 0.3, msg id 2137 + "\x12\x34\x56\x78\x11\x11\x11\x11" // token + "\xd7\x02\x70\x6d\x69\x6e\x3d\x6e\x30" // URI_QUERY 15 pmin=n0 + ; + + fluf_data_t data; + AVS_UNIT_ASSERT_FAILED( + fluf_msg_decode(MSG, sizeof(MSG) - 1, FLUF_BINDING_UDP, &data)); +} diff --git a/tests/fluf/lwm2m_prepare.c b/tests/fluf/lwm2m_prepare.c new file mode 100644 index 00000000..d10b7e07 --- /dev/null +++ b/tests/fluf/lwm2m_prepare.c @@ -0,0 +1,423 @@ +/* + * Copyright 2023-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 + +AVS_UNIT_TEST(fluf_prepare, prepare_register) { + fluf_data_t data; + memset(&data, 0, sizeof(fluf_data_t)); + uint8_t buff[100]; + size_t out_msg_size; + + data.binding = FLUF_BINDING_UDP; + data.operation = FLUF_OP_REGISTER; + data.content_format = FLUF_COAP_FORMAT_LINK_FORMAT; + data.payload = "<1/1>"; + data.payload_size = 5; + + data.attr.register_attr.has_endpoint = true; + data.attr.register_attr.has_lifetime = true; + data.attr.register_attr.has_lwm2m_ver = true; + data.attr.register_attr.has_Q = true; + data.attr.register_attr.endpoint = "name"; + data.attr.register_attr.lifetime = 120; + data.attr.register_attr.lwm2m_ver = "1.2"; + + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_prepare(&data, buff, sizeof(buff), &out_msg_size)); + + uint8_t EXPECTED[] = + "\x48" // Confirmable, tkl 8 + "\x02\x00\x01" // POST 0x02, msg id 0001 because fluf_init is not + // called + "\x00\x00\x00\x00\x00\x00\x00\x00" // token + "\xb2\x72\x64" // uri path /rd + "\x11\x28" // content_format: application/link-format + "\x37\x65\x70\x3d\x6e\x61\x6d\x65" // uri-query ep=name + "\x06\x6c\x74\x3d\x31\x32\x30" // uri-query lt=120 + "\x09\x6c\x77\x6d\x32\x6d\x3d\x31\x2e\x32" // uri-query lwm2m=1.2 + "\x01\x51" // uri-query Q + "\xFF" + "\x3c\x31\x2f\x31\x3e"; + memcpy(&EXPECTED[4], data.coap.coap_udp.token.bytes, 8); // copy token + + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(buff, EXPECTED, sizeof(EXPECTED) - 1); + AVS_UNIT_ASSERT_EQUAL(out_msg_size, 50); +} + +AVS_UNIT_TEST(fluf_prepare, prepare_update) { + fluf_data_t data; + memset(&data, 0, sizeof(fluf_data_t)); + uint8_t buff[100]; + size_t out_msg_size; + + data.binding = FLUF_BINDING_UDP; + data.operation = FLUF_OP_UPDATE; + data.location_path.location[0] = "name"; + data.location_path.location_len[0] = 4; + data.location_path.location_count = 1; + + data.attr.register_attr.has_sms_number = true; + data.attr.register_attr.has_binding = true; + data.attr.register_attr.binding = "U"; + + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_prepare(&data, buff, sizeof(buff), &out_msg_size)); + + uint8_t EXPECTED[] = "\x48" // Confirmable, tkl 8 + "\x02\x00\x02" // POST 0x02, msg id 0002 + "\x00\x00\x00\x00\x00\x00\x00\x00" // token + "\xb2\x72\x64" // uri path /rd + "\x04\x6e\x61\x6d\x65" // uri path /name + "\x43\x62\x3d\x55" // uri-query b=U + "\x03\x73\x6d\x73" // uri-query sms + ; + memcpy(&EXPECTED[4], data.coap.coap_udp.token.bytes, 8); // copy token + + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(buff, EXPECTED, sizeof(EXPECTED) - 1); + AVS_UNIT_ASSERT_EQUAL(out_msg_size, 28); +} + +AVS_UNIT_TEST(fluf_prepare, prepare_deregister) { + fluf_data_t data; + memset(&data, 0, sizeof(fluf_data_t)); + uint8_t buff[100]; + size_t out_msg_size; + + data.binding = FLUF_BINDING_UDP; + data.operation = FLUF_OP_DEREGISTER; + data.location_path.location[0] = "name"; + data.location_path.location_len[0] = 4; + data.location_path.location_count = 1; + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_prepare(&data, buff, sizeof(buff), &out_msg_size)); + + uint8_t EXPECTED[] = "\x48" // Confirmable, tkl 8 + "\x04\x00\x03" // DELETE 0x04, msg id 0003 + "\x00\x00\x00\x00\x00\x00\x00\x00" // token + "\xb2\x72\x64" // uri path /rd + "\x04\x6e\x61\x6d\x65" // uri path /name + ; + memcpy(&EXPECTED[4], data.coap.coap_udp.token.bytes, 8); // copy token + + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(buff, EXPECTED, sizeof(EXPECTED) - 1); + AVS_UNIT_ASSERT_EQUAL(out_msg_size, 20); +} + +AVS_UNIT_TEST(fluf_prepare, prepare_bootstrap_request) { + fluf_data_t data; + memset(&data, 0, sizeof(fluf_data_t)); + uint8_t buff[100]; + size_t out_msg_size; + + data.binding = FLUF_BINDING_UDP; + data.operation = FLUF_OP_BOOTSTRAP_REQ; + + data.attr.bootstrap_attr.has_endpoint = true; + data.attr.bootstrap_attr.has_pct = true; + data.attr.bootstrap_attr.endpoint = "name"; + data.attr.bootstrap_attr.pct = 60; + + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_prepare(&data, buff, sizeof(buff), &out_msg_size)); + + uint8_t EXPECTED[] = "\x48" // Confirmable, tkl 8 + "\x02\x00\x04" // POST 0x02, msg id 0004 + "\x00\x00\x00\x00\x00\x00\x00\x00" // token + "\xb2\x62\x73" // uri path /bs + "\x47\x65\x70\x3d\x6e\x61\x6d\x65" // uri-query ep=name + "\x06\x70\x63\x74\x3d\x36\x30" // uri-query pct=60 + ; + memcpy(&EXPECTED[4], data.coap.coap_udp.token.bytes, 8); // copy token + + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(buff, EXPECTED, sizeof(EXPECTED) - 1); + AVS_UNIT_ASSERT_EQUAL(out_msg_size, 30); +} + +AVS_UNIT_TEST(fluf_prepare, prepare_bootstrap_pack_request) { + fluf_data_t data; + memset(&data, 0, sizeof(fluf_data_t)); + uint8_t buff[100]; + size_t out_msg_size; + + data.binding = FLUF_BINDING_UDP; + data.operation = FLUF_OP_BOOTSTRAP_PACK_REQ; + data.accept = FLUF_COAP_FORMAT_SENML_ETCH_JSON; + + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_prepare(&data, buff, sizeof(buff), &out_msg_size)); + + uint8_t EXPECTED[] = "\x48" // Confirmable, tkl 8 + "\x01\x00\x05" // GET 0x01, msg id 0005 + "\x00\x00\x00\x00\x00\x00\x00\x00" // token + "\xb6\x62\x73\x70\x61\x63\x6b" // uri path /bspack + "\x62\x01\x40" // accept /ETCH_JSON + + ; + memcpy(&EXPECTED[4], data.coap.coap_udp.token.bytes, 8); // copy token + + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(buff, EXPECTED, sizeof(EXPECTED) - 1); + AVS_UNIT_ASSERT_EQUAL(out_msg_size, 22); +} + +AVS_UNIT_TEST(fluf_prepare, prepare_non_con_notify) { + fluf_data_t data; + memset(&data, 0, sizeof(fluf_data_t)); + uint8_t buff[100]; + size_t out_msg_size; + + data.binding = FLUF_BINDING_UDP; + data.operation = FLUF_OP_INF_NON_CON_NOTIFY; + data.coap.coap_udp.token.size = 2; + data.coap.coap_udp.token.bytes[0] = 0x44; + data.coap.coap_udp.token.bytes[1] = 0x44; + data.content_format = 0; + data.observe_number = 0x2233; + data.payload_size = 3; + data.payload = "211"; + + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_prepare(&data, buff, sizeof(buff), &out_msg_size)); + + uint8_t EXPECTED[] = "\x52" // NonConfirmable, tkl 2 + "\x45\x00\x06" // CONTENT 2.5 msg id 0006 + "\x44\x44" // token + "\x62\x22\x33" // observe 0x2233 + "\x60" // content-format 0 + "\xFF" + "\x32\x31\x31" + + ; + + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(buff, EXPECTED, sizeof(EXPECTED) - 1); + AVS_UNIT_ASSERT_EQUAL(out_msg_size, 14); +} + +AVS_UNIT_TEST(fluf_prepare, prepare_send) { + fluf_data_t data; + memset(&data, 0, sizeof(fluf_data_t)); + uint8_t buff[100]; + size_t out_msg_size; + + data.binding = FLUF_BINDING_UDP; + data.operation = FLUF_OP_INF_SEND; + data.content_format = FLUF_COAP_FORMAT_OPAQUE_STREAM; + data.payload = "<1/1>"; + data.payload_size = 5; + + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_prepare(&data, buff, sizeof(buff), &out_msg_size)); + + uint8_t EXPECTED[] = "\x48" // Confirmable, tkl 8 + "\x02\x00\x07" // POST 0x02, msg id 0007 + "\x00\x00\x00\x00\x00\x00\x00\x00" // token + "\xb2\x64\x70" // uri path /dp + "\x11\x2A" // content_format: octet-stream + "\xFF" + "\x3c\x31\x2f\x31\x3e"; + memcpy(&EXPECTED[4], data.coap.coap_udp.token.bytes, 8); // copy token + + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(buff, EXPECTED, sizeof(EXPECTED) - 1); + AVS_UNIT_ASSERT_EQUAL(out_msg_size, 23); +} + +AVS_UNIT_TEST(fluf_prepare, prepare_con_notify) { + fluf_data_t data; + memset(&data, 0, sizeof(fluf_data_t)); + uint8_t buff[100]; + size_t out_msg_size; + + data.binding = FLUF_BINDING_UDP; + data.operation = FLUF_OP_INF_CON_NOTIFY; + data.coap.coap_udp.token.size = 2; + data.coap.coap_udp.token.bytes[0] = 0x44; + data.coap.coap_udp.token.bytes[1] = 0x44; + data.content_format = 0; + data.observe_number = 0x2233; + data.payload_size = 3; + data.payload = "211"; + + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_prepare(&data, buff, sizeof(buff), &out_msg_size)); + + uint8_t EXPECTED[] = "\x42" // Confirmable, tkl 2 + "\x45\x00\x08" // CONTENT 2.5 msg id 0008 + "\x44\x44" // token + "\x62\x22\x33" // observe 0x2233 + "\x60" // content-format 0 + "\xFF" + "\x32\x31\x31" + + ; + + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(buff, EXPECTED, sizeof(EXPECTED) - 1); + AVS_UNIT_ASSERT_EQUAL(out_msg_size, 14); +} + +AVS_UNIT_TEST(fluf_prepare, prepare_response) { + fluf_data_t data; + memset(&data, 0, sizeof(fluf_data_t)); + uint8_t buff[100]; + size_t out_msg_size; + + data.binding = FLUF_BINDING_UDP; + data.operation = FLUF_OP_RESPONSE; + data.msg_code = FLUF_COAP_CODE_CREATED; + // msd_id and token are normally taken from request + data.coap.coap_udp.message_id = 0x2222; + data.coap.coap_udp.token.size = 3; + data.coap.coap_udp.token.bytes[0] = 0x11; + data.coap.coap_udp.token.bytes[1] = 0x22; + data.coap.coap_udp.token.bytes[2] = 0x33; + + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_prepare(&data, buff, sizeof(buff), &out_msg_size)); + + uint8_t EXPECTED[] = "\x63" // ACK, tkl 3 + "\x41\x22\x22" // CREATED 0x41 + "\x11\x22\x33" // token + ; + + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(buff, EXPECTED, sizeof(EXPECTED) - 1); + AVS_UNIT_ASSERT_EQUAL(out_msg_size, 7); +} + +AVS_UNIT_TEST(fluf_prepare, prepare_response_with_etag) { + fluf_data_t data; + memset(&data, 0, sizeof(fluf_data_t)); + uint8_t buff[100]; + size_t out_msg_size; + + data.binding = FLUF_BINDING_UDP; + data.operation = FLUF_OP_RESPONSE; + data.msg_code = FLUF_COAP_CODE_CREATED; + // msd_id and token are normally taken from request + data.coap.coap_udp.message_id = 0x2222; + data.coap.coap_udp.token.size = 3; + data.coap.coap_udp.token.bytes[0] = 0x11; + data.coap.coap_udp.token.bytes[1] = 0x22; + data.coap.coap_udp.token.bytes[2] = 0x33; + data.etag.bytes[0] = '3'; + data.etag.bytes[1] = '3'; + data.etag.bytes[2] = '2'; + data.etag.size = 3; + + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_prepare(&data, buff, sizeof(buff), &out_msg_size)); + + uint8_t EXPECTED[] = "\x63" // ACK, tkl 3 + "\x41\x22\x22" // CREATED 0x41 + "\x11\x22\x33" // token + "\x43\x33\x33\x32" // etag 3 332 + ; + + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(buff, EXPECTED, sizeof(EXPECTED) - 1); + AVS_UNIT_ASSERT_EQUAL(out_msg_size, 11); +} + +AVS_UNIT_TEST(fluf_prepare, prepare_response_with_payload) { + fluf_data_t data; + memset(&data, 0, sizeof(fluf_data_t)); + uint8_t buff[100]; + size_t out_msg_size; + + data.binding = FLUF_BINDING_UDP; + data.operation = FLUF_OP_RESPONSE; + data.msg_code = FLUF_COAP_CODE_CONTENT; + data.content_format = FLUF_COAP_FORMAT_CBOR; + data.payload_size = 5; + data.payload = "00000"; + + data.coap.coap_udp.message_id = 0x2222; + data.coap.coap_udp.token.size = 3; + data.coap.coap_udp.token.bytes[0] = 0x11; + data.coap.coap_udp.token.bytes[1] = 0x22; + data.coap.coap_udp.token.bytes[2] = 0x33; + + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_prepare(&data, buff, sizeof(buff), &out_msg_size)); + + uint8_t EXPECTED[] = "\x63" // ACK, tkl 3 + "\x45\x22\x22" // CONTENT 0x45 + "\x11\x22\x33" // token + "\xC1\x3C" // content_format: cbor + "\xFF" + "\x30\x30\x30\x30\x30"; + + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(buff, EXPECTED, sizeof(EXPECTED) - 1); + AVS_UNIT_ASSERT_EQUAL(out_msg_size, 15); +} + +AVS_UNIT_TEST(fluf_prepare, prepare_response_with_block) { + fluf_data_t data; + memset(&data, 0, sizeof(fluf_data_t)); + uint8_t buff[100]; + size_t out_msg_size; + + data.binding = FLUF_BINDING_UDP; + data.operation = FLUF_OP_RESPONSE; + data.msg_code = FLUF_COAP_CODE_CONTENT; + data.payload_size = 5; + data.payload = "00000"; + + data.block.block_type = FLUF_OPTION_BLOCK_2; + data.block.size = 512; + data.block.number = 132; + data.block.more_flag = true; + + data.coap.coap_udp.message_id = 0x2222; + data.coap.coap_udp.token.size = 3; + data.coap.coap_udp.token.bytes[0] = 0x11; + data.coap.coap_udp.token.bytes[1] = 0x22; + data.coap.coap_udp.token.bytes[2] = 0x33; + + AVS_UNIT_ASSERT_SUCCESS( + fluf_msg_prepare(&data, buff, sizeof(buff), &out_msg_size)); + + uint8_t EXPECTED[] = "\x63" // ACK, tkl 3 + "\x45\x22\x22" // CONTENT 0x45 + "\x11\x22\x33" // token + "\xC0" // CONTENT_FORMAT 12 + "\xb2\x08\x4D" // block2 23 + "\xFF" + "\x30\x30\x30\x30\x30"; + + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(buff, EXPECTED, sizeof(EXPECTED) - 1); + AVS_UNIT_ASSERT_EQUAL(out_msg_size, 17); +} + +AVS_UNIT_TEST(fluf_prepare, prepare_error_buff_size) { + fluf_data_t data; + memset(&data, 0, sizeof(fluf_data_t)); + uint8_t buff[100]; + size_t out_msg_size; + + data.binding = FLUF_BINDING_UDP; + data.operation = FLUF_OP_REGISTER; + data.content_format = FLUF_COAP_FORMAT_LINK_FORMAT; + data.payload = "<1/1><1/1>"; + data.payload_size = 10; + data.attr.register_attr.has_endpoint = true; + data.attr.register_attr.has_lifetime = true; + data.attr.register_attr.has_lwm2m_ver = true; + data.attr.register_attr.has_Q = true; + data.attr.register_attr.endpoint = "name"; + data.attr.register_attr.lifetime = 120; + data.attr.register_attr.lwm2m_ver = "1.2"; + + for (size_t i = 1; i < 55; i++) { + AVS_UNIT_ASSERT_FAILED(fluf_msg_prepare(&data, buff, i, &out_msg_size)); + } + AVS_UNIT_ASSERT_SUCCESS(fluf_msg_prepare(&data, buff, 55, &out_msg_size)); + AVS_UNIT_ASSERT_EQUAL(out_msg_size, 55); +} diff --git a/tests/fluf/options.c b/tests/fluf/options.c new file mode 100644 index 00000000..a647e74c --- /dev/null +++ b/tests/fluf/options.c @@ -0,0 +1,327 @@ +/* + * Copyright 2023-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 "../../src/fluf/fluf_options.h" + +#define _FLUF_COAP_OPTIONS_INIT_EMPTY_WITH_BUFF(Name, OptionsSize, \ + MsgBuffSize) \ + fluf_coap_option_t _Opt##Name[OptionsSize]; \ + uint8_t _OptionMsgBuffer##Name[MsgBuffSize] = { 0 }; \ + fluf_coap_options_t Name = { \ + .options_size = OptionsSize, \ + .options_number = 0, \ + .options = _Opt##Name, \ + .buff_size = MsgBuffSize, \ + .buff_begin = (void *) _OptionMsgBuffer##Name \ + } + +AVS_UNIT_TEST(coap_options, insert_last) { + _FLUF_COAP_OPTIONS_INIT_EMPTY_WITH_BUFF(opts, 10, 50); + + AVS_UNIT_ASSERT_SUCCESS( + _fluf_coap_options_add_data(&opts, 0, "0", 1)); // num 0 + AVS_UNIT_ASSERT_SUCCESS( + _fluf_coap_options_add_string(&opts, 1, "1")); // num 1 + AVS_UNIT_ASSERT_SUCCESS( + _fluf_coap_options_add_u16(&opts, 3, 0x1234)); // num 3 + AVS_UNIT_ASSERT_SUCCESS( + _fluf_coap_options_add_u32(&opts, 4, 0x12345678)); // num 4 + + const uint8_t EXPECTED[] = "\x01\x30" // num 0 (+0), "0" + "\x11\x31" // num 1 (+1), "1" + "\x22\x12\x34" // num 3 (+1), 0x1234 + "\x14\x12\x34\x56\x78" // num 4 (+1), 0x12345678 + ; + + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(opts.buff_begin, EXPECTED, + sizeof(EXPECTED) - 1); +} + +AVS_UNIT_TEST(coap_options, insert_first) { + _FLUF_COAP_OPTIONS_INIT_EMPTY_WITH_BUFF(opts, 10, 50); + + AVS_UNIT_ASSERT_SUCCESS( + _fluf_coap_options_add_u32(&opts, 4, 0x12345678)); // num 4 + AVS_UNIT_ASSERT_SUCCESS( + _fluf_coap_options_add_u16(&opts, 3, 0x1234)); // num 3 + AVS_UNIT_ASSERT_SUCCESS( + _fluf_coap_options_add_string(&opts, 1, "1")); // num 1 + AVS_UNIT_ASSERT_SUCCESS( + _fluf_coap_options_add_data(&opts, 0, "0", 1)); // num 0 + + const uint8_t EXPECTED[] = "\x01\x30" // num 0 (+0), "0" + "\x11\x31" // num 1 (+1), "1" + "\x22\x12\x34" // num 3 (+1), 0x1234 + "\x14\x12\x34\x56\x78" // num 4 (+1), 0x12345678 + ; + + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(opts.buff_begin, EXPECTED, + sizeof(EXPECTED) - 1); +} + +AVS_UNIT_TEST(coap_options, insert_not_enough_space) { + _FLUF_COAP_OPTIONS_INIT_EMPTY_WITH_BUFF(opts, 10, 10); + + AVS_UNIT_ASSERT_SUCCESS(_fluf_coap_options_add_string(&opts, 1, "123456")); + AVS_UNIT_ASSERT_FAILED(_fluf_coap_options_add_string(&opts, 0, "123456")); +} + +AVS_UNIT_TEST(coap_options, insert_not_enough_space_in_options_array) { + _FLUF_COAP_OPTIONS_INIT_EMPTY_WITH_BUFF(opts, 2, 50); + + AVS_UNIT_ASSERT_SUCCESS(_fluf_coap_options_add_string(&opts, 1, "123456")); + AVS_UNIT_ASSERT_SUCCESS(_fluf_coap_options_add_string(&opts, 2, "123456")); + AVS_UNIT_ASSERT_FAILED(_fluf_coap_options_add_string(&opts, 0, "123456")); +} + +AVS_UNIT_TEST(coap_options, insert_middle) { + _FLUF_COAP_OPTIONS_INIT_EMPTY_WITH_BUFF(opts, 10, 50); + + AVS_UNIT_ASSERT_SUCCESS( + _fluf_coap_options_add_data(&opts, 0, "0", 1)); // num 0 + AVS_UNIT_ASSERT_SUCCESS( + _fluf_coap_options_add_string(&opts, 1, "1")); // num 1 + AVS_UNIT_ASSERT_SUCCESS( + _fluf_coap_options_add_u16(&opts, 12, 0x4444)); // num 12 + AVS_UNIT_ASSERT_SUCCESS( + _fluf_coap_options_add_u32(&opts, 4, 0x12345678)); // num 4 + AVS_UNIT_ASSERT_SUCCESS( + _fluf_coap_options_add_u16(&opts, 3, 0x1234)); // num 3 + + const uint8_t EXPECTED[] = "\x01\x30" // num 0 (+0), "0" + "\x11\x31" // num 1 (+1), "1" + "\x22\x12\x34" // num 3 (+1), 0x1234 + "\x14\x12\x34\x56\x78" // num 4 (+1), 0x12345678 + "\x82\x44\x44" // num 12 (+8), 0x4444 + ; + + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(opts.buff_begin, EXPECTED, + sizeof(EXPECTED) - 1); +} + +AVS_UNIT_TEST(coap_options, insert_repeated) { + _FLUF_COAP_OPTIONS_INIT_EMPTY_WITH_BUFF(opts, 10, 50); + + AVS_UNIT_ASSERT_SUCCESS( + _fluf_coap_options_add_data(&opts, 0, "0", 1)); // num 0 + AVS_UNIT_ASSERT_SUCCESS( + _fluf_coap_options_add_string(&opts, 1, "1")); // num 1 + AVS_UNIT_ASSERT_SUCCESS( + _fluf_coap_options_add_u16(&opts, 12, 0x4444)); // num 12 + AVS_UNIT_ASSERT_SUCCESS( + _fluf_coap_options_add_u32(&opts, 4, 0x12345678)); // num 4 + AVS_UNIT_ASSERT_SUCCESS( + _fluf_coap_options_add_u16(&opts, 3, 0x1234)); // num 3 + AVS_UNIT_ASSERT_SUCCESS( + _fluf_coap_options_add_string(&opts, 1, "2")); // num 1 + AVS_UNIT_ASSERT_SUCCESS( + _fluf_coap_options_add_string(&opts, 1, "3")); // num 1 + + const uint8_t EXPECTED[] = "\x01\x30" // num 0 (+0), "0" + "\x11\x31" // num 1 (+1), "1" + "\x01\x32" // num 1 (+1), "2" + "\x01\x33" // num 1 (+1), "3" + "\x22\x12\x34" // num 3 (+1), 0x1234 + "\x14\x12\x34\x56\x78" // num 4 (+1), 0x12345678 + "\x82\x44\x44" // num 12 (+8), 0x4444 + ; + + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(opts.buff_begin, EXPECTED, + sizeof(EXPECTED) - 1); +} + +AVS_UNIT_TEST(coap_options, content_format) { + _FLUF_COAP_OPTIONS_INIT_EMPTY_WITH_BUFF(opts, 10, 50); + + uint16_t content_format_1 = FLUF_COAP_FORMAT_PLAINTEXT; + uint16_t content_format_2 = FLUF_COAP_FORMAT_CBOR; + uint16_t content_format_3 = FLUF_COAP_FORMAT_OMA_LWM2M_CBOR; + + AVS_UNIT_ASSERT_SUCCESS(_fluf_coap_options_add_u16( + &opts, _FLUF_COAP_OPTION_CONTENT_FORMAT, content_format_2)); + AVS_UNIT_ASSERT_SUCCESS(_fluf_coap_options_add_u16( + &opts, _FLUF_COAP_OPTION_CONTENT_FORMAT, content_format_1)); + AVS_UNIT_ASSERT_SUCCESS(_fluf_coap_options_add_u16( + &opts, _FLUF_COAP_OPTION_CONTENT_FORMAT, content_format_3)); + + const uint8_t EXPECTED[] = "\xC1\x3c" + "\x00" + "\x02\x2D\x18"; + + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(opts.buff_begin, EXPECTED, + sizeof(EXPECTED) - 1); +} + +AVS_UNIT_TEST(coap_options, get_string) { + char opt1[] = "opt1"; + char opt2[] = "opt_2"; + + _FLUF_COAP_OPTIONS_INIT_EMPTY_WITH_BUFF(opts, 5, 20); + memset(opts.buff_begin, 0xFF, + 20); // _fluf_coap_options_decode end on 0xFF marker or buffer end + + AVS_UNIT_ASSERT_SUCCESS(_fluf_coap_options_add_string( + &opts, _FLUF_COAP_OPTION_URI_PATH, opt1)); + AVS_UNIT_ASSERT_SUCCESS(_fluf_coap_options_add_string( + &opts, _FLUF_COAP_OPTION_URI_PATH, opt2)); + + char buffer[32]; + size_t option_size; + size_t iterator = 0; + size_t bytes_read = 0; + size_t msg_size = 100; + _FLUF_COAP_OPTIONS_INIT_EMPTY(opts_r, 5); + + AVS_UNIT_ASSERT_SUCCESS(_fluf_coap_options_decode(&opts_r, opts.buff_begin, + msg_size, &bytes_read)); + + AVS_UNIT_ASSERT_EQUAL(_fluf_coap_options_get_string_iterate( + &opts_r, _FLUF_COAP_OPTION_URI_PATH, + &iterator, &option_size, buffer, + sizeof(buffer)), + 0); + AVS_UNIT_ASSERT_EQUAL(option_size, sizeof(opt1)); + AVS_UNIT_ASSERT_EQUAL_BYTES(buffer, opt1); + + AVS_UNIT_ASSERT_EQUAL(_fluf_coap_options_get_string_iterate( + &opts_r, _FLUF_COAP_OPTION_URI_PATH, + &iterator, &option_size, buffer, + sizeof(buffer)), + 0); + AVS_UNIT_ASSERT_EQUAL(option_size, sizeof(opt2)); + AVS_UNIT_ASSERT_EQUAL_BYTES(buffer, opt2); + + AVS_UNIT_ASSERT_EQUAL(_fluf_coap_options_get_string_iterate( + &opts_r, _FLUF_COAP_OPTION_URI_PATH, + &iterator, &option_size, buffer, + sizeof(buffer)), + _FLUF_COAP_OPTION_MISSING); +} + +AVS_UNIT_TEST(coap_options, get_many_options) { + char opt1[] = "1"; + char opt2[] = "_2"; + char opt3[] = "_3____________________"; + uint8_t opt4 = 0x22; + uint16_t opt5 = 0x2277; + uint32_t opt6 = 0x21372137; + + _FLUF_COAP_OPTIONS_INIT_EMPTY_WITH_BUFF(opts, 6, 100); + memset(opts.buff_begin, 0xFF, + 100); // _fluf_coap_options_decode end on 0xFF marker or buffer end + + AVS_UNIT_ASSERT_SUCCESS(_fluf_coap_options_add_string( + &opts, _FLUF_COAP_OPTION_PROXY_URI, opt1)); + AVS_UNIT_ASSERT_SUCCESS(_fluf_coap_options_add_string( + &opts, _FLUF_COAP_OPTION_MAX_AGE, opt2)); + AVS_UNIT_ASSERT_SUCCESS(_fluf_coap_options_add_data( + &opts, _FLUF_COAP_OPTION_MAX_AGE, opt3, sizeof(opt3) - 1)); + AVS_UNIT_ASSERT_SUCCESS(_fluf_coap_options_add_data( + &opts, _FLUF_COAP_OPTION_URI_PORT, &opt4, 1)); + AVS_UNIT_ASSERT_SUCCESS(_fluf_coap_options_add_u16( + &opts, _FLUF_COAP_OPTION_URI_PORT, opt5)); + AVS_UNIT_ASSERT_SUCCESS( + _fluf_coap_options_add_u32(&opts, _FLUF_COAP_OPTION_OBSERVE, opt6)); + + char buffer[100]; + size_t option_size; + size_t iterator = 0; + size_t bytes_read = 0; + size_t msg_size = 100; + _FLUF_COAP_OPTIONS_INIT_EMPTY(opts_r, 6); + + AVS_UNIT_ASSERT_SUCCESS(_fluf_coap_options_decode(&opts_r, opts.buff_begin, + msg_size, &bytes_read)); + AVS_UNIT_ASSERT_EQUAL(opts_r.options_number, 6); + + AVS_UNIT_ASSERT_EQUAL(_fluf_coap_options_get_string_iterate( + &opts_r, _FLUF_COAP_OPTION_PROXY_URI, NULL, + &option_size, buffer, sizeof(buffer)), + 0); + AVS_UNIT_ASSERT_EQUAL(option_size, sizeof(opt1)); + AVS_UNIT_ASSERT_EQUAL_BYTES(buffer, opt1); + + AVS_UNIT_ASSERT_EQUAL(_fluf_coap_options_get_string_iterate( + &opts_r, _FLUF_COAP_OPTION_MAX_AGE, &iterator, + &option_size, buffer, sizeof(buffer)), + 0); + AVS_UNIT_ASSERT_EQUAL(option_size, sizeof(opt2)); + AVS_UNIT_ASSERT_EQUAL_BYTES(buffer, opt2); + AVS_UNIT_ASSERT_EQUAL(_fluf_coap_options_get_string_iterate( + &opts_r, _FLUF_COAP_OPTION_MAX_AGE, &iterator, + &option_size, buffer, sizeof(buffer)), + 0); + AVS_UNIT_ASSERT_EQUAL(option_size, sizeof(opt3)); + AVS_UNIT_ASSERT_EQUAL_BYTES(buffer, opt3); + + iterator = 0; + AVS_UNIT_ASSERT_EQUAL(_fluf_coap_options_get_data_iterate( + &opts_r, _FLUF_COAP_OPTION_URI_PORT, + &iterator, &option_size, buffer, + sizeof(buffer)), + 0); + AVS_UNIT_ASSERT_EQUAL(option_size, sizeof(opt4)); + AVS_UNIT_ASSERT_EQUAL(buffer[0], opt4); + uint16_t u16_value = 0; + AVS_UNIT_ASSERT_EQUAL(_fluf_coap_options_get_u16_iterate( + &opts_r, _FLUF_COAP_OPTION_URI_PORT, + &iterator, &u16_value), + 0); + AVS_UNIT_ASSERT_EQUAL(u16_value, opt5); + AVS_UNIT_ASSERT_EQUAL(_fluf_coap_options_get_data_iterate( + &opts_r, _FLUF_COAP_OPTION_URI_PORT, + &iterator, &option_size, buffer, + sizeof(buffer)), + _FLUF_COAP_OPTION_MISSING); + + uint32_t u32_value = 0; + AVS_UNIT_ASSERT_EQUAL(_fluf_coap_options_get_u32_iterate( + &opts_r, _FLUF_COAP_OPTION_OBSERVE, NULL, + &u32_value), + 0); + AVS_UNIT_ASSERT_EQUAL(u32_value, opt6); +} + +AVS_UNIT_TEST(coap_options, get_options_errors_check) { + char opt1[] = "1"; + char opt2[] = "_2"; + char opt3[] = "_3____________________"; + + _FLUF_COAP_OPTIONS_INIT_EMPTY_WITH_BUFF(opts1, 2, 100); + AVS_UNIT_ASSERT_SUCCESS(_fluf_coap_options_add_string( + &opts1, _FLUF_COAP_OPTION_PROXY_URI, opt1)); + AVS_UNIT_ASSERT_SUCCESS(_fluf_coap_options_add_string( + &opts1, _FLUF_COAP_OPTION_MAX_AGE, opt2)); + AVS_UNIT_ASSERT_FAILED(_fluf_coap_options_add_data( + &opts1, _FLUF_COAP_OPTION_MAX_AGE, opt3, sizeof(opt3) - 1)); + + _FLUF_COAP_OPTIONS_INIT_EMPTY_WITH_BUFF(opts2, 3, 10); + memset(opts2.buff_begin, 0xFF, 10); + AVS_UNIT_ASSERT_SUCCESS(_fluf_coap_options_add_string( + &opts2, _FLUF_COAP_OPTION_PROXY_URI, opt1)); + AVS_UNIT_ASSERT_SUCCESS(_fluf_coap_options_add_string( + &opts2, _FLUF_COAP_OPTION_MAX_AGE, opt2)); + AVS_UNIT_ASSERT_FAILED(_fluf_coap_options_add_data( + &opts2, _FLUF_COAP_OPTION_MAX_AGE, opt3, sizeof(opt3) - 1)); + + size_t bytes_read = 0; + _FLUF_COAP_OPTIONS_INIT_EMPTY(opts_r_1, 5); + _FLUF_COAP_OPTIONS_INIT_EMPTY(opts_r_2, 1); + _FLUF_COAP_OPTIONS_INIT_EMPTY(opts_r_3, 2); + AVS_UNIT_ASSERT_FAILED( + _fluf_coap_options_decode(&opts_r_1, opts1.buff_begin, 100, + &bytes_read)); // no 0xFF marker + AVS_UNIT_ASSERT_FAILED( + _fluf_coap_options_decode(&opts_r_2, opts2.buff_begin, 100, + &bytes_read)); // opt array too small + AVS_UNIT_ASSERT_SUCCESS(_fluf_coap_options_decode( + &opts_r_3, opts2.buff_begin, 100, &bytes_read)); +} diff --git a/tests/fluf/register_payload.c b/tests/fluf/register_payload.c new file mode 100644 index 00000000..410177b6 --- /dev/null +++ b/tests/fluf/register_payload.c @@ -0,0 +1,373 @@ +/* + * Copyright 2023-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 + +#define VERIFY_PAYLOAD(Payload, Buff, Len) \ + do { \ + AVS_UNIT_ASSERT_EQUAL(Len, strlen(Buff)); \ + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(Payload, Buff, Len); \ + } while (0) + +AVS_UNIT_TEST(register_payload, only_objects) { + fluf_io_register_ctx_t ctx; + char out_buff[100] = { 0 }; + size_t copied_bytes = 0; + size_t msg_len = 0; + + fluf_io_register_ctx_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(1), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(2), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(3), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(4), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(5), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + VERIFY_PAYLOAD(",,,,", out_buff, msg_len); +} + +AVS_UNIT_TEST(register_payload, objects_with_version) { + fluf_io_register_ctx_t ctx; + char out_buff[100] = { 0 }; + size_t copied_bytes = 0; + size_t msg_len = 0; + + fluf_io_register_ctx_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(1), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(2), "1.2")); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(3), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(4), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(5), "2.3")); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + + VERIFY_PAYLOAD(",;ver=1.2,,,;ver=2.3", out_buff, + msg_len); +} + +AVS_UNIT_TEST(register_payload, objects_with_instances) { + fluf_io_register_ctx_t ctx; + char out_buff[100] = { 0 }; + size_t copied_bytes = 0; + size_t msg_len = 0; + + fluf_io_register_ctx_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(1, 0), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(1, 1), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(2), "1.2")); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(2, 0), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(3, 0), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(3, 1), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(3, 2), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(3, 3), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(4), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(5), "2.3")); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + + VERIFY_PAYLOAD(",,;ver=1.2,,,,,,,;ver=2.3", + out_buff, msg_len); +} + +AVS_UNIT_TEST(register_payload, instances_without_version) { + fluf_io_register_ctx_t ctx; + char out_buff[100] = { 0 }; + size_t copied_bytes = 0; + size_t msg_len = 0; + + fluf_io_register_ctx_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(1, 0), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(1, 1), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(2, 0), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(3, 0), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(3, 1), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(3, 2), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(3, 3), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + + VERIFY_PAYLOAD(",,,,,,,,", + out_buff, msg_len); +} + +AVS_UNIT_TEST(register_payload, instances_with_version) { + fluf_io_register_ctx_t ctx; + char out_buff[100] = { 0 }; + size_t copied_bytes = 0; + size_t msg_len = 0; + + fluf_io_register_ctx_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(1), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(3, 3), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(4), "1.1")); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(5, 0), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + + VERIFY_PAYLOAD(",,;ver=1.1,", out_buff, msg_len); +} + +AVS_UNIT_TEST(register_payload, big_numbers) { + fluf_io_register_ctx_t ctx; + char out_buff[100] = { 0 }; + size_t copied_bytes = 0; + size_t msg_len = 0; + + fluf_io_register_ctx_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(1, 0), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(1, 1), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(22), "1.2")); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(22, 0), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(333, 0), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(333, 1), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(333, 2), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(333, 3333), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(4444), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(55555), "2.3")); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + + VERIFY_PAYLOAD(",,;ver=1.2,,,,,,,;ver=2.3", + out_buff, msg_len); +} + +AVS_UNIT_TEST(register_payload, errors) { + fluf_io_register_ctx_t ctx; + char out_buff[100] = { 0 }; + size_t copied_bytes = 0; + size_t msg_len = 0; + + fluf_io_register_ctx_init(&ctx); + + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(2), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_FAILED(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(1), NULL)); + + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(2, 0), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(2, 2), NULL)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_FAILED(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_INSTANCE_PATH(2, 1), NULL)); + + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(22), "1.2")); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_FAILED(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(23), "12")); + AVS_UNIT_ASSERT_FAILED(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(23), ".12")); + AVS_UNIT_ASSERT_FAILED(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(23), "12.")); + AVS_UNIT_ASSERT_FAILED(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(23), "a.2")); + AVS_UNIT_ASSERT_FAILED(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(23), "2.b")); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(23), "1.2")); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_get_payload( + &ctx, &out_buff[msg_len], 100, &copied_bytes)); + msg_len += copied_bytes; + + VERIFY_PAYLOAD(",,,;ver=1.2,;ver=1.2", out_buff, + msg_len); +} + +AVS_UNIT_TEST(register_payload, block_transfer) { + for (size_t i = 5; i < 25; i++) { + fluf_io_register_ctx_t ctx; + char out_buff[50] = { 0 }; + fluf_io_register_ctx_init(&ctx); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_register_ctx_new_entry( + &ctx, &FLUF_MAKE_OBJECT_PATH(65222), "9.9")); + + int res = -1; + size_t copied_bytes = 0; + size_t msg_len = 0; + while (res) { + res = fluf_io_register_ctx_get_payload(&ctx, &out_buff[msg_len], i, + &copied_bytes); + msg_len += copied_bytes; + AVS_UNIT_ASSERT_TRUE(res == 0 || res == FLUF_IO_NEED_NEXT_CALL); + } + VERIFY_PAYLOAD(";ver=9.9", out_buff, msg_len); + } +} diff --git a/tests/fluf/senml_cbor_encoder.c b/tests/fluf/senml_cbor_encoder.c new file mode 100644 index 00000000..fc0d3a70 --- /dev/null +++ b/tests/fluf/senml_cbor_encoder.c @@ -0,0 +1,600 @@ +/* + * Copyright 2023-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 "../../src/fluf/fluf_cbor_encoder.h" + +typedef struct { + fluf_io_out_ctx_t ctx; + fluf_op_t op_type; + char buf[500]; + size_t buffer_length; + size_t out_length; +} senml_cbor_test_env_t; + +static void senml_cbor_test_setup(senml_cbor_test_env_t *env, + fluf_uri_path_t *base_path, + size_t items_count, + fluf_op_t op_type) { + env->buffer_length = sizeof(env->buf); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_out_ctx_init(&env->ctx, op_type, base_path, + items_count, + FLUF_COAP_FORMAT_SENML_CBOR)); +} + +typedef struct { + const char *data; + size_t size; +} test_data_t; + +#define MAKE_TEST_DATA(Data) \ + (test_data_t) { \ + .data = Data, \ + .size = sizeof(Data) - 1 \ + } + +#define VERIFY_BYTES(Env, Data) \ + do { \ + AVS_UNIT_ASSERT_EQUAL_BYTES(Env.buf, Data); \ + AVS_UNIT_ASSERT_EQUAL(Env.out_length, sizeof(Data) - 1); \ + } while (0) + +AVS_UNIT_TEST(senml_cbor_encoder, single_send_record_with_all_fields) { + senml_cbor_test_env_t env = { 0 }; + + senml_cbor_test_setup(&env, NULL, 1, FLUF_OP_INF_SEND); + + fluf_io_out_entry_t entry = { + .timestamp = 100000.0, + .path = FLUF_MAKE_RESOURCE_PATH(3, 3, 3), + .type = FLUF_DATA_TYPE_UINT, + .value.uint_value = 25 + }; + + AVS_UNIT_ASSERT_SUCCESS(fluf_io_out_ctx_new_entry(&env.ctx, &entry)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_out_ctx_get_payload( + &env.ctx, env.buf, env.buffer_length, &env.out_length)); + VERIFY_BYTES(env, "\x81\xA3" + "\x00\x66\x2F\x33\x2F\x33\x2F\x33" // name + "\x22\xFA\x47\xC3\x50\x00" // base time + "\x02\x18\x19"); +} + +AVS_UNIT_TEST(senml_cbor_encoder, single_read_record_with_all_fields) { + senml_cbor_test_env_t env = { 0 }; + + fluf_uri_path_t base_path = FLUF_MAKE_INSTANCE_PATH(3, 3); + senml_cbor_test_setup(&env, &base_path, 1, FLUF_OP_DM_READ); + + fluf_io_out_entry_t entry = { + .path = FLUF_MAKE_RESOURCE_PATH(3, 3, 3), + .type = FLUF_DATA_TYPE_UINT, + .value.uint_value = 25 + }; + + AVS_UNIT_ASSERT_SUCCESS(fluf_io_out_ctx_new_entry(&env.ctx, &entry)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_out_ctx_get_payload( + &env.ctx, env.buf, env.buffer_length, &env.out_length)); + VERIFY_BYTES(env, "\x81\xA3" + "\x21\x64\x2F\x33\x2F\x33" // base name + "\x00\x62\x2F\x33" // name + "\x02\x18\x19"); +} + +AVS_UNIT_TEST(senml_cbor_encoder, largest_possible_size_of_single_msg) { + senml_cbor_test_env_t env = { 0 }; + + fluf_uri_path_t base_path = FLUF_MAKE_INSTANCE_PATH(65534, 65534); + env.buffer_length = sizeof(env.buf); + env.ctx._format = FLUF_COAP_FORMAT_SENML_CBOR; + // call _fluf_senml_cbor_encoder_init directly to allow to set basename and + // timestamp in one message + AVS_UNIT_ASSERT_SUCCESS( + _fluf_senml_cbor_encoder_init(&env.ctx, &base_path, 65534, true)); + + fluf_io_out_entry_t entry = { + .timestamp = 1.0e+300, + .path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(65534, 65534, 65534, 65534), + .type = FLUF_DATA_TYPE_OBJLNK, + .value.objlnk.oid = 65534, + .value.objlnk.iid = 65534 + }; + + AVS_UNIT_ASSERT_SUCCESS(fluf_io_out_ctx_new_entry(&env.ctx, &entry)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_out_ctx_get_payload( + &env.ctx, env.buf, env.buffer_length, &env.out_length)); + VERIFY_BYTES( + env, + "\x99\xFF\xFE\xA4" + "\x21\x6C\x2F\x36\x35\x35\x33\x34\x2F\x36\x35\x35\x33\x34" // basename + "\x00\x6C\x2F\x36\x35\x35\x33\x34\x2F\x36\x35\x35\x33\x34" // name + "\x22\xFB\x7E\x37\xE4\x3C\x88\x00\x75\x9C" // base time + "\x63" + "vlo" // objlink + "\x6B\x36\x35\x35\x33\x34\x3A\x36\x35\x35\x33\x34"); + AVS_UNIT_ASSERT_EQUAL(env.out_length, + _FLUF_IO_SENML_CBOR_SIMPLE_RECORD_MAX_LENGTH - 1); +} + +AVS_UNIT_TEST(senml_cbor_encoder, int) { + senml_cbor_test_env_t env = { 0 }; + + senml_cbor_test_setup(&env, NULL, 1, FLUF_OP_INF_NON_CON_NOTIFY); + + fluf_io_out_entry_t entry = { + .timestamp = NAN, + .path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(77, 77, 77, 77), + .type = FLUF_DATA_TYPE_INT, + .value.int_value = -1000 + }; + + AVS_UNIT_ASSERT_SUCCESS(fluf_io_out_ctx_new_entry(&env.ctx, &entry)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_out_ctx_get_payload( + &env.ctx, env.buf, env.buffer_length, &env.out_length)); + VERIFY_BYTES(env, "\x81\xA2" + "\x00\x6C\x2F\x37\x37\x2F\x37\x37\x2F\x37\x37\x2F\x37\x37" + "\x02\x39\x03\xE7"); +} + +AVS_UNIT_TEST(senml_cbor_encoder, uint) { + senml_cbor_test_env_t env = { 0 }; + + senml_cbor_test_setup(&env, NULL, 1, FLUF_OP_INF_NON_CON_NOTIFY); + + fluf_io_out_entry_t entry = { + .timestamp = NAN, + .path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(77, 77, 77, 77), + .type = FLUF_DATA_TYPE_UINT, + .value.uint_value = UINT32_MAX + }; + + AVS_UNIT_ASSERT_SUCCESS(fluf_io_out_ctx_new_entry(&env.ctx, &entry)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_out_ctx_get_payload( + &env.ctx, env.buf, env.buffer_length, &env.out_length)); + VERIFY_BYTES(env, "\x81\xA2" + "\x00\x6C\x2F\x37\x37\x2F\x37\x37\x2F\x37\x37\x2F\x37\x37" + "\x02\x1A\xFF\xFF\xFF\xFF"); +} + +AVS_UNIT_TEST(senml_cbor_encoder, time) { + senml_cbor_test_env_t env = { 0 }; + + senml_cbor_test_setup(&env, NULL, 1, FLUF_OP_INF_NON_CON_NOTIFY); + + fluf_io_out_entry_t entry = { + .timestamp = NAN, + .path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(77, 77, 77, 77), + .type = FLUF_DATA_TYPE_TIME, + .value.time_value = 1000000 + }; + + AVS_UNIT_ASSERT_SUCCESS(fluf_io_out_ctx_new_entry(&env.ctx, &entry)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_out_ctx_get_payload( + &env.ctx, env.buf, env.buffer_length, &env.out_length)); + VERIFY_BYTES(env, "\x81\xA2" + "\x00\x6C\x2F\x37\x37\x2F\x37\x37\x2F\x37\x37\x2F\x37\x37" + "\x02\xC1\x1A\x00\x0F\x42\x40"); +} + +AVS_UNIT_TEST(senml_cbor_encoder, bool) { + senml_cbor_test_env_t env = { 0 }; + + senml_cbor_test_setup(&env, NULL, 1, FLUF_OP_INF_NON_CON_NOTIFY); + + fluf_io_out_entry_t entry = { + .timestamp = NAN, + .path = FLUF_MAKE_RESOURCE_PATH(7, 7, 7), + .type = FLUF_DATA_TYPE_BOOL, + .value.bool_value = true + }; + + AVS_UNIT_ASSERT_SUCCESS(fluf_io_out_ctx_new_entry(&env.ctx, &entry)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_out_ctx_get_payload( + &env.ctx, env.buf, env.buffer_length, &env.out_length)); + VERIFY_BYTES(env, "\x81\xA2" + "\x00\x66\x2F\x37\x2F\x37\x2F\x37" + "\x04\xF5"); +} + +AVS_UNIT_TEST(senml_cbor_encoder, float) { + senml_cbor_test_env_t env = { 0 }; + + senml_cbor_test_setup(&env, NULL, 1, FLUF_OP_INF_NON_CON_NOTIFY); + + fluf_io_out_entry_t entry = { + .timestamp = NAN, + .path = FLUF_MAKE_RESOURCE_PATH(7, 7, 7), + .type = FLUF_DATA_TYPE_DOUBLE, + .value.double_value = 100000.0 + }; + + AVS_UNIT_ASSERT_SUCCESS(fluf_io_out_ctx_new_entry(&env.ctx, &entry)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_out_ctx_get_payload( + &env.ctx, env.buf, env.buffer_length, &env.out_length)); + VERIFY_BYTES(env, "\x81\xA2" + "\x00\x66\x2F\x37\x2F\x37\x2F\x37" + "\x02\xFA\x47\xC3\x50\x00"); +} + +AVS_UNIT_TEST(senml_cbor_encoder, double) { + senml_cbor_test_env_t env = { 0 }; + + senml_cbor_test_setup(&env, NULL, 1, FLUF_OP_INF_NON_CON_NOTIFY); + + fluf_io_out_entry_t entry = { + .timestamp = NAN, + .path = FLUF_MAKE_RESOURCE_PATH(7, 7, 7), + .type = FLUF_DATA_TYPE_DOUBLE, + .value.double_value = -4.1 + }; + + AVS_UNIT_ASSERT_SUCCESS(fluf_io_out_ctx_new_entry(&env.ctx, &entry)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_out_ctx_get_payload( + &env.ctx, env.buf, env.buffer_length, &env.out_length)); + VERIFY_BYTES(env, "\x81\xA2" + "\x00\x66\x2F\x37\x2F\x37\x2F\x37" + "\x02\xFB\xC0\x10\x66\x66\x66\x66\x66\x66"); +} + +AVS_UNIT_TEST(senml_cbor_encoder, string) { + senml_cbor_test_env_t env = { 0 }; + + senml_cbor_test_setup(&env, NULL, 1, FLUF_OP_INF_NON_CON_NOTIFY); + + fluf_io_out_entry_t entry = { + .timestamp = NAN, + .path = FLUF_MAKE_RESOURCE_PATH(7, 7, 7), + .type = FLUF_DATA_TYPE_STRING, + .value.bytes_or_string.data = "DDDDDDDDDD" + }; + + AVS_UNIT_ASSERT_SUCCESS(fluf_io_out_ctx_new_entry(&env.ctx, &entry)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_out_ctx_get_payload( + &env.ctx, env.buf, env.buffer_length, &env.out_length)); + VERIFY_BYTES(env, "\x81\xA2" + "\x00\x66\x2F\x37\x2F\x37\x2F\x37" + "\x03\x6A" + "DDDDDDDDDD"); +} + +AVS_UNIT_TEST(senml_cbor_encoder, bytes) { + senml_cbor_test_env_t env = { 0 }; + + senml_cbor_test_setup(&env, NULL, 1, FLUF_OP_INF_NON_CON_NOTIFY); + + fluf_io_out_entry_t entry = { + .timestamp = NAN, + .path = FLUF_MAKE_RESOURCE_PATH(7, 7, 7), + .type = FLUF_DATA_TYPE_BYTES, + .value.bytes_or_string.data = "DDDDDDDDDD", + .value.bytes_or_string.chunk_length = 10 + }; + + AVS_UNIT_ASSERT_SUCCESS(fluf_io_out_ctx_new_entry(&env.ctx, &entry)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_out_ctx_get_payload( + &env.ctx, env.buf, env.buffer_length, &env.out_length)); + VERIFY_BYTES(env, "\x81\xA2" + "\x00\x66\x2F\x37\x2F\x37\x2F\x37" + "\x08\x4A" + "DDDDDDDDDD"); +} + +static char *ptr_for_callback = NULL; +static int external_data_handler(void *buffer, + size_t bytes_to_copy, + size_t offset, + void *args) { + (void) args; + memcpy(buffer, &ptr_for_callback[offset], bytes_to_copy); + return 0; +} + +AVS_UNIT_TEST(senml_cbor_encoder, ext_string) { + senml_cbor_test_env_t env = { 0 }; + + senml_cbor_test_setup(&env, NULL, 1, FLUF_OP_INF_SEND); + + fluf_io_out_entry_t entry = { + .timestamp = NAN, + .path = FLUF_MAKE_RESOURCE_PATH(7, 7, 7), + .type = FLUF_DATA_TYPE_EXTERNAL_STRING, + .value.external_data.length = 10, + .value.external_data.user_args = NULL, + .value.external_data.get_external_data = external_data_handler + }; + ptr_for_callback = "DDDDDDDDDD"; + + AVS_UNIT_ASSERT_SUCCESS(fluf_io_out_ctx_new_entry(&env.ctx, &entry)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_out_ctx_get_payload( + &env.ctx, env.buf, env.buffer_length, &env.out_length)); + VERIFY_BYTES(env, "\x81\xA2" + "\x00\x66\x2F\x37\x2F\x37\x2F\x37" + "\x03\x6A" + "DDDDDDDDDD"); +} + +AVS_UNIT_TEST(senml_cbor_encoder, ext_bytes) { + senml_cbor_test_env_t env = { 0 }; + + senml_cbor_test_setup(&env, NULL, 1, FLUF_OP_INF_SEND); + + fluf_io_out_entry_t entry = { + .timestamp = NAN, + .path = FLUF_MAKE_RESOURCE_PATH(7, 7, 7), + .type = FLUF_DATA_TYPE_EXTERNAL_BYTES, + .value.external_data.length = 10, + .value.external_data.user_args = NULL, + .value.external_data.get_external_data = external_data_handler + }; + ptr_for_callback = "DDDDDDDDDD"; + + AVS_UNIT_ASSERT_SUCCESS(fluf_io_out_ctx_new_entry(&env.ctx, &entry)); + AVS_UNIT_ASSERT_SUCCESS(fluf_io_out_ctx_get_payload( + &env.ctx, env.buf, env.buffer_length, &env.out_length)); + VERIFY_BYTES(env, "\x81\xA2" + "\x00\x66\x2F\x37\x2F\x37\x2F\x37" + "\x08\x4A" + "DDDDDDDDDD"); +} + +AVS_UNIT_TEST(senml_cbor_encoder, complex_notify_msg) { + senml_cbor_test_env_t env = { 0 }; + fluf_io_out_entry_t entries[] = { + { + .timestamp = 65504.0, + .path = FLUF_MAKE_RESOURCE_PATH(8, 8, 0), + .type = FLUF_DATA_TYPE_INT, + .value.int_value = 25, + }, + { + .timestamp = 65504.0, + .path = FLUF_MAKE_RESOURCE_PATH(8, 8, 1), + .type = FLUF_DATA_TYPE_UINT, + .value.uint_value = 100, + }, + { + .timestamp = 65504.0, + .path = FLUF_MAKE_RESOURCE_PATH(8, 8, 2), + .type = FLUF_DATA_TYPE_STRING, + .value.bytes_or_string.data = + "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" + "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD", + }, + { + .timestamp = 65504.0, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 1), + .type = FLUF_DATA_TYPE_BYTES, + .value.bytes_or_string.data = + "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" + "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" + "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" + "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD", + .value.bytes_or_string.chunk_length = 200 + }, + { + .timestamp = 1.5, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 25), + .type = FLUF_DATA_TYPE_BOOL, + .value.bool_value = false + }, + { + .timestamp = 1.5, + .path = FLUF_MAKE_RESOURCE_PATH(1, 1, 26), + .type = FLUF_DATA_TYPE_OBJLNK, + .value.objlnk.oid = 17, + .value.objlnk.iid = 19, + } + }; + + senml_cbor_test_setup(&env, NULL, AVS_ARRAY_SIZE(entries), + FLUF_OP_INF_NON_CON_NOTIFY); + + for (size_t i = 0; i < AVS_ARRAY_SIZE(entries); i++) { + size_t record_len = 0; + + AVS_UNIT_ASSERT_SUCCESS( + fluf_io_out_ctx_new_entry(&env.ctx, &entries[i])); + int res = -1; + while (res) { + size_t temp_len = 0; + res = fluf_io_out_ctx_get_payload( + &env.ctx, &env.buf[env.out_length + record_len], 50, + &temp_len); + AVS_UNIT_ASSERT_TRUE(res == 0 || res == FLUF_IO_NEED_NEXT_CALL); + record_len += temp_len; + } + env.out_length += record_len; + } + + VERIFY_BYTES(env, "\x86\xA3" + "\x00\x66\x2F\x38\x2F\x38\x2F\x30" // 8/8/0 + "\x22\xFA\x47\x7F\xE0\x00" // base time + "\x02\x18\x19" + "\xA2" // 8/8/1 + "\x00\x66\x2F\x38\x2F\x38\x2F\x31" + "\x02\x18\x64" + "\xA2" // 8/8/2 + "\x00\x66\x2F\x38\x2F\x38\x2F\x32" + "\x03\x78\x64" + "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" + "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" + "\xA2" // 1/1/1 + "\x00\x66\x2F\x31\x2F\x31\x2F\x31" + "\x08\x58\xC8" + "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" + "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" + "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" + "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" + "\xA3" // 1/1/25 + "\x00\x67\x2F\x31\x2F\x31\x2F\x32\x35" + "\x22\xFA\x3F\xC0\x00\x00" + "\x04\xF4" + "\xA2" // 1/1/26 + "\x00\x67\x2F\x31\x2F\x31\x2F\x32\x36" + "\x63" + "vlo" + "\x65\x31\x37\x3A\x31\x39"); +} + +AVS_UNIT_TEST(senml_cbor_encoder, complex_read_msg) { + senml_cbor_test_env_t env = { 0 }; + fluf_uri_path_t base_path = FLUF_MAKE_INSTANCE_PATH(8, 8); + fluf_io_out_entry_t entries[] = { + { + .path = FLUF_MAKE_RESOURCE_PATH(8, 8, 0), + .type = FLUF_DATA_TYPE_INT, + .value.int_value = 25, + }, + { + .path = FLUF_MAKE_RESOURCE_PATH(8, 8, 1), + .type = FLUF_DATA_TYPE_UINT, + .value.uint_value = 100, + }, + { + .path = FLUF_MAKE_RESOURCE_PATH(8, 8, 2), + .type = FLUF_DATA_TYPE_STRING, + .value.bytes_or_string.data = + "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" + "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD", + }, + { + .path = FLUF_MAKE_RESOURCE_PATH(8, 8, 3), + .type = FLUF_DATA_TYPE_BYTES, + .value.bytes_or_string.data = + "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" + "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" + "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" + "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD", + .value.bytes_or_string.chunk_length = 200 + }, + { + .path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(8, 8, 4, 0), + .type = FLUF_DATA_TYPE_BOOL, + .value.bool_value = false + }, + { + .path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(8, 8, 4, 1), + .type = FLUF_DATA_TYPE_OBJLNK, + .value.objlnk.oid = 17, + .value.objlnk.iid = 19, + } + }; + + senml_cbor_test_setup(&env, &base_path, AVS_ARRAY_SIZE(entries), + FLUF_OP_DM_READ); + + for (size_t i = 0; i < AVS_ARRAY_SIZE(entries); i++) { + size_t record_len = 0; + AVS_UNIT_ASSERT_SUCCESS( + fluf_io_out_ctx_new_entry(&env.ctx, &entries[i])); + int res = -1; + while (res) { + size_t temp_len = 0; + res = fluf_io_out_ctx_get_payload( + &env.ctx, &env.buf[env.out_length + record_len], 70, + &temp_len); + AVS_UNIT_ASSERT_TRUE(res == 0 || res == FLUF_IO_NEED_NEXT_CALL); + record_len += temp_len; + } + env.out_length += record_len; + } + + VERIFY_BYTES(env, "\x86\xA3" + "\x21\x64\x2F\x38\x2F\x38" + "\x00\x62\x2F\x30" // 8/8/0 + "\x02\x18\x19" + "\xA2" // 8/8/1 + "\x00\x62\x2F\x31" + "\x02\x18\x64" + "\xA2" // 8/8/2 + "\x00\x62\x2F\x32" + "\x03\x78\x64" + "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" + "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" + "\xA2" // 8/8/3 + "\x00\x62\x2F\x33" + "\x08\x58\xC8" + "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" + "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" + "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" + "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" + "\xA2" // 8/8/4/0 + "\x00\x64\x2F\x34\x2F\x30" + "\x04\xF4" + "\xA2" // 8/8/4/1 + "\x00\x64\x2F\x34\x2F\x31" + "\x63" + "vlo" + "\x65\x31\x37\x3A\x31\x39"); +} + +#define DATA_HANDLER_ERROR_CODE -888 +static int external_data_handler_with_error(void *buffer, + size_t bytes_to_copy, + size_t offset, + void *args) { + (void) buffer; + (void) bytes_to_copy; + (void) offset; + (void) args; + return DATA_HANDLER_ERROR_CODE; +} + +AVS_UNIT_TEST(senml_cbor_encoder, read_error) { + senml_cbor_test_env_t env = { 0 }; + + fluf_uri_path_t base_path = FLUF_MAKE_INSTANCE_PATH(3, 3); + senml_cbor_test_setup(&env, &base_path, 1, FLUF_OP_DM_READ); + + fluf_io_out_entry_t entry_1 = { + .path = FLUF_MAKE_RESOURCE_PATH(1, 3, 3), + .type = FLUF_DATA_TYPE_UINT, + .value.uint_value = 25 + }; + + AVS_UNIT_ASSERT_FAILED(fluf_io_out_ctx_new_entry(&env.ctx, &entry_1)); + + fluf_io_out_entry_t entry_2 = { + .path = FLUF_MAKE_RESOURCE_INSTANCE_PATH(3, 1, 3, 1), + .type = FLUF_DATA_TYPE_UINT, + .value.uint_value = 25 + }; + + AVS_UNIT_ASSERT_FAILED(fluf_io_out_ctx_new_entry(&env.ctx, &entry_2)); + + fluf_io_out_entry_t entry_3 = { + .path = FLUF_MAKE_INSTANCE_PATH(3, 3), + .type = FLUF_DATA_TYPE_UINT, + .value.uint_value = 25 + }; + + AVS_UNIT_ASSERT_FAILED(fluf_io_out_ctx_new_entry(&env.ctx, &entry_3)); + + fluf_io_out_entry_t entry_4 = { + .path = FLUF_MAKE_RESOURCE_PATH(3, 3, 4), + .type = FLUF_DATA_TYPE_EXTERNAL_STRING, + .value.external_data.length = 10, + .value.external_data.user_args = NULL, + .value.external_data.get_external_data = + external_data_handler_with_error + }; + + AVS_UNIT_ASSERT_SUCCESS(fluf_io_out_ctx_new_entry(&env.ctx, &entry_4)); + AVS_UNIT_ASSERT_EQUAL(fluf_io_out_ctx_get_payload(&env.ctx, env.buf, + env.buffer_length, + &env.out_length), + DATA_HANDLER_ERROR_CODE); +} diff --git a/tests/fluf/tlv_in.c b/tests/fluf/tlv_in.c new file mode 100644 index 00000000..ffdee129 --- /dev/null +++ b/tests/fluf/tlv_in.c @@ -0,0 +1,883 @@ +/* + * Copyright 2023-2024 AVSystem + * AVSystem Anjay LwM2M SDK + * All rights reserved. + * + * Licensed under the AVSystem-5-clause License. + * See the attached LICENSE file for details. + */ + +#include +#define AVS_UNIT_ENABLE_SHORT_ASSERTS +#include + +#include +#include +#include + +#include "bigdata.h" + +#define TEST_ENV(Data, Path, PayloadFinished) \ + fluf_io_in_ctx_t ctx; \ + ASSERT_OK(fluf_io_in_ctx_init(&ctx, FLUF_OP_DM_WRITE_PARTIAL_UPDATE, \ + &(Path), FLUF_COAP_FORMAT_OMA_LWM2M_TLV)); \ + const fluf_res_value_t *value = NULL; \ + const fluf_uri_path_t *path = NULL; \ + ASSERT_OK(fluf_io_in_ctx_feed_payload(&ctx, Data, sizeof(Data) - 1, \ + PayloadFinished)) + +static const fluf_uri_path_t TEST_INSTANCE_PATH = + _FLUF_URI_PATH_INITIALIZER(3, 4, FLUF_ID_INVALID, FLUF_ID_INVALID, 2); + +#define MAKE_TEST_RESOURCE_PATH(Rid) \ + (FLUF_MAKE_RESOURCE_PATH(TEST_INSTANCE_PATH.ids[FLUF_ID_OID], \ + TEST_INSTANCE_PATH.ids[FLUF_ID_IID], (Rid))) + +#define TLV_BYTES_TEST(Name, Path, Header, Data) \ + AVS_UNIT_TEST(tlv_in_bytes, Name) { \ + TEST_ENV(Header Data, TEST_INSTANCE_PATH, true); \ + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_BYTES; \ + ASSERT_OK( \ + fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); \ + ASSERT_TRUE(fluf_uri_path_equal(path, &(Path))); \ + ASSERT_EQ(value->bytes_or_string.chunk_length, sizeof(Data) - 1); \ + ASSERT_EQ_BYTES(value->bytes_or_string.data, Data); \ + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, \ + &path), \ + FLUF_IO_EOF); \ + AVS_UNIT_ASSERT_NULL(value); \ + AVS_UNIT_ASSERT_NULL(path); \ + } + +// 3 bits for length - <=7 +TLV_BYTES_TEST(len3b_id8b, MAKE_TEST_RESOURCE_PATH(0), "\xC7\x00", "1234567") +TLV_BYTES_TEST(len3b_id16b, + MAKE_TEST_RESOURCE_PATH(42000), + "\xE7\xA4\x10", + "1234567") +TLV_BYTES_TEST(len8b_id8b, + MAKE_TEST_RESOURCE_PATH(255), + "\xC8\xFF\x08", + "12345678") +TLV_BYTES_TEST(len8b_id16b, + MAKE_TEST_RESOURCE_PATH(65534), + "\xE8\xFF\xFE\x08", + "12345678") + +TLV_BYTES_TEST(len16b_id8b, + MAKE_TEST_RESOURCE_PATH(42), + "\xD0\x2A\x03\xE8", + DATA1kB) +TLV_BYTES_TEST(len16b_id16b, + MAKE_TEST_RESOURCE_PATH(42420), + "\xF0\xA5\xB4\x03\xE8", + DATA1kB) + +TLV_BYTES_TEST(len24b_id8b, + MAKE_TEST_RESOURCE_PATH(69), + "\xD8\x45\x01\x86\xA0", + DATA100kB) +TLV_BYTES_TEST(len24b_id16b, + MAKE_TEST_RESOURCE_PATH(258), + "\xF8\x01\x02\x01\x86\xA0", + DATA100kB) + +#undef TLV_BYTES_TEST + +AVS_UNIT_TEST(tlv_in_bytes, id_too_short) { + TEST_ENV("\xE7", FLUF_MAKE_OBJECT_PATH(3), false); + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_BYTES; + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_WANT_NEXT_PAYLOAD); +} + +AVS_UNIT_TEST(tlv_in_bytes, id_too_short_with_payload_finished) { + TEST_ENV("\xE7", FLUF_MAKE_OBJECT_PATH(3), true); + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_BYTES; + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_ERR_FORMAT); +} + +AVS_UNIT_TEST(tlv_in_bytes, length_too_short) { + TEST_ENV("\xF8\x01\x02\x01\x86", FLUF_MAKE_OBJECT_PATH(3), false); + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_BYTES; + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_WANT_NEXT_PAYLOAD); +} + +AVS_UNIT_TEST(tlv_in_bytes, length_too_short_with_payload_finished) { + TEST_ENV("\xF8\x01\x02\x01\x86", FLUF_MAKE_OBJECT_PATH(3), true); + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_BYTES; + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_ERR_FORMAT); +} + +AVS_UNIT_TEST(tlv_in_bytes, multiple_resource_entries) { + // [ RID(42)="0123", RID(69)="0123456", RID(22)="01234" ] + char DATA[] = "\xC4\x2A" + "0123" + "\xC7\x45" + "0123456" + "\xC5\x16" + "01234"; + TEST_ENV(DATA, TEST_INSTANCE_PATH, true); + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_BYTES; + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_TRUE(fluf_uri_path_equal(path, &FLUF_MAKE_RESOURCE_PATH(3, 4, 42))); + ASSERT_EQ(value->bytes_or_string.chunk_length, 4); + ASSERT_EQ_BYTES(value->bytes_or_string.data, "0123"); + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_TRUE(fluf_uri_path_equal(path, &FLUF_MAKE_RESOURCE_PATH(3, 4, 69))); + ASSERT_EQ(value->bytes_or_string.chunk_length, 7); + ASSERT_EQ_BYTES(value->bytes_or_string.data, "0123456"); + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_TRUE(fluf_uri_path_equal(path, &FLUF_MAKE_RESOURCE_PATH(3, 4, 22))); + ASSERT_EQ(value->bytes_or_string.chunk_length, 5); + ASSERT_EQ_BYTES(value->bytes_or_string.data, "01234"); + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_EOF); +} + +AVS_UNIT_TEST(tlv_in_bytes, premature_end) { + static char DATA[] = "\xC7\x2A" + "012"; + TEST_ENV(DATA, TEST_INSTANCE_PATH, false); + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_BYTES; + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); +} + +AVS_UNIT_TEST(tlv_in_bytes, premature_end_with_payload_finished) { + static char DATA[] = "\xC7\x2A" + "012"; + TEST_ENV(DATA, TEST_INSTANCE_PATH, true); + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_BYTES; + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_ERR_FORMAT); +} + +AVS_UNIT_TEST(tlv_in_bytes, premature_end_with_feeding) { + static char DATA[] = "\xC7\x2A" + "012"; + TEST_ENV(DATA, TEST_INSTANCE_PATH, false); + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_BYTES; + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_EQ(value->bytes_or_string.chunk_length, 3); + ASSERT_EQ_BYTES(value->bytes_or_string.data, "012"); + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_WANT_NEXT_PAYLOAD); + ASSERT_OK(fluf_io_in_ctx_feed_payload(&ctx, "3456", 4, true)); + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_EQ(value->bytes_or_string.chunk_length, 4); + ASSERT_EQ_BYTES(value->bytes_or_string.data, "3456"); + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_EOF); +} + +AVS_UNIT_TEST(tlv_in_bytes, no_data) { + TEST_ENV("", FLUF_MAKE_OBJECT_PATH(3), false); + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_BYTES; + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_WANT_NEXT_PAYLOAD); + AVS_UNIT_ASSERT_NULL(value); + AVS_UNIT_ASSERT_NULL(path); +} + +AVS_UNIT_TEST(tlv_in_bytes, no_data_with_payload_finished) { + TEST_ENV("", FLUF_MAKE_OBJECT_PATH(3), true); + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_BYTES; + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_EOF); + AVS_UNIT_ASSERT_NULL(value); + AVS_UNIT_ASSERT_NULL(path); +} + +AVS_UNIT_TEST(tlv_in_types, string_ok) { + // RID(01)="Hello, world!" + static char DATA[] = "\xC8\x01\x0D" + "Hello, world!"; + TEST_ENV(DATA, FLUF_MAKE_OBJECT_PATH(3), true); + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_STRING; + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_EQ(value->bytes_or_string.chunk_length, 13); + ASSERT_EQ_BYTES(value->bytes_or_string.data, "Hello, world!"); +} + +#define TEST_NUM_IMPL(Name, Type, Suffix, TypeBitmask, Num, Data) \ + AVS_UNIT_TEST(tlv_in_types, Name) { \ + TEST_ENV(Data, TEST_INSTANCE_PATH, true); \ + fluf_data_type_t type_bitmask = TypeBitmask; \ + ASSERT_OK( \ + fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); \ + ASSERT_EQ(value->Suffix, (Type) Num); \ + } + +#define TEST_NUM_FAIL_IMPL(Name, Type, Suffix, TypeBitmask, Data) \ + AVS_UNIT_TEST(tlv_in_types, Name) { \ + TEST_ENV(Data, TEST_INSTANCE_PATH, true); \ + fluf_data_type_t type_bitmask = TypeBitmask; \ + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, \ + &path), \ + FLUF_IO_ERR_FORMAT); \ + } + +#define TEST_NUM(Type, Suffix, TypeBitmask, Num, Data) \ + TEST_NUM_IMPL(AVS_CONCAT(Suffix##_, __LINE__), Type, Suffix, TypeBitmask, \ + Num, Data) +#define TEST_NUM_FAIL(Type, Suffix, TypeBitmask, Data) \ + TEST_NUM_FAIL_IMPL(AVS_CONCAT(Suffix##fail_, __LINE__), Type, Suffix, \ + TypeBitmask, Data) + +#define TEST_INT64(Num, Data) \ + TEST_NUM(int64_t, int_value, FLUF_DATA_TYPE_INT, Num##LL, Data) + +#define TEST_INT64_FAIL(Data) \ + TEST_NUM_FAIL(int64_t, int_value, FLUF_DATA_TYPE_INT, Data) +TEST_INT64_FAIL("\xC0\x01" + "") +TEST_INT64(42, + "\xC1\x01" + "\x2A") +TEST_INT64(4242, + "\xC2\x01" + "\x10\x92") +TEST_INT64_FAIL("\xC3\x01" + "\x06\x79\x32") +TEST_INT64(424242, + "\xC4\x01" + "\x00\x06\x79\x32") +TEST_INT64(42424242, + "\xC4\x01" + "\x02\x87\x57\xB2") +TEST_INT64((int32_t) 4242424242, + "\xC4\01" + "\xFC\xDE\x41\xB2") +TEST_INT64(4242424242, + "\xC8\x01\x08" + "\x00\x00\x00\x00\xFC\xDE\x41\xB2") +TEST_INT64_FAIL("\xC5\x01" + "\x62\xC6\xD1\xA9\xB2") +TEST_INT64(424242424242, + "\xC8\x01\x08" + "\x00\x00\x00\x62\xC6\xD1\xA9\xB2") +TEST_INT64_FAIL("\xC6\x01" + "\x26\x95\xA9\xE6\x49\xB2") +TEST_INT64(42424242424242, + "\xC8\x01\x08" + "\x00\x00\x26\x95\xA9\xE6\x49\xB2") +TEST_INT64_FAIL("\xC8\x01\x07" + "\x0F\x12\x76\x5D\xF4\xC9\xB2") +TEST_INT64(4242424242424242, + "\xC8\x01\x08" + "\x00\x0F\x12\x76\x5D\xF4\xC9\xB2") +TEST_INT64(424242424242424242, + "\xC8\x01\x08" + "\x05\xE3\x36\x3C\xB3\x9E\xC9\xB2") +TEST_INT64_FAIL("\xC8\x01\x09" + "\x00\x05\xE3\x36\x3C\xB3\x9E\xC9\xB2") + +TEST_INT64_FAIL( + "\xC8\x01\x10" + "\x00\x00\x00\x00\x00\x00\x00\x00\x05\xE3\x36\x3C\xB3\x9E\xC9\xB2") + +AVS_UNIT_TEST(tlv_in_types, int64_two_feeds) { + static char DATA[] = "\xC8\x01\x08" + "\x05\xE3\x36"; + TEST_ENV(DATA, TEST_INSTANCE_PATH, false); + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_INT; + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_WANT_NEXT_PAYLOAD); + AVS_UNIT_ASSERT_NULL(value); + ASSERT_EQ_BYTES_SIZED(path, &MAKE_TEST_RESOURCE_PATH(1), + sizeof(FLUF_MAKE_ROOT_PATH())); + ASSERT_OK( + fluf_io_in_ctx_feed_payload(&ctx, "\x3C\xB3\x9E\xC9\xB2", 5, true)); + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_EQ(value->int_value, 424242424242424242); +} + +#define TEST_UINT64(Num, Data) \ + TEST_NUM(uint64_t, uint_value, FLUF_DATA_TYPE_UINT, Num##ULL, Data) +#define TEST_UINT64_FAIL(Data) \ + TEST_NUM_FAIL(uint64_t, uint_value, FLUF_DATA_TYPE_UINT, Data) + +TEST_UINT64_FAIL("\xC0\x01" + "") +TEST_UINT64(42, + "\xC1\x01" + "\x2A") +TEST_UINT64_FAIL("\xC3\x01" + "\x06\x79\x32") +TEST_UINT64(4294967295, + "\xC4\x01" + "\xFF\xFF\xFF\xFF") +TEST_UINT64_FAIL("\xC5\x01" + "\x01\x00\x00\x00\x00") +TEST_UINT64(4294967296, + "\xC8\x01\x08" + "\x00\x00\x00\x01\x00\x00\x00\x00") +TEST_UINT64(18446744073709551615, + "\xC8\x01\x08" + "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF") + +TEST_UINT64_FAIL( + "\xC8\x01\x10" + "\x00\x00\x00\x00\x00\x00\x00\x00\x05\xE3\x36\x3C\xB3\x9E\xC9\xB2") + +AVS_UNIT_TEST(tlv_in_types, uint64_two_feeds) { + static char DATA[] = "\xC8\x01\x08" + "\x05\xE3\x36"; + TEST_ENV(DATA, TEST_INSTANCE_PATH, false); + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_UINT; + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_WANT_NEXT_PAYLOAD); + AVS_UNIT_ASSERT_NULL(value); + ASSERT_EQ_BYTES_SIZED(path, &MAKE_TEST_RESOURCE_PATH(1), + sizeof(FLUF_MAKE_ROOT_PATH())); + ASSERT_OK( + fluf_io_in_ctx_feed_payload(&ctx, "\x3C\xB3\x9E\xC9\xB2", 5, true)); + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_EQ(value->uint_value, 424242424242424242); +} + +#define TEST_DOUBLE(Num, Data) \ + TEST_NUM(double, double_value, FLUF_DATA_TYPE_DOUBLE, Num, Data) + +#define TEST_DOUBLE_FAIL(Data) \ + TEST_NUM_FAIL(double, double, FLUF_DATA_TYPE_DOUBLE, Data) + +TEST_DOUBLE_FAIL("\xC0\x01" + "") +TEST_DOUBLE_FAIL("\xC1\x01" + "\x3F") +TEST_DOUBLE_FAIL("\xC2\x01" + "\x3F\x80") +TEST_DOUBLE_FAIL("\xC3\x01" + "\x3F\x80\x00") +TEST_DOUBLE(1.0, + "\xC4\x01" + "\x3F\x80\x00\x00") +TEST_DOUBLE(-42.0e3, + "\xC4\x01" + "\xC7\x24\x10\x00") +TEST_DOUBLE_FAIL("\xC5\x01" + "\x3F\xF0\x00\x00\x00") +TEST_DOUBLE_FAIL("\xC6\x01" + "\x3F\xF0\x00\x00\x00\x00") +TEST_DOUBLE_FAIL("\xC7\x01" + "\x3F\xF0\x00\x00\x00\x00\x00") +TEST_DOUBLE(1.0, + "\xC8\x01\x08" + "\x3F\xF0\x00\x00\x00\x00\x00\x00") +TEST_DOUBLE(1.1, + "\xC8\x01\x08" + "\x3F\xF1\x99\x99\x99\x99\x99\x9A") +TEST_DOUBLE(-42.0e3, + "\xC8\x01\x08" + "\xC0\xE4\x82\x00\x00\x00\x00\x00") +TEST_DOUBLE_FAIL("\xC8\x01\x09" + "\xC0\xE4\x82\x00\x00\x00\x00\x00\x00") + +AVS_UNIT_TEST(tlv_in_types, double_two_feeds) { + static char DATA[] = "\xC8\x01\x08" + "\x3F\xF1\x99"; + TEST_ENV(DATA, TEST_INSTANCE_PATH, false); + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_DOUBLE; + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_WANT_NEXT_PAYLOAD); + AVS_UNIT_ASSERT_NULL(value); + ASSERT_EQ_BYTES_SIZED(path, &MAKE_TEST_RESOURCE_PATH(1), + sizeof(FLUF_MAKE_ROOT_PATH())); + ASSERT_OK( + fluf_io_in_ctx_feed_payload(&ctx, "\x99\x99\x99\x99\x9A", 5, true)); + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_EQ(value->double_value, 1.1); +} + +#define TEST_BOOL_IMPL(Name, Value, Data, TypeBitmask) \ + AVS_UNIT_TEST(tlv_in_types, Name) { \ + TEST_ENV(Data, TEST_INSTANCE_PATH, true); \ + fluf_data_type_t type_bitmask = TypeBitmask; \ + ASSERT_OK( \ + fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); \ + ASSERT_EQ(!!(Value), value->bool_value); \ + } + +#define TEST_BOOL_FAIL_IMPL(Name, Data, TypeBitmask) \ + AVS_UNIT_TEST(tlv_in_types, Name) { \ + TEST_ENV(Data, TEST_INSTANCE_PATH, true); \ + fluf_data_type_t type_bitmask = TypeBitmask; \ + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, \ + &path), \ + FLUF_IO_ERR_FORMAT); \ + } + +#define TEST_BOOL(Value, Data) \ + TEST_BOOL_IMPL(AVS_CONCAT(bool_, __LINE__), Value, Data, \ + FLUF_DATA_TYPE_BOOL) +#define TEST_BOOL_FAIL(Data) \ + TEST_BOOL_FAIL_IMPL(AVS_CONCAT(bool_, __LINE__), Data, FLUF_DATA_TYPE_BOOL) + +TEST_BOOL_FAIL("\xC0\x01" + "") +TEST_BOOL(false, + "\xC1\x01" + "\0") +TEST_BOOL(true, + "\xC1\x01" + "\1") +TEST_BOOL_FAIL("\xC1\x01" + "\2") +TEST_BOOL_FAIL("\xC2\x01" + "\0\0") + +#define TEST_OBJLNK_IMPL(Name, TypeBitmask, Oid, Iid, Data) \ + AVS_UNIT_TEST(tlv_in_types, Name) { \ + TEST_ENV(Data, TEST_INSTANCE_PATH, true); \ + fluf_data_type_t type_bitmask = TypeBitmask; \ + ASSERT_OK( \ + fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); \ + ASSERT_EQ(value->objlnk.oid, Oid); \ + ASSERT_EQ(value->objlnk.iid, Iid); \ + } + +#define TEST_OBJLNK_FAIL_IMPL(Name, TypeBitmask, Data) \ + AVS_UNIT_TEST(tlv_in_types, Name) { \ + TEST_ENV(Data, TEST_INSTANCE_PATH, true); \ + fluf_data_type_t type_bitmask = TypeBitmask; \ + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, \ + &path), \ + FLUF_IO_ERR_FORMAT); \ + } + +#define TEST_OBJLNK(...) \ + TEST_OBJLNK_IMPL(AVS_CONCAT(objlnk_, __LINE__), FLUF_DATA_TYPE_OBJLNK, \ + __VA_ARGS__) +#define TEST_OBJLNK_FAIL(...) \ + TEST_OBJLNK_FAIL_IMPL(AVS_CONCAT(objlnk_, __LINE__), \ + FLUF_DATA_TYPE_OBJLNK, __VA_ARGS__) + +TEST_OBJLNK_FAIL("\xC0\x01" + "") +TEST_OBJLNK_FAIL("\xC1\x01" + "\x00") +TEST_OBJLNK_FAIL("\xC2\x01" + "\x00\x00") +TEST_OBJLNK_FAIL("\xC3\x01" + "\x00\x00\x00") +TEST_OBJLNK(0, + 0, + "\xC4\x01" + "\x00\x00\x00\x00") +TEST_OBJLNK(1, + 0, + "\xC4\x01" + "\x00\x01\x00\x00") +TEST_OBJLNK(0, + 1, + "\xC4\x01" + "\x00\x00\x00\x01") +TEST_OBJLNK(1, + 65535, + "\xC4\x01" + "\x00\x01\xFF\xFF") +TEST_OBJLNK(65535, + 1, + "\xC4\x01" + "\xFF\xFF\x00\x01") +TEST_OBJLNK(65535, + 65535, + "\xC4\x01" + "\xFF\xFF\xFF\xFF") +TEST_OBJLNK_FAIL("\xC5\x01" + "\xFF\xFF\xFF\xFF\xFF") + +AVS_UNIT_TEST(tlv_in_types, objlnk_two_feeds) { + static char DATA[] = "\xC4\x01" + "\x00"; + TEST_ENV(DATA, TEST_INSTANCE_PATH, false); + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_OBJLNK; + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_WANT_NEXT_PAYLOAD); + AVS_UNIT_ASSERT_NULL(value); + ASSERT_EQ_BYTES_SIZED(path, &MAKE_TEST_RESOURCE_PATH(1), + sizeof(FLUF_MAKE_ROOT_PATH())); + ASSERT_OK(fluf_io_in_ctx_feed_payload(&ctx, "\x01\xFF\xFF", 3, true)); + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_EQ(value->objlnk.oid, 1); + ASSERT_EQ(value->objlnk.iid, 65535); +} + +AVS_UNIT_TEST(tlv_in_types, time_ok) { + static char DATA[] = "\xC8\x01\x08" + "\x00\x00\x00\x00\x42\x4E\xF4\x5C"; + TEST_ENV(DATA, TEST_INSTANCE_PATH, true); + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_TIME; + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_EQ(value->time_value, 1112470620); +} + +AVS_UNIT_TEST(tlv_in_types, no_value) { + static char DATA[] = "\xC0\x01"; + TEST_ENV(DATA, TEST_INSTANCE_PATH, false); + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_ANY; + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_WANT_TYPE_DISAMBIGUATION); + ASSERT_EQ(type_bitmask, FLUF_DATA_TYPE_ANY); + type_bitmask = FLUF_DATA_TYPE_BYTES; + ASSERT_TRUE(fluf_uri_path_equal(path, &FLUF_MAKE_RESOURCE_PATH(3, 4, 1))); + ASSERT_NULL(value); + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_EQ(type_bitmask, FLUF_DATA_TYPE_BYTES); + ASSERT_TRUE(fluf_uri_path_equal(path, &FLUF_MAKE_RESOURCE_PATH(3, 4, 1))); + ASSERT_NULL(value->bytes_or_string.data); + ASSERT_EQ(value->bytes_or_string.offset, 0); + ASSERT_EQ(value->bytes_or_string.chunk_length, 0); + ASSERT_EQ(value->bytes_or_string.full_length_hint, 0); + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_WANT_NEXT_PAYLOAD); +} + +AVS_UNIT_TEST(tlv_in_types, no_value_with_payload_finished) { + static char DATA[] = "\xC0\x01"; + TEST_ENV(DATA, TEST_INSTANCE_PATH, true); + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_ANY; + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_WANT_TYPE_DISAMBIGUATION); + ASSERT_EQ(type_bitmask, FLUF_DATA_TYPE_ANY); + type_bitmask = FLUF_DATA_TYPE_BYTES; + ASSERT_TRUE(fluf_uri_path_equal(path, &FLUF_MAKE_RESOURCE_PATH(3, 4, 1))); + ASSERT_NULL(value); + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_EQ(type_bitmask, FLUF_DATA_TYPE_BYTES); + ASSERT_TRUE(fluf_uri_path_equal(path, &FLUF_MAKE_RESOURCE_PATH(3, 4, 1))); + ASSERT_NULL(value->bytes_or_string.data); + ASSERT_EQ(value->bytes_or_string.offset, 0); + ASSERT_EQ(value->bytes_or_string.chunk_length, 0); + ASSERT_EQ(value->bytes_or_string.full_length_hint, 0); + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_EOF); +} + +AVS_UNIT_TEST(tlv_in_path, typical_payload_for_create_without_iid) { + static char DATA[] = "\xC7\x00" + "1234567"; + TEST_ENV(DATA, FLUF_MAKE_OBJECT_PATH(42), true); + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_STRING; + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + + ASSERT_TRUE(fluf_uri_path_equal( + path, &FLUF_MAKE_RESOURCE_PATH(42, FLUF_ID_INVALID, 0))); + ASSERT_EQ(value->bytes_or_string.chunk_length, 7); + ASSERT_EQ_BYTES(value->bytes_or_string.data, "1234567"); + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_EOF); +} + +AVS_UNIT_TEST(tlv_in_path, payload_write_on_instance_with_rids_only) { + // [ RID(1)=10, RID(2)=10, RID(3)=10 ] + static char DATA[] = "\xc1\x01\x0a\xc1\x02\x0b\xc1\x03\x0c"; + TEST_ENV(DATA, FLUF_MAKE_INSTANCE_PATH(3, 4), true); + + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_INT; + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_TRUE(fluf_uri_path_equal(path, &FLUF_MAKE_RESOURCE_PATH(3, 4, 1))); + ASSERT_EQ(value->int_value, 10); + + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_TRUE(fluf_uri_path_equal(path, &FLUF_MAKE_RESOURCE_PATH(3, 4, 2))); + ASSERT_EQ(value->int_value, 11); + + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_TRUE(fluf_uri_path_equal(path, &FLUF_MAKE_RESOURCE_PATH(3, 4, 3))); + ASSERT_EQ(value->int_value, 12); + + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_EOF); +} + +AVS_UNIT_TEST(tlv_in_path, + payload_write_on_instance_with_rids_uri_iid_mismatch) { + // IID(5, [ RID(1)=10 ]) + static char DATA[] = "\x03\x05\xc1\x01\x0a"; + TEST_ENV(DATA, FLUF_MAKE_INSTANCE_PATH(3, 4), true); + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_INT; + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_ERR_FORMAT); +} + +AVS_UNIT_TEST(tlv_in_path, fail_on_path_with_invalid_iid) { + // IID(ANJAY_ID_INVALID, [ RID(1)=1 ]) + static char DATA[] = "\x23\xff\xff\xc1\x01\x0a"; + TEST_ENV(DATA, FLUF_MAKE_OBJECT_PATH(3), true); + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_INT; + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_ERR_FORMAT); +} + +AVS_UNIT_TEST(tlv_in_path, fail_on_path_with_invalid_rid) { + // IID(5, [ RID(ANJAY_ID_INVALID)=10 ]) + static char DATA[] = "\x04\x05\xe1\xff\xff\x0a"; + TEST_ENV(DATA, FLUF_MAKE_OBJECT_PATH(3), true); + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_INT; + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_ERR_FORMAT); +} + +AVS_UNIT_TEST(tlv_in_path, fail_on_path_with_invalid_riid) { + // RIID=ANJAY_ID_INVALID + static char DATA[] = "\x61\xff\xff\x0a"; + TEST_ENV(DATA, FLUF_MAKE_RESOURCE_PATH(5, 0, 1), true); + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_INT; + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_ERR_FORMAT); +} + +AVS_UNIT_TEST(tlv_in_path, payload_write_on_instance_with_rids) { + // IID(4, [ RID(1)=10, RID(2)=11 ]) + static char DATA[] = "\x06\x04\xc1\x01\x0a\xc1\x02\x0b"; + TEST_ENV(DATA, FLUF_MAKE_INSTANCE_PATH(3, 4), true); + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_INT; + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_TRUE(fluf_uri_path_equal(path, &FLUF_MAKE_RESOURCE_PATH(3, 4, 1))); + ASSERT_EQ(value->int_value, 10); + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_TRUE(fluf_uri_path_equal(path, &FLUF_MAKE_RESOURCE_PATH(3, 4, 2))); + ASSERT_EQ(value->int_value, 11); + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_EOF); +} + +AVS_UNIT_TEST(tlv_in_path, payload_write_on_resource_with_riids_only) { + // [ RIID(1)=10, RIID(2)=11, RIID(3)=112 ] + static char DATA[] = "\x41\x01\x0a\x41\x02\x0b\x41\x03\x0c"; + TEST_ENV(DATA, FLUF_MAKE_RESOURCE_PATH(3, 4, 5), true); + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_INT; + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_TRUE(fluf_uri_path_equal( + path, &FLUF_MAKE_RESOURCE_INSTANCE_PATH(3, 4, 5, 1))); + ASSERT_EQ(value->int_value, 10); + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_TRUE(fluf_uri_path_equal( + path, &FLUF_MAKE_RESOURCE_INSTANCE_PATH(3, 4, 5, 2))); + ASSERT_EQ(value->int_value, 11); + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_TRUE(fluf_uri_path_equal( + path, &FLUF_MAKE_RESOURCE_INSTANCE_PATH(3, 4, 5, 3))); + ASSERT_EQ(value->int_value, 12); + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_EOF); +} + +AVS_UNIT_TEST(tlv_in_path, payload_write_on_resource_with_riids) { + // [ RID(5)=[ RIID(1)=10, RIID(2)=11] ] + static char DATA[] = "\x86\x05\x41\x01\x0a\x41\x02\x0b"; + TEST_ENV(DATA, FLUF_MAKE_INSTANCE_PATH(3, 4), true); + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_INT; + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_TRUE(fluf_uri_path_equal( + path, &FLUF_MAKE_RESOURCE_INSTANCE_PATH(3, 4, 5, 1))); + ASSERT_EQ(value->int_value, 10); + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_TRUE(fluf_uri_path_equal( + path, &FLUF_MAKE_RESOURCE_INSTANCE_PATH(3, 4, 5, 2))); + ASSERT_EQ(value->int_value, 11); + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_EOF); +} + +AVS_UNIT_TEST(tlv_in_path, payload_write_on_instance_with_resource_with_riids) { + // IID(4, [ RID(5)=[ RIID(1)=10, RIID(2)=11] ]) + static char DATA[] = "\x08\x04\x08\x86\x05\x41\x01\x0a\x41\x02\x0b"; + TEST_ENV(DATA, FLUF_MAKE_OBJECT_PATH(3), true); + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_INT; + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_TRUE(fluf_uri_path_equal( + path, &FLUF_MAKE_RESOURCE_INSTANCE_PATH(3, 4, 5, 1))); + ASSERT_EQ(value->int_value, 10); + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_TRUE(fluf_uri_path_equal( + path, &FLUF_MAKE_RESOURCE_INSTANCE_PATH(3, 4, 5, 2))); + ASSERT_EQ(value->int_value, 11); + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_EOF); +} + +AVS_UNIT_TEST(tlv_in_path, empty_instances_list) { + // [ Instance(1), Instance(2) ] + static char DATA[] = "\x00\x01\x00\x02"; + TEST_ENV(DATA, FLUF_MAKE_OBJECT_PATH(3), true); + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_ANY; + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_EQ(type_bitmask, FLUF_DATA_TYPE_NULL); + ASSERT_TRUE(fluf_uri_path_equal(path, &FLUF_MAKE_INSTANCE_PATH(3, 1))); + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_EQ(type_bitmask, FLUF_DATA_TYPE_NULL); + ASSERT_TRUE(fluf_uri_path_equal(path, &FLUF_MAKE_INSTANCE_PATH(3, 2))); + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_EOF); +} + +AVS_UNIT_TEST(tlv_in_general_tests, + feed_payload_with_chunk_of_size_zero_with_finished_set_to_true) { + // [ RID(1)=10 ] + static char DATA[] = "\xc1\x01\x0a"; + + // payload_finished flag set to false + TEST_ENV(DATA, FLUF_MAKE_INSTANCE_PATH(3, 4), false); + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_INT; + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_TRUE(fluf_uri_path_equal(path, &FLUF_MAKE_RESOURCE_PATH(3, 4, 1))); + ASSERT_EQ(value->int_value, 10); + + // below call to fluf_io_in_ctx_get_entry() should return + // FLUF_IO_WANT_NEXT_PAYLOAD as last feed was with payload_finished flag set + // to false + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_WANT_NEXT_PAYLOAD); + + ASSERT_OK(fluf_io_in_ctx_feed_payload(&ctx, "", 0, true)); + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_EOF); +} + +AVS_UNIT_TEST(tlv_in_general_tests, check_want_disambiguation) { + char *in_tlv = "\xC7\x05" + "1234567"; + fluf_io_in_ctx_t ctx; + ASSERT_OK(fluf_io_in_ctx_init(&ctx, FLUF_OP_DM_WRITE_PARTIAL_UPDATE, + &TEST_INSTANCE_PATH, + FLUF_COAP_FORMAT_OMA_LWM2M_TLV)); + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_ANY; + const fluf_res_value_t *value = NULL; + const fluf_uri_path_t *path = NULL; + ASSERT_OK(fluf_io_in_ctx_feed_payload(&ctx, in_tlv, 9, true)); + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_WANT_TYPE_DISAMBIGUATION); + ASSERT_TRUE(fluf_uri_path_equal(path, &FLUF_MAKE_RESOURCE_PATH(3, 4, 5))); + ASSERT_NULL(value); + type_bitmask = FLUF_DATA_TYPE_BYTES; + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_TRUE(fluf_uri_path_equal(path, &MAKE_TEST_RESOURCE_PATH(5))); + ASSERT_EQ(value->bytes_or_string.chunk_length, 7); + ASSERT_EQ_BYTES(value->bytes_or_string.data, "1234567"); + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_EOF); +} + +AVS_UNIT_TEST(tlv_in_general_tests, string_in_chunks) { + // RID(01)="Hello, world!1234567892137Papaj" + static char DATA1[] = "\xC8\x05\x1F" + "Hello, world!"; + static char DATA2[] = "123456789"; + static char DATA3[] = "2137Papaj"; + + fluf_io_in_ctx_t ctx; + ASSERT_OK(fluf_io_in_ctx_init(&ctx, FLUF_OP_DM_WRITE_PARTIAL_UPDATE, + &TEST_INSTANCE_PATH, + FLUF_COAP_FORMAT_OMA_LWM2M_TLV)); + const fluf_res_value_t *value = NULL; + const fluf_uri_path_t *path = NULL; + + // feed first chunk + ASSERT_OK( + fluf_io_in_ctx_feed_payload(&ctx, DATA1, sizeof(DATA1) - 1, false)); + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_STRING; + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_TRUE(fluf_uri_path_equal(path, &MAKE_TEST_RESOURCE_PATH(5))); + ASSERT_EQ(value->bytes_or_string.chunk_length, 13); + ASSERT_EQ(value->bytes_or_string.offset, 0); + ASSERT_EQ(value->bytes_or_string.full_length_hint, + sizeof(DATA1) - 1 - 3 + sizeof(DATA2) - 1 + sizeof(DATA3) - 1); + ASSERT_EQ_BYTES_SIZED(value->bytes_or_string.data, &DATA1[3], 13); + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_WANT_NEXT_PAYLOAD); + + // feed second chunk + ASSERT_OK( + fluf_io_in_ctx_feed_payload(&ctx, DATA2, sizeof(DATA2) - 1, false)); + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_TRUE(fluf_uri_path_equal(path, &MAKE_TEST_RESOURCE_PATH(5))); + ASSERT_EQ(value->bytes_or_string.chunk_length, sizeof(DATA2) - 1); + ASSERT_EQ(value->bytes_or_string.offset, 13); + ASSERT_EQ(value->bytes_or_string.full_length_hint, + sizeof(DATA1) - 1 - 3 + sizeof(DATA2) - 1 + sizeof(DATA3) - 1); + ASSERT_EQ_BYTES_SIZED(value->bytes_or_string.data, DATA2, + sizeof(DATA2) - 1); + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_WANT_NEXT_PAYLOAD); + + // feed third chunk + ASSERT_OK( + fluf_io_in_ctx_feed_payload(&ctx, DATA3, sizeof(DATA3) - 1, true)); + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_TRUE(fluf_uri_path_equal(path, &MAKE_TEST_RESOURCE_PATH(5))); + ASSERT_EQ(value->bytes_or_string.chunk_length, sizeof(DATA3) - 1); + ASSERT_EQ(value->bytes_or_string.offset, 13 + sizeof(DATA2) - 1); + ASSERT_EQ(value->bytes_or_string.full_length_hint, + sizeof(DATA1) - 1 - 3 + sizeof(DATA2) - 1 + sizeof(DATA3) - 1); + ASSERT_EQ_BYTES_SIZED(value->bytes_or_string.data, DATA3, + sizeof(DATA3) - 1); + + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_EOF); +} + +AVS_UNIT_TEST(tlv_in_general_tests, instance_with_rid_of_different_type) { + // IID(4, [ RID(5)=10, RID(6)="Hello, world!" ]) + static char DATA[] = "\x08\x04\x13\xC1\x05\x0a\xC8\x06\x0D" + "Hello, world!"; + TEST_ENV(DATA, FLUF_MAKE_OBJECT_PATH(3), true); + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_INT; + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_EQ(type_bitmask, FLUF_DATA_TYPE_INT); + ASSERT_TRUE(fluf_uri_path_equal(path, &FLUF_MAKE_RESOURCE_PATH(3, 4, 5))); + ASSERT_EQ(value->int_value, 10); + type_bitmask = FLUF_DATA_TYPE_STRING; + ASSERT_OK(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); + ASSERT_TRUE(fluf_uri_path_equal(path, &FLUF_MAKE_RESOURCE_PATH(3, 4, 6))); + ASSERT_EQ(type_bitmask, FLUF_DATA_TYPE_STRING); + ASSERT_EQ(value->bytes_or_string.chunk_length, 13); + ASSERT_EQ_BYTES(value->bytes_or_string.data, "Hello, world!"); + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path), + FLUF_IO_EOF); +} + +AVS_UNIT_TEST(tlv_in_general_tests, get_entry_count) { + fluf_io_in_ctx_t ctx; + ASSERT_OK(fluf_io_in_ctx_init(&ctx, FLUF_OP_DM_WRITE_PARTIAL_UPDATE, + &TEST_INSTANCE_PATH, + FLUF_COAP_FORMAT_OMA_LWM2M_TLV)); + size_t out_count; + ASSERT_EQ(fluf_io_in_ctx_get_entry_count(&ctx, &out_count), + FLUF_IO_ERR_FORMAT); +} + +#define HEADER_IN_CHUNKS(Header1, Header2, Value) \ + HEADER_IN_CHUNKS_TEST(AVS_CONCAT(tlv_in_header_in_chunks_, __LINE__), \ + Header1, Header2, Value) + +#define HEADER_IN_CHUNKS_TEST(Name, Header1, Header2, Value) \ + AVS_UNIT_TEST(tlv_in_header_in_chunks, Name) { \ + TEST_ENV(Header1, FLUF_MAKE_OBJECT_PATH(3), false); \ + fluf_data_type_t type_bitmask = FLUF_DATA_TYPE_BYTES; \ + ASSERT_EQ(fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, \ + &path), \ + FLUF_IO_WANT_NEXT_PAYLOAD); \ + ASSERT_OK(fluf_io_in_ctx_feed_payload( \ + &ctx, Header2 Value, sizeof(Header2 Value) - 1, true)); \ + ASSERT_OK( \ + fluf_io_in_ctx_get_entry(&ctx, &type_bitmask, &value, &path)); \ + ASSERT_EQ(value->bytes_or_string.chunk_length, sizeof(Value) - 1); \ + ASSERT_EQ_BYTES(value->bytes_or_string.data, Value); \ + } + +HEADER_IN_CHUNKS("", "\xC8\x01\x0D", "Hello, world!") +HEADER_IN_CHUNKS("\xC8", "\x01\x0D", "Hello, world!") +HEADER_IN_CHUNKS("\xC8\x01", "\x0D", "Hello, world!") +HEADER_IN_CHUNKS("", "\xF8\x01\x02\x01\x86\xA0", DATA100kB) +HEADER_IN_CHUNKS("\xF8", "\x01\x02\x01\x86\xA0", DATA100kB) +HEADER_IN_CHUNKS("\xF8\x01", "\x02\x01\x86\xA0", DATA100kB) +HEADER_IN_CHUNKS("\xF8\x01\x02", "\x01\x86\xA0", DATA100kB) +HEADER_IN_CHUNKS("\xF8\x01\x02\x01", "\x86\xA0", DATA100kB) +HEADER_IN_CHUNKS("\xF8\x01\x02\x01\x86", "\xA0", DATA100kB) +HEADER_IN_CHUNKS("\xF8\x01\x02\x01\x86\xA0", "", DATA100kB) diff --git a/tests/fluf/utils.c b/tests/fluf/utils.c new file mode 100644 index 00000000..0a52fe75 --- /dev/null +++ b/tests/fluf/utils.c @@ -0,0 +1,55 @@ +/* + * Copyright 2023-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 + +static void test_double_to_string(double value, const char *result) { + char buff[100] = { 0 }; + fluf_double_to_simple_str_value(value, buff); + AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(buff, result, strlen(result)); +}; + +AVS_UNIT_TEST(utils, double_to_str_custom) { + test_double_to_string(0, "0"); + test_double_to_string((double) UINT16_MAX, "65535"); + test_double_to_string((double) UINT32_MAX - 0.02, "4294967294.98"); + test_double_to_string((double) UINT32_MAX, "4294967295"); + test_double_to_string((double) UINT32_MAX + 1.0, "4294967296"); + test_double_to_string(0.0005999999999999999, "0.0005999999999999999"); + test_double_to_string(0.00000122, "0.00000122"); + test_double_to_string(0.000000002, "0.000000002"); + test_double_to_string(777.000760, "777.00076"); + test_double_to_string(10.022, "10.022"); + test_double_to_string(100.022, "100.022"); + test_double_to_string(1000.033, "1000.033"); + test_double_to_string(99999.03, "99999.03"); + test_double_to_string(999999999.4440002, "999999999.4440002"); + test_double_to_string(1234e15, "1234000000000000000"); + test_double_to_string(1e16, "10000000000000000"); + test_double_to_string(1000000000000001, "1000000000000001"); + test_double_to_string(2111e18, "2.111e21"); + test_double_to_string(0.0, "0"); + test_double_to_string(NAN, "nan"); + test_double_to_string(INFINITY, "inf"); + test_double_to_string(-INFINITY, "-inf"); + test_double_to_string(-(double) UINT32_MAX, "-4294967295"); + test_double_to_string(-10.022, "-10.022"); + test_double_to_string(-100.022, "-100.022"); + test_double_to_string(-1234e15, "-1234000000000000000"); + test_double_to_string(-2111e18, "-2.111e21"); + test_double_to_string(-124e-15, "-1.24e-13"); + test_double_to_string(-4568e-22, "-4.568e-19"); + test_double_to_string(1.0, "1"); + test_double_to_string(78e120, "7.8e121"); +} diff --git a/tools/coverage b/tools/coverage new file mode 100755 index 00000000..5cce6eb2 --- /dev/null +++ b/tools/coverage @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# +# Copyright 2023-2024 AVSystem +# AVSystem Anjay LwM2M SDK +# All rights reserved. +# Licensed under the AVSystem-5-clause License. +# See the attached LICENSE file for details. + +set -e + +canonicalize() { + echo "$(cd "$(dirname "$1")" && pwd -P)/$(basename "$1")" +} + +num_processors() { + if command -v nproc > /dev/null; then + nproc + else + sysctl -n hw.ncpu + fi +} + +function die() { + echo -e "$@" >&2 + exit 1 +} + +[[ "$PROJECT_ROOT" ]] || PROJECT_ROOT="$(dirname "$(dirname "$(canonicalize "$0")")")" + +which gcovr || die "gcovr not found, exiting" + +rm -rf "$PROJECT_ROOT/build/coverage" +mkdir -p "$PROJECT_ROOT/build/coverage" +pushd "$PROJECT_ROOT/build/coverage" + cmake -D CMAKE_C_FLAGS="-std=c99 -D_POSIX_C_SOURCE=200809L -g --coverage" -D CMAKE_EXE_LINKER_FLAGS="--coverage" "$@" -H../.. -B. + make -j$(num_processors) fluf_tests dm_tests sdm_tests + ./fluf_tests + ./dm_tests + ./sdm_tests + + mkdir -p "$PROJECT_ROOT/coverage" + gcovr . --html --html-details -r "$PROJECT_ROOT" \ + -f "$PROJECT_ROOT/src" -f "$PROJECT_ROOT/inc" -f "$PROJECT_ROOT/include_public" \ + -f "$PROJECT_ROOT/deps/avs_commons/src" -f "$PROJECT_ROOT/deps/avs_commons/include_public" \ + -o "$PROJECT_ROOT/coverage/coverage.html" +popd + +cat <